A good way to look at it is to try to make the code tell a story. Being explicit and making the code as clear as possible should also be part of the team culture. People should not be afraid to say that a part of the code is hard to understand. By doing so, they’re not saying they don’t understand the code or that they’re not good developers. They’re saying that the logic could be written in a way that would make it easier to understand. A team should have a common understanding that code hard to understand is not up to the team standards and should be improved. We should be considerate of other people’s time. While doing code review, developers should spend time reviewing the logic and the architecture instead of trying to understand what the code is doing. It will also help tremendously to the code maintainability since it’s easier to refactor something that is easy to understand and will reduce the risk or errors while refactoring or changing the code behavior. It can also often help understand the “why” as much as the “how”.
Naming is hard
You might have already heard the popular saying
Name your variables as if the person who will next read your code is a psychopath who knows where you live.
There are only two hard things in computer programming: cache invalidation and naming things.
It’s common to have a hard time naming a variable, at least not for me since I’m awful at it. This is an important step when writing code since a wrong variable name can quickly cause confusion throughout a codebase. Using a generic variable name, like
x, will add a layer of abstraction to the code since people will have to infer what it means from the context. A bad nomenclature can also be misleading, leading team members to understand something different and create confusion throughout the team.
It’s also good to stay boring. Either for a project, module, or variable name. It can be tempting to get creative and use a reference or try to look funny. Usually, if the name is boring, it means it does his job. Turnover in an engineering team can be more frequent than in other industries, so you should never assume that there will have someone there able to explain the context in the future.
It’s also important to give as much context as possible when choosing a variable name. Generic names like
params should be avoided. If by trying to give enough context you end up with a super long name like
GetUserDetailAndCreateBookIfMissing, it usually means the function is doing too many things at once. Splitting the logic into multiple smaller parts can be a solution to those long names. You can also create modules to isolate a part of the code;
Book.getByName(...) instead of
Sometimes smaller is better! Let’s take a function size as an example. The longer it gets, the more complex it becomes. There’s a principle called single responsibility that really helps to avoid those issues. As much as possible, a function should do only one thing but to do it well. Keeping a function simple will also have the side effect of being easier to unit test and make the code more robust.
The same concept also applies to code reviews. Introducing too many changes at once can have negative effects. The bigger the pull request is, the harder it is to get good and pertinent feedback. It’s not always possible to have a working feature with only a subset of changes. A way to keep the pull request smaller in these scenarios is to use a feature branch, and gradually introduce changes, even if the code merged isn’t 100% functional. This way it’s going to be easier to review.
Pull requests are useful to propose changes and discuss alternative solutions, but it’s also a good medium to share knowledge and context. Asking questions during the code review process should be encouraged for parts that are unclear to someone since it usually means the pull request lack context or the code lacks clarity.
Consistency is key when we want to keep a whole codebase readable. Enforcing it manually can be tough, but fortunately, there are plenty of tools that can help. A code linter like eslint and a code formatter like prettier can be used as part of a team arsenal to automate the process so people can focus on more important things and stop arguing about personal preferences like using spaces vs tabs.
It’s also always a good idea to have a proper code-style guideline documented for things that can’t be covered by those tools. That documentation can be useful for new developers joining the team so they can adapt to the team style more quickly.
Types are your friend
Good code should be self-documenting. If you have to explain what’s happening, it usually means the code is not clear enough and could be improved. Sometimes, the logic has to be complex by nature, so it’s not always possible to simplify it enough. Adding a small comment in these situations can go a long way.
A better way to look at comments is to use them to give context on things that can’t be inferred by looking at the code. We usually can understand what the code is doing by looking at it, but we can’t always explain the context around it. If you can’t figure out “why” something was done by looking at the code ( could be because there’s a weird behavior from a third-party library, a product decision that doesn’t fit well into the architecture, or the code is depending on a legacy system ), then it’s a good idea to comment it. We don’t know who’s going to look at that code in the future, so it’s good to assume these people will have zero context about it.
- Avoid nested ternary since it’s making it more difficult to see the separation between multiple ternaries.
if logic simple by splitting conditions with properly named variables.
- Avoid unnecessary negation to prevent ending up with a double negation.
- Split complex operations into multiple steps instead of chaining/indenting them. The main benefit is to force having a clear variable name to every step to give more context to the operation. It will also allow the code to breathe more and makes it easier to think about one specific step at a time.
- Add the
is prefix for boolean variables. Ex:
isOpen instead of
- Avoid abbreviations for a variable name. Ex:
firstName instead of
password instead of
- Don’t try to write ninja code.
Ideally, features would go from a prototype phase ( making it work ) to a productized phase. In a professional context, speed tends to be highly prioritized for external or monetary reasons, often cutting short the feature development at the prototype phase, or in an unfinished productized phase. It can lead to a lack of vision in the long term and can become really problematic as a codebase grows. Writing code, as well as other forms of writing, is hard. It takes time and experience to be able to write clean code, especially in a larger codebase. This responsibility should not land on the developer’s shoulders only, the whole company needs to be on board. This means that communication with other team members is very important and is something that needs practice and should be nurtured.
When writing code, it’s really hard to get everything right the first time, even for experienced developers. Making the code work is only the first step. The next one is to take a step back and focus on making it clean, maintainable, and easy to understand. Having the right mindset and using a few simple tricks like the ones mentioned above can make a big difference. It will benefit you, other team members, and the whole organization in the long run. This is what makes great developers stand out, they make it look easy, they write to read.