“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code.”
― Robert C. Martin, Clean Code
As developers, we spend a lot of time reading the code. We read the code of our colleagues, open-source repositories, and of course our own code. And sometimes it is a hard job. Mental overhead of reading a “dirty” and unreadable code is huge, you know that without me. Here I will give you several principles, hints, and rules of “how to make your code more readable”.
What makes code unreadable?
The main reason why the code becomes unreadable is the accumulation of complexity.
When you only start to write your program it looks simple and shiny. It is clearly visible what your code does, what your variables and constants mean, execution flow looks simple. But with adding more features to your program it becomes more and more complex. You add new functions and methods, new variables and constants, and so on. At some point, you understand that your code became less readable. The cause is accumulated complexity.
Here we have a paradox — we cannot simply remove the complexity because our code is the complexity itself. Code is what makes our program work.
The second reason is connected with how human memory works.
While working with the code you operate mostly with short-term memory. According to Miller’s Law, this type of memory can hold 7±2 objects. I would say, it is very optimistic, in real life it will be closer to 4–5. If your code deals with more objects than your memory capacity can hold, you are in trouble.
Ok, what about long-term memory, can we use it instead? Well, yes, it has a much longer duration and much larger capacity, but the problem is that there is no fast way to put something in long-term “storage”. And long-term memory won’t help us if we read some code for the first time, for example.
So, how to overcome accumulated complexity and small capacity of our short-term memory?
Answer: distribute the complexity.
You can hide, reduce and distribute the complexity using the following three approaches:
Let’s look at each of them.
Our brain works more efficiently if it is able to recognize patterns. So we should exploit this for our own good.
Recipe #1: use style guide
Define for yourself or company you work for a style guide and follow it strictly.
Setup a linter for your favorite IDE and enforce the chosen style for the whole project. At first, you may have trouble getting used to the new coding style (if you didn’t use it before), but in the long run, you will benefit from that approach.
Recipe #2: use libraries and frameworks
It is always easier to read the code if you already worked with a given library or framework in the past.
For example, if you used axios library in past, you will dive much quicker into new code which uses this library compared to the code which uses a self-written wrapper for AJAX requests. Also, libraries will help you to avoid reinventing the wheel (cap!).
Frameworks will standardize the code in general. The same principle here: if you worked with e.g Laravel framework before, you will quickly understand the new project and will start contribute faster.
Decomposition will help decrease cognitive overhead and make your code more organized.
Recipe #3: split the code
This one is simple — the more your code grows, the more you should split it into smaller logical parts.
Imagine that you start with a single-file program. You keep adding features and functionality and it becomes tedious to manage everything there, so you split it into several files. You find some bits of logic that work together and place them into a separate file. Keep splitting your program until distinct parts of a single file become unsplittable.
Later on, you should bundle such files into modules and packages.
Later on, you can start using the programming abstractions of your languages — classes, mixins, interfaces, and other syntax sugar.
Not only this will make your program parts more reusable, but also decrease a mental overhead when reading those parts.
Recipe # 4: use single responsibility principle
To help you understand how split your code use single responsibility principle. Your code will be much readable if every entity of your code is responsible only for a single functionality.
Try to make your functions short. Short and compact.
Function should be responsible for one action. If you see that it is doing a few actions, try to split it into several functions. It will help both readability and reuse-ability. If your function actions depends on a flag it may be a sign, that it is actually doing more than one action.
Don’t pass too many arguments into your functions. The more arguments it receives the harder it will be to understand such function.
Small functions are easier to read.
Meta-information is an additional information about your code. Such information doesn’t influence how your code works, but it greatly helps readability.
Recipe #5: use good identifiers
Identifiers are names of variables, constants, functions, classes, etc.
Remember, good identifier should speak for itself. It should answer the question “what is this?”. If you can answer that question without reading the value of that identifier — you have a very nice name.
camelCase, in Python it is
snake_case, etc. Consistency is the key.
Don’t be too afraid of composite names. For example, method
getElementById contains 4 words inside, but it doesn’t hurt readability. Our brain actually plays a little trick to us — such identifiers are read as a single entity, so it is actually not heavy to read. Of course, don’t get too courage here and don’t compile an identifier with 26 words.
Recipe #6: use comments wisely
Comments should answer the question “why?”.
Bad comment explains what the code is doing, the good comment says why it is doing that. The best comment is your code itself, of course, because identifiers should tell a story. But sometimes you need to pull this extra knowledge from your head and make a good comment in order to help future you to better understand the decision.
Actually, good comments are hard. Consider this python example:
sorted(users) // sorting a list
sorted(users) // we need to show new users on top
A classic trap of “what” and “why” questions. In the first example, comment explains what your code is doing. And it is redundant because the code itself will tell you that. In contrast, the second example explains why exactly you need to apply sorting — because you want new users to be on top.
Recipe #7: create documentation
This one is tricky. This causes better readability indirectly. Let me explain.
Usually, documentation is the last resort. We go to the documentation when we can’t understand some behavior of the code. But there are several pitfalls with the docs:
- most of the developers are lazy to write it
- docs tend to de-synchronize with the code
- usually time-consuming for developers, so skipped
But there is a better approach for this issue — documentation in the code.
Frameworks developers use this trick very often. Idea here is to add comments to the definitions of your methods and functions right inside the code. In such a way you avoid writing classic docs, but still provide quite a good explanation of what is happening there. Later, you may use some docs compiler to generate static version out of inline docs.
It is a pleasure to see inline documentation of the method because without reading the code itself you are able to understand what this method is doing, what are the parameters, and what is the output. Very convenient.
Let’s do a quick round-up of all the recipes:
- Use a style guide
- Use libraries and frameworks
- Split the code
- Use Single Responsibility principle
- Use good identifiers
- Use comments wisely
- Create documentation
I hope these recipes will help you write a better code. Happy clean coding!