Code is for communication. Good code is clear code. Clever code is crap. As Abelson and Sussman phrase it in “Structure and Interpretation of Computer Programs,” “Programs must be written for people to read, and only incidentally for machines to execute.” But we rarely open a source code out of pure intellectual curiosity; rather, we open it with the intention of changing or extending some behavior. So our benchmarks for “clarity” and “readability” cannot be the same as those we use when reading for pleasure. We can enjoy books whose characters are complex and ambiguously motivated, but we can never safely change code whose intention is unclear.
It’s notable how rare the things are that are universally agreed to make reasoning difficult: the unstructured goto, inconsistent formatting, and deeply nested conditionals. Other than that, “It depends” is the order of the day. I tend to dislike shared state, but stateful objects are the building blocks of the vast majority of enterprise code. I see no problem in creating variables and functions near where they are used, but I have many friends who think that consistency of structure is important in functions, modules and programs.
(Related: The best coding book of the decade, so far)
Clarity is in the eye of the beholder, and we tend to think our personal prejudices are universal. I was once at a workshop for programming language designers. The speaker’s language used Smalltalk/Objective-C message syntax (‘Foo:WithBar:AndBat:’) because “It’s highly readable.” Someone raised their hand and said, “For the record, I’ve always found that style very hard to read.” And even among this very experienced crowd, the reaction was simply to shrug: Sure, this affects every line of code written the language, but it’s not worth arguing about. (To be fair, it’s also a choice that could hardly be revisited mid-presentation!)
Once we’ve internalized a concept, we tend to forget our early struggles with them. Whenever I read an introduction to recursion, I know I’ll see some variation of “recursive functions are easier to read.” No, they aren’t, at least not the first hundred. Once we no longer need a crutch, we tend to deny ever needing to use it. I wouldn’t choose Basic for a project, but it’s been taught successfully for 50 years. Similarly, it’s hip in certain circles to trash object-orientation, ignoring the fact that OOP dramatically expanded the scope of programming projects that could be reasonably achieved by most teams.
It is tempting to equate the “learnability” of a language or technique with its clarity, but that’s going too far. For instance, using matrix transforms to manipulate geometry takes some time to learn. But once you get it, a matrix is, I think, the clearest representation of a transform.
I have been working on a tide-prediction complication (that’s a widget for you non-horologists) for the Apple Watch, a task that involves calculations with literally dozens of cyclical components and corrections, with constants transcribed carefully from a textbook. The first function I tackled I simply typed in the constants and variables, connecting them with plus signs. And, of course, I got the wrong answer (I’d probably have a heart attack if I ever typed in one of these functions without a mistake in the constants or operations).
I couldn’t immediately see my mistake, so I reworked the code: I created one list of constants and another list of operations. Then I zipped them together into tuples, applied multiplication to all of them, and summed the results. For developers familiar with map and reduce functions, that might be clear. For others, maybe not. (As I’d hoped, the anomalously large component resulting from my transcription error stood out and was easily corrected.)
As I discussed in my “Programming Interview Problems” column, the best way to clarify things is to talk with the other person before coding. Talking with our future maintainers (or our future selves) is the point of comments, but as was discussed earlier, as humans we tend to be terrible at recognizing our own prejudices and internalized understandings. I like comments that clarify how the source relates to some domain concern (“Lunar phase calc per Astronomical Algorithms, 2nd Ed., Meeus, 1998, Chap 49”), but comments that explain techniques are often the sign of a programmer who gave up too early and could not make their own code clear even to themselves.
The challenge of code clarity is most acute in the hybrid languages that dominate mainstream programming. Single-paradigm “opinionated” languages embrace C.A.R. Hoare’s admonition that “software tools, including the compiler, have to be based on some theory of what it means to write a correct program.” What’s clear to Haskell programmers may not be clear to Smalltalk programmers, but within those communities, clarity is easier to define than it is within the mainstream communities. While it’s true that C++ has an inherent complexity, what really makes a new C++ codebase intimidating is an awareness of how many different approaches might be embodied in the source. Similarly, while JavaScript may be easy enough to be used by beginners, a large JavaScript codebase is likely to be influenced, in various places, by several paradigms: imperative, object-oriented and functional.
I don’t have an easy answer to this: I’m as prone as anyone to the mistakes I’ve described. I do know that the worst mistakes are made in mixed-paradigm languages at that steep part of the learning curve when things are beginning to click, but you are not yet tackling whole problems idiomatically in one or the other paradigm. Every mainstream language now supports at least some aspects of the functional programming paradigm, and much of the new code I’ve seen combines functional with other approaches.
In other words, I think a lot of us are going to regret the code we’re currently writing. That’s regrettable, but part of being a professional is recognizing that the best is the enemy of the good. Strive for clarity, but recognize that you are probably blind to your own worst errors. Keep your comments in sync with your code. And, as John Woods said, “Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”