Programmers complain about readability and talk about bad code and unclean code, and the difficulties they run into trying to understand and maintain that code. What do we mean by readability? What makes code unreadable?
Russian translation of this article available thanks to Vlad Brown:
Что означает читаемость кода?
Reframing the problem
I hear programmers say that some code “is unreadable.” I read articles and books about readability and maintainability. What does that mean? Programmers usually attribute readability, or more often the lack of, to the code itself. But, like beauty, readability lies in the eyes of the beholder. When we say that some code “is unreadable” we actually mean one or more of:
- I can’t read the code because I don’t have sufficient experience or expertise (with the language or domain).
- I haven’t spent enough time trying to read and understand the code (“it’s not obvious” or “it’s not intuitive”).
- I don’t have much interest in understanding this code, I prefer to rewrite it in my own style.
- The code offends my sense of aesthetics; I would write it differently.
- The original programmer didn’t know how to write code.
- The code appears to violate some principles or patterns I believe in.
Simply reframing the statement “this code is unreadable” as “I can’t read this code” puts the problem into perspective.
By analogy, plenty of people find reading Homer, Shakespeare, or Nabokov difficult and challenging, but we don’t say “Macbeth is unreadable.” We understand that the problem lies with the reader. We may not have sufficient experience with the language and idioms. We may not have enough historical and cultural context (similar to lacking domain expertise when looking at software). We may not have the patience or desire to invest time learning how to read a challenging book. Wikipedia articles and Cliff’s Notes exist to give tl;dr versions of books to people who can’t or don’t want to read the original. When we observe this tendency in other (non-programming) contexts we may interpret it as laziness or short attention span. When we react this way to code we blame the code and the original programmer.
When I first read Knuth’s The Art Of Computer Programming as a teenaged amateur programmer I found Knuth’s math-heavy analysis of algorithms hard to understand. I didn’t think Knuth doesn’t know how to write, or that his examples and explanations needed refactoring and dumbing-down. I thought I needed to learn more about the math and analytic techniques so I could competently understand the books. As my comfort with the math improved Knuth’s previously unreadable books yielded a lot of great information.
I recently told a programmer friend that, in my experience doing a lot of maintenance work on legacy systems, all code yields to maintenance with enough study. I get more concerned about the time I require to understand the code, and the risks when making changes to it. Good code should yield to understanding, and present fewer risks when making changes. Those qualities come from the skill of the original programmer, and the constraints imposed at the time they wrote the code. The original programmer may have tried to predict future maintenance and write the code in a way they think of as clear and readable, but doing that requires making assumptions and predictions about unknown future requirements, and unknown future maintenance programmers. Programmers can easily get carried away making those assumptions, adding complexity and generality and getting farther away from the requirements, without actually making the code more readable or maintainable.
Programmers usually think that they should focus on writing code. Reading code, especially someone else’s code, seems like grunt work, a necessary evil, often relegated to junior programmers in maintenance roles. Reading code and figuring it out does not feel like a creative or even productive activity.
I have personally witnessed (more than a few times) professional programmers dismiss working, production code as “unreadable” and “unmaintainable” after looking at it for just a few minutes. Their objections usually come down to aesthetics: “I hate PHP.” “The code uses tabs instead of spaces and looks bad in my editor.” “The code wasn’t written with classes and objects.” They haven’t spent enough time to understand the system, or learning enough about the business domain to read the code in context (like someone unaware of ancient Greek history dismissing The Iliad).
Programmers looking at code they don’t understand or don’t like can find examples of sacred principles and so-called best practices violated: “This breaks the Single-Responsibility Principle.” “This looks like a DRY violation.” “Globals are a code smell.” Then they propose rewriting the system, or maybe rewriting pieces of it in their preferred language, idiom, and style (often erroneously called “refactoring” because that sounds like a technical thing to the customer or boss).
Whenever I have to think to understand what the code is doing, I ask myself if I can refactor the code to make that understanding more immediately apparent. – Martin Fowler.
Any fool can write code that a computer can understand. Good programmers write code that humans can understand. – Martin Fowler
All respect to Martin Fowler, but those quotes illustrate my point. My experience leads me to expect I will “have to think” to understand unfamiliar code. How hard I have to think matters, but I don’t expect to immediately understand non-trivial code at a glance, even with my decades of experience. After I understand the code I may think I can write it more clearly and refactor it, or I may feel like I learned something new from the code and leave it alone. I don’t think code has to yield to understanding at a glance, especially considering the very wide range in skills and experience among programmers. And I don’t call other programmers fools if I don’t immediately understand their code. “Other humans,” or even other programmers, includes far too many people across a vast skill and experience spectrum to make “write code that humans can understand” a meaningful goal. If good authors wrote books that everyone could instantly understand we’d have nothing but The Hungry Caterpillar and Puppy Peek-a-boo on our bookshelves.
Just because people tell you it can’t be done, that doesn’t necessarily mean that it can’t be done. It just means that they can’t do it. – Anders Hejlsberg
That applies just as much when reading code as it does when writing code. Remember that the next time you or someone you work with declares some code “unreadable” and “impossible to maintain.”
The true test of intelligence is not how much we know how to do, but how to behave when we don’t know what to do. – John Holt
Simple vs. dumbing down
This came across my Twitter feed a while back:
Good code is simple. Code reviews are a great way to train teams to write simple code. Don’t be afraid to say “this is hard to understand.” – Eric Elliott
“Good code is simple” doesn’t actually say anything. My many years of programming experience and business domain expertise gives me a very different idea of “simple” than someone with less experience and no domain expertise looking at some code for a few minutes. What we call “simple” depends on our experience, skills, interest, patience, and curiosity. Programmers should say something when they don’t understand code, but rather than saying “this code sucks” they should say “I can’t understand this code – yet.” That puts the focus on the person who struggles to understand rather than on the code. I agree that code reviews improve code quality and team cohesion, but whether that translates to “simple” code depends on the programmers. Programming teams usually converge on common idioms and style, and that make programming together easier, but that convergence doesn’t mean the code will look readable to an outsider looking at it six months later.
Does that mean all programmers should dumb their code down so even beginners with no domain expertise can understand it at a glance? Should we strive to satisfy the Shakespeare for Dummies demographic? When faced with code I don’t understand I first question my own skills and patience, and if I have sufficient motivation (like a paying customer) I will spend time studying the code to improve my ability to understand it. I may have to look at language or framework documentation, or experiment with the code to figure out how it works. When I understand the code I may think that I know a simpler or more clear way to express it, or I may think that the code only presented a challenge to me because I didn’t have the skills or knowledge or right frame of mind. In my experience figuring code out takes significant time and effort, but when I get through that I don’t usually think the code has fatal readability flaws, or that the original programmer didn’t know how to write code.
Controlling complexity is the essence of computer programming. – Brian W. Kernighan
Complexity in programming happens at many levels, from the overall system architecture to the choice of variable names and flow control idioms. With experience and reflection programmers should develop a sense for unnecessary complexity, and learn how to control it in their own work. The presence of apparent complexity does not always mean the code sucks or resists reading; some problems prove complicated to implement and the resulting code will require some study to understand.
Readability performance art
We think of programming as a process of using and writing abstractions. When the abstractions map to the requirements in a reasonably obvious way, the code usually makes more sense. When the abstractions come from gratuitous application of “design patterns” or “best practices” for their own sake, the code gets harder to understand because you have to mentally follow disconnected chains of abstractions: the business requirements and the software implementation.
In the olden days programmers wrote code that few other programmers would see – the other members of their team or group, their mentors, maybe an analyst or project manager. Now we have open source and people posting their code in blogs and in Stack Overflow questions. Programmers, prone to investing their ego in their code, worry about criticism from a lot of people they don’t know. Slurs about competency abound in programmer forums. Code samples out of context get critiqued and subjected to stylistic nit-picking. That process can prove helpful for programmers with thick skins who can interpret criticism as either useful mentoring or useless insults. Less experienced programmers can get misled and brow-beaten. Public code reviews create a kind of programmer performance art, when programmers write code to impress other programmers, or to avoid withering criticism from self-appointed experts.
The key to efficient development is to make interesting new mistakes. – Tom Love
It helps to discover your own mistakes or have someone point them out to you in a helpful way. That’s how we learn to program, and learn how to write better code. I don’t think it helps to write code with the prospect of public humiliation in the back of your mind.
Programming is a creative art form based in logic. Every programmer is different and will code differently. It’s the output that matters. – John Romero
The most important property of a program is whether it accomplishes the intention of its user. – C.A.R. Hoare
Better programming comes through practice, study (from books and other code), and mentoring. It doesn’t come from trying to blindly adhere to rules and dogma and cargo cults you don’t understand or can’t relate to actual code.
How to write readable code
When I started programming back in the mid-1970s, one of my mentors gave me a copy of The Elements Of Programming Style (Kernighan and Plauger, 1974). From that book I learned that some techniques often worked better than others, I learned about language idioms, naming variables and functions, and other stylistic and aesthetic considerations that have stuck with me. From there I read quite a few other books about “good” programming, above the level of style and aesthetics. And I read a lot of code, some I understood and a lot that I didn’t (or didn’t understand right away). Like the English writing classic The Elements Of Style, Kernighan and Plauger focus mainly on coding techniques and style, not on the larger issues of system design, module decomposition, cohesion and coupling, and requirements gathering. The Elements Of Programming Style serves as a starting point, an introduction to developing a style that will make your code expressive, concise, and easier to read. Similarly The Elements Of Style advises “Omit needless words” and cautions writers away from passive voice and too many adjectives.
We build our computer (systems) the way we build our cities: over time, without a plan, on top of ruins. – Ellen Ullman
Well, yes, and we tend to like many of our cities. I agree with Ellen Ullman that software development often follows a more haphazard path than we like to tell ourselves. That just means we can’t reliably predict the future, and we don’t always have the resources or will to tear things down and start over. The genre of programming aphorisms brims with statements like this, which only make sense if we believe in a “right way” to develop software, a set of hard, proven principles that lead to “good” code, and that any code we don’t immediately understand must have inherent bad-ness, or indicates a fool at the keyboard.
Programmers seem to believe in a realm of beautiful, readable, easy-to-maintain code that they haven’t seen or worked with yet. They seem to think that other programmers get the time and support to write perfect, clean, tested code. That mythical realm doesn’t exist. All code baffles and frustrates and offends a significant subset of programmers. All software gets developed under time, budget, management, requirements, and skill constraints that prevent doing anything perfectly. We should keep those constraints and limits in mind when we look at code and immediately conclude the code resists understanding, or that only fools would have produced such software.
No one in the brief history of computing has ever written a piece of perfect software. It’s unlikely that you’ll be the first. – Andy Hunt
How do you learn to write readable code? Like learning to write readable English, you have to read a lot. Spend the time to try to understand code beyond superficial qualities that don’t match your biases and preferences. Emulate code you find especially readable. Read some of the popular well-reviewed books on programming style and code quality to get some benefit from more experienced programmers. Try to describe why code resists easy reading or maintenance in concrete terms, try your alternatives (refactorings), make sure they work, get other programmers to constructively review your code and listen to them.
Thanks to the Programming Wisdom @CodeWisdom Twitter feed for the quotes in this article.