Why and how to write code for humans?
If you think that programming languages are the language of machines, you are mistaken.
The programming languages are primarily developed for us, the human beings. Yes, we use them to instruct computers on what they should do, but we do it in a way that we software developers prefer more.
Machines prefer code like this:
01001011 10100110 11111110 00101100 10110011 10110011 00101101
Developers prefer code like this:
function calculateSum(a, b) {
return a + b;
}
So if you write for machines, why wouldn’t you just use ones and zeros?
Why you should write code for humans?
Here are some reasons why you should target your code primarily for human beings rather than machines.
After a couple of months, you don’t remember what your code does
When you return to code that you wrote months ago, it’s likely that you’ve forgotten what each method or line does and why you wrote the code in that way.
For example, let’s consider a scenario in which we had written the calculateSum
function a long time ago as follows:
function a(b,c){return b+c;}
The function works, but its purpose may not be immediately clear without reverse engineer or simply remembering it. However, in the long run, you cannot really remember every detail of the codebase, so you end up exploring legacy code.
While this example is oversimplified, it illustrates the importance of writing code for developers rather than machines. Code that is easier to read requires less reverse engineering and ultimately saves you a huge amount of time in the long run.
You use much more time reading code than writing it
The world is full of software that is used every day by countless people. Developers must maintain this software, add new features and fix bugs. This means that many developers will spend a lot of time reading existing code.
Writing new software from scratch is a rare luxury, and even then, you’ll read plenty of code after the first few weeks or months of the development.
It is said that the time the developers spend writing code would be as little as 1 to 10 minutes per day. This might be a lowball estimate, but the message is clear: We use much more time reading code than writing it.
“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
It’s important to keep in mind that while you may write new code only once, others will read it more often. And you’ll need to read your own code too, such as when fixing bugs. That’s why writing clean code can save a lot of time and frustration for both you and your team members.
Your productivity decreases when you are dealing with messy code
If you have worked as a software developer, it is likely that you have encountered messy spaghetti code. Spaghetti code refers to unstructured and difficult-to-maintain source code. It is the kind of code that you curse when you have to work with it. You know it takes plenty of time to figure out what the code does, how it works, and why it exists. Your productivity suffers when you have to spend time reading the legacy code repeatedly instead of focusing on your task.
Our human mind is quite limited. The working memory is like a small desk where things get dropped out when you put new things in. This means that if you have to think all the time about what the existing code does, you have less of your working memory to be used to solve your task. This makes working with messy code even slower and less productive.
You may have experienced situations where, after investing time in deciphering what legacy code does, you forgot your original task and implementation idea. This is because your working memory got overloaded, and your brain’s “garbage collector” cleared its “internal cache” to allocate more working memory to code reverse engineering. In other words, the cognitive load of working with messy code was high.
Keeping your code well-structured and clean reduces cognitive load and makes it easier to maintain productivity for both you and your team.
Code is your language to express your solutions
You express all your solutions ultimately by code. Although you may sometimes create design documentation or specifications, the source code is where you fully describe your solution. It’s your language for expressing how you fulfill the customer’s requirements and describing what the system exactly does and how it does it.
Your primary audience is humans, with machines being secondary. Remember: they prefer ones and zeros, not programming languages of higher abstraction levels.
Express your solutions in a way that you can be proud of and you and your colleagues can read and understand afterwards.
Generative AI tools work better with more descriptive code
Generative AI tools are becoming increasingly popular in software development.
We can leverage the improved readability of code also with generative AI tools. When the code is more descriptive, the AI tool has a better understanding of its context. You can think of it as the AI having two inputs: the program execution logic and its real-life counterpart or context.
This means that the input of generative AI tools like GitHub Copilot can be enhanced by writing more descriptive code, which in turn improves their output.
It’s a joy to work with clean and well-written code
Last but not least, when your code is easy to read, well structured and has an excellent design, it’s a joy to write new features on top of it. You can simply focus on implementing the feature. The clean code is fun to work with.
Be kind to your colleagues and, more importantly, to your future self. Write code for humans, not for machines, and let yourself experience the joy of working with clean code.
How to write clean code for humans?
It’s easy to say that you should write your code for humans, but mastering this skill is harder than you might think. Fortunately, with practice, you get better on it as with any skill. Here are some tips to get started on improving your skills.
“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” -Martin Fowler
1. Use descriptive names
If you have only one takeaway from this blog post, it should be this: Use clear and descriptive names for variables, functions and classes. Avoid abbreviations and creating new jargon. Variable names should tell what they contain and function names should tell what they do.
“Names in software are 90 percent of what make software readable” - Robert C. Martin
😡 Avoid:
const cc = 3;
🙂 Prefer:
const catCount = 3;
// or
const numberOfCats = 3;
2. Use constants instead of hard-coded values
In your code, use constants, such as SECONDS_IN_DAY
instead of hard-coded values, such as 86400
because they are much descriptive. The only hard-coded values you should use in your code are 0 and 1, which are usually self-explanatory for the reader. It’s better to use constants for any other values to describe the meaning of them. This usually allows you to omit comments as well.
😡 Avoid:
if (time > 86400) { // seconds in a day
// ...
}
🙂 Prefer:
const SECONDS_IN_DAY = 24 * 60 * 60;
if (time > SECONDS_IN_DAY) {
// ...
}
3. Do one thing well
Each part of your code should do one, and only one, thing well. This improves the structure and design of the code and thus makes it more readable.
Write:
- Short functions which do only one thing. If the function does not fit to the display, it is way too long. (And buying a bigger display does not improve your function.)
- Short and simple classes which follow the single responsibility principle (SRP). Every class should have one responsibility or purpose.
- Short files. If your file has over 300-400 lines of code, consider splitting it into multiple files by splitting the class or module to smaller ones. Prefer composition over inheritance.
😡 Avoid:
function addNewUser(username, emailAddress) {
let users = db.query("SELECT * from users WHERE username = $1",
username);
if (users) {
throw new Error("Username already exists");
}
db.query("INSERT INTO USERS(username, email) VALUES ($1, $2)",
username, email);
mail.sendMail({
from: '[email protected]',
to: emailAddress,
subject: 'Welcome to my service',
text: 'Hello'
}
}
🙂 Prefer:
function registerUser(username, emailAddress) {
userService.addUser(username, emailAddress);
userService.sendWelcomeEmail(username, emailAddress);
}
“But doesn’t the function still do two things?” you may ask. It adds a user and sends an email. So, does the function adhere to the single responsibility principle?
Robert C. Martin has a rule in his book “Clean Code”: “To know that a function is doing more than ‘one thing’ is if you can extract another function from it with a name that is not merely a restatement of its implementation [G34].”
As we cannot extract nothing but single-line functions, we can conclude that the function follows the SRP principle.
4. Flatten arrow code
Arrow code is code with deep indentations and excessive nesting of loops or conditional clauses. It looks like in the example below:
😡 Avoid:
if (a) {
for (...) {
if (b) {
for (...) {
if (c) {
...
}
}
}
}
This type of code can be difficult to read and often makes methods longer than necessary, doubling our pain. Deep nesting is known as the Arrow Anti-Pattern.
To avoid deep nesting, follow the single responsibility principle of doing one thing well. By doing so, your functions should not have any arrow code at all.
The arrow anti-pattern has found its way into many codebases, so you often need to refactor the code to make it better. The easiest way to flatten the code is by extracting methods from it. Consider the following example:
😡 Avoid:
for (const duck in ducks)
{
if (duck.color=="yellow") {
for (i=0; i<3; i++) {
duck.quack();
}
}
🙂 Prefer:
for (const duck in ducks)
{
duck.quackIfYellow();
}
We have extracted most of the code into quackIfYellow()
method and the arrow anti pattern is gone. For more tips, see Flattening Arrow Code blog post by Jeff Atwood.
5. Have the same order in all of your classes
Be consistent in how you order your classes. I mean deciding where to place member variables, public/private methods, static methods and so on. When all your classes use the same ordering, you save time in two ways:
- You can find the code you need more easily
- You don’t have to spend time deciding where to place new code.
Consistency is more important than the exact ordering of your classes. Personally, I prefer to use this order:
- Member variables on the top of the class. Constants first, then public, protected and private variables in that order.
- Constructors
- Public methods. Static ones first.
- Protected methods. Static ones first.
- Private methods. Static ones first.
I prefer placing public methods before protected and private ones because they are more frequently used. Private methods contain only implementation details of the class, while public methods serve as the interface of the class to the outside world.
6. Let the linter take care of formatting
The formatting of the code files is the job of machines. Save your time and energy by not arguing about how you should format the code. It’s simply a waste of your time.
- Use a linting tool. A linter, or lint, is a static code analysis tool used to find errors in code. It can be used to check the formatting of your code by comparing it to a pre-defined formatting ruleset. Use Google to find a linter for the programming language you are using.
- Prefer an existing, widely-used ruleset if you are starting a new project, or customize the rules to match your project if you have an existing code repository. It is more important to be consistent with formatting than with how all the formatting rules are defined.
- Make the linter run every time you check in code to the repository.
Using a linter allows you to focus on more important things than checking the formatting of code. Code reviews are also more productive when you can focus more on the design instead of the formatting.
7. Use Test-Driven Development (TDD)
Test-Driven Development is a good approach to writing well-structured and easy-to-read code. This is because you write tests before writing the actual code, which forces you to think about the naming and interfaces of the classes and methods. This naturally leads to more well-designed code. Some even call TDD “Test-Driven Design” because of this feature.
Using TDD has other benefits besides improving code design, such as increased maintainability of your code. It is a great tool to have in your toolbox for developing a well-structured code base.
8. Add comments, but only where they add value
I have done more than a hundred dev job interviews in my career and often when I have asked about what is good code like, the first response has been that it has comments. I don’t know where people get this idea, but I think many overrate the importance of comments or misunderstand them. Yes, comments can improve the readability of your code, but they can also make it much worse by being just extra noise and clutter with no real value for the reader.
It is much more important to write code in a way that is easily understandable by others than adding comments.
I am not saying that you should not write comments at all. However, what I suggest is that you:
- Refactor the code so that it does not need comments at all.
- Add comments anyway, if they add value, to make the code even more readable.
Comments should always add value to the reader. They should not repeat what the code already says.
😡 Avoid:
for(i=0; i<100; i++) { // Loop i from 0 to 99
console.log(i); // Log i to console
}
These comments repeat exactly the same thing that is already said by the code. They are just clutter for the reader. Just please don’t do this or you make me cry.
🙂 Prefer:
/**
* Adds specified user and sends them a welcome email.
* @throws {AlreadyExistsException} Username already exists.
*/
function registerUser(username, emailAddress) {
userService.addUser(username, emailAddress);
userService.sendWelcomeEmail(username, emailAddress);
}
Mentioning emailing and explaining what happens if the username does not exist creates value for the reader in the example above. Note that I have not added @param
comments here for the parameters, as they are self-explanatory and clearly strings.
I like to add comments to all public methods where the comment briefly describes what the method does. This helps me remember its purpose. Most IDEs can also display these comments when using the method, which further increases productivity.
Conclusion
Remember that the reader of your code is a human, and you should write it accordingly.
It’s not enough for your code to simply work and accomplish its task. While machines prefer ones and zeros, your fellow developers appreciate well-structured and well-designed code that is easy to read. And trust me, your future self will appreciate this as well.
8 tips for writing clean and readable code:
- Use descriptive names.
- Use constants instead of hard-coded values.
- Do one thing well.
- Flatten arrow code.
- Maintain the same order in all your classes.
- Let the linter take care of formatting.
- Use Test Driven Development (TDD).
- Add comments where they add value.