If the devs on my team remember anything, I want them to remember these fundamental programming guidelines. 🎩
(examples are based on a Javascript environment, but many can be extrapolated to other environments)
Handle all errors
Don't let anything fail silently! You will thank me when you have to debug some random error on Production that does not occur locally.
On the client-side, you'll want to flash the error to the screen in whatever framework you are using:
this.props.dispatch({ type: 'flash-error', data: "Specific error message." })
On the server-side, you'll want to print to the console and either rethrow or send a 500 response (if you are in a route):
someAsyncThing
.then(...)
.catch(err) {
console.error('Specific description of where we are in the code.', err)
throw new Error(err)
// make sure to send a 500 to the client if in a route!
// res.status(500).send('Specific description of where we are in the code.')
})
Make sure you print the actual err object as seen above, not just the error message, so that you print the full stack trace! Do NOT do console.log('ERROR: ' + err) as the error will be converted to a string and the stack trace will be lost!
Prefer immutable
State (mutable values) is the source of all evil. More seriously, it is a messy construct that introduces time as an implicit variable. More simply, it makes your code more error-prone and harder to debug.
- Instead of using
varorlet, useconst. This one will set you on the right path 95% of the time. If you find yourself reaching for something other thanconst, ask yourself if you really need a mutable value. It's far better to create a brand new value most of the time than reusing an existing value. The reason this is so important is not that you are going to realistically mess upvarevery time. It is that writingvarforces other developers to search the entire file to make sure the value is not being modified in any other sneaky ways.constcommunicates loud and clear: this is guaranteed to be the only place this value is set. This communicates clear intentions and saves other programmers lots of time. - Instead of writing a
forloop and pushing items onto an array, do anarr.map(...). - Instead of writing a bunch of logic to process array items to generate a final value, use
arr.reduce(...)(you can really do a lot withreduce). - Instead of modifying a value in an object, use
Object.assignto copy an object with a new value.
Code bunnies everywhere will rejoice! 🐰
Return promises
If you write a function that does some asynchronous processing, return a promise that resolves when it completes! This is a good practice even if you are not using the return value. Without it, there is no way to catch errors, no way to tell how long something took, and no way to serialize other operations. Always returning a promise for asynchronous functions gives you flexibility in the future to chain asynchronous options together and handle errors consistently.
Respect abstractions
What?!? I know. This one is hard to explain. Probably the most important one to help you level up as a programmer though. Write code that does one thing and does it well. Write functions/modules that hide their implementation, only exposing the minimum interface required. Make decisions about what functions/modules are responsible for, and don't let that bleed into other areas!
Saying the job of a programmer is to "just get it to work" is like saying the job of mountaineer is to put their left leg in front of their right. That's the easy part. The job of a programmer is to manage complexity. (Complexity != Learning Curve!!!) Good code hides implementation details while exposing a clean, intuitive interface to other modules. Much of programming is making judgment calls about possible tradeoffs. Optimize for low complexity and surface area and you will be in good shape 99% of the time.
Invest in your codebase
- Use a linter. How nice to have a machine keep your code tidy! 🤖
- Write unit tests. They will save you hours of work finding regressions. 10% vs 0% coverage is 100x better than 20% vs 10% coverage!
- Take time to build protection around common slip-ups. For example, a simple pre-commit hook that checks for extraneous dependencies in a node project will catch developers who accidentally installed a module without saving it to the package.json:
"echo \"Looking for extraneous dependencies...\" && ! (npm ls --depth 0 2>/dev/null | grep extraneous && echo \"Extraneous dependencies detected. Please use \"npm install --save\" for wanted packages and \"npm prune\" to remove all unwanted packages.\")". Yay! - If you realized you implemented something the wrong way, make the effort to fix it even if it's already working. It's one thing to choose a simpler, less robust implementation because of the engineering effort saved. It's another thing to justify keeping shitty code in your code base because "it's not worth it now." Trust me, you do not want to go down this path. It sets up a bad cycle of writing bad code, hiding a deeper problem of poor planning/architectural skills. Those are the raw ingredients of technical debt.
I hope these tips help you and your team level up your skills and maintain a clean and maintainable codebase!
Raine Revere
clarityofheart.com