Good code needs to meet two key requirements. First, it should be correct: when executing, it should produce the result that is expected. Second, it should be easy to read for other developers.
Coding is a social activity. Your code does not exist in a vacuum, just implementing a lone task. The code you write will be re-read by many other developers, who want to either understand or modify how your code works.
Why is readability so important? It's because checking if the is correct is relatively straightforward. Unit tests are excellent ways to do this, but manual tests or monitoring the system also help catch incorrect code.
While incorrect code cannot hide for long, unreadable code can go undetected for a long time. It keeps silent in the codebase until another developer comes along and tries to understand what the code does. They might be trying to fix a bug or adding a new feature. They might want to understand what this unreadable piece of code does and perhaps also need to change it.
When the code is not readable, the engineer changing the code next will burn a lot more time on what should have been straightforward work. They might misunderstand the code and use it in ways it was not meant to be used. They will then spend multiple iterations getting the change right. Or they might change the code, unintentionally breaking functionality elsewhere. If there are automated tests for the breaking functionality, this is only a minor annoyance. If there are no tests, this will lead to more problems and more time spent on making this change correctly.
In some cases, the other developers might spend so much time, failing to understand the code, that they might completely rewrite it: deleting the original code and writing a new implementation. Of course, when rewriting something completely, perhaps not all edge cases will be covered. This will result in more time spent on the rewrite than the time it took to write the original code.
The biggest risk that non-readable code brings is that it starts a pattern of poor readability. The engineer making a change burns a lot of time to figure out how the code should work. Instead of making the code more readable, they make the smallest possible change possible, resulting in even less readable code. And the person coming next will spend even more time understanding the code, unintentionally breaking the system, or just giving up, deleting the code and rewriting the whole thing.
Non-readable code is a large contributor to technical debt. While tech debt can build up for a variety of reasons - a lack of automated testing, lacking processes like CI or CD or poor onboarding and documentation - non-readable code is a major driver of the tech debt that slows teams down.
Readable code, together with well-tested code, are the two most important principles that pragmatic engineers follow. Readable and well-tested code is what makes refactoring, extending, and modifying parts of the system straightforward. Readable and well-tested code is the foundation of a solid codebase, where engineers are confident and quick to make changes.
Readable code will mean slightly different things to each person. It varies between teams, companies, and programming languages. There are two groups of important people who are judges of how readable the code is: yourself and everyone else who will later read the code.
Readable code starts with code that you find easy to read. When you finish coding, take a break to clear your mind. Then try to re-read the code, putting yourself in the mindset that you know nothing about the changes and why you made them.
Can you follow along with your code? Do the variables and method names help understand what they do? Are there comments at places where just the code is not enough? Is the style of the code consistent across the changes?
Think about how you could make the code more readable. Perhaps you see some functions that do too many things and are too long. Perhaps you find that renaming a variable would make its purpose clearer. Make changes until you feel like the code is as expressive, concise, and pretty as it can be.
The real test of readable code is others reading it. So get feedback from others, via code reviews. Ask people to share feedback on how clear the code is. Encourage people to ask questions if something does not make sense. Code reviews - especially thorough code reviews - are the best way to get feedback on how good and readable your code is.
Readable code will attract little to no clarifying questions, and reviewers won't misunderstand it. So pay careful attention to the cases when you realize someone misunderstood the intent of what you wrote or asked a clarifying question. Every question or misunderstanding hints to opportunities to make the code more readable.
A good way to get more feedback on the clarity of your code is to ask for feedback from someone, who is not an expert on the codebase you are working on. Ask specifically for feedback on how easy to read your code is. Because this developer is not an expert on the codebase, they'll focus on how much they can follow your code. Most of the comments they make will be about your code's readability.
If both you and other developers are happy with how readable the code is, you're on the right track. Many principles can help the code become even more readable and more clear. But before you go too deep on these, focus on what matters. Focus on the code being easy to read for you, and for the people you work with.
There are numerous principles on what readable code is. For me, readable code means following these principles.
- Single responsibility. All building blocks - classes, methods, variables - follow the single responsibility principle: every building block does exactly one thing. This makes it easy for the person reading the code to understand what this responsibility is. It also makes it clear what part of the code needs to change.
- Well-structured. The codebase is easy to navigate around, as functions, classes, modules follow a logical structure. Formatting is consistent across classes and the codebase.
- Thoughtful naming. Class, function, and variable names all help understand what is happening, and making reading more seamless. Code that has good names often had developers spend multiple iterations coming to these clear names.
- Simple and concise. The code tries to be as humble and simple as possible. Developers don't use fancy tricks and also avoid over-complicating things. Functions are mostly short, making them easy to read. Classes are also not overly large.
- Comments explain the "why," not the "how." Most of the code can be understood by itself. Comments fill in the remaining gaps.
- Continuously refactored to keep being readable. Codebases grow. As a simple class gets more responsibility, it grows in size and complexity. Readable codebases stay readable due to continuous refactoring. The new, complex class might be broken into multiple parts or changed other ways to stay easy to read.
- Well-tested. Well-tested code can be modified quickly and without fear of breaking things. Having the code tested via automated tests is important for the code to stay readable. Without tests, refactoring the code becomes risky, and developers eventually stop doing it. With tests, there is no excuse on why not to make even large and risky refactors, that keep the code easy to read.
There are many great books and other resources that go into far more depth on what readable code is and ways to make code more clear. I especially recommend the books The Art of Readable Code and Clean Code.
What does readable code mean to you?
This article was originally posted on The Pragmatic Engineer Blog
About me: I'm an engineer turned engineering manager, working at the intersection of Silicon Valley & European startups and tech companies. Follow me here and on Twitter. I am writing a book on growing as a software engineer - read more about it and sign up for updates on it here.