In programming, as in probably all creative fields, we speak of certain practitioners having good taste while others—sadly—lack it. Obviously, having good taste is generally better than the alternative, but opinions vary on how important it is.
On the one hand, given that code—unlike say a painting or a piece of fiction—has a functional purpose, some folks take the point of view that the only thing that really matters is that a program works as desired; matters of taste may be fun to argue about at lunch but, back at our desks, they don’t really matter.
On the other hand, anyone who’s maintained code knows that certain matters of taste are at least correlated with the code being correct. So we should care about taste because it is a leading indicator of quality—programmers with good taste are more likely to write correct code because they are obviously paying more attention to all the details of their code. But taste, in this view, is still not important for its own sake—it’s just a proxy for quality. It may not even matter exactly what they consider good taste; as long as they care enough to have opinions, that may be good enough.
Indeed, the Latin epigram quoted above, loosely translated as, “In matters of taste, there can be no disputes”, is usually taken to mean there’s no point in arguing about taste because it is arbitrary. And we have plenty of English phrases that capture this sentiment: “just a matter of taste” or “there’s no accounting for taste”. Yet we also talk about “good taste”. Is there such a thing?
As it turns out, the literal translation of the Latin phrase is, “About tastes, it must not to be discussed.” Which is, as my friend Oscar would say, some bullshit. Not only must we discuss taste but developing good taste is the primary task of all programmers.
Yes, you have to learn your programming language and your frameworks and pick up some domain knowledge and learn to put it all together into something that works, but that’s not enough. If that’s all you’ve learned, you’re still a crappy programmer. Which is not to say you aren’t an employable programmer. But you won’t be a good programmer until you’ve also developed good taste. And worse than that, if you haven’t developed good taste, your code will blight the lives of your co-workers, destroy good human feelings, and make the world a worse place.
There are a number of aspects of programming that ultimately come down to taste—areas where the computer doesn’t care but your fellow human beings will. A lot. You can develop your taste by paying attention to all these aspects and how they make you feel about the code you are working on.
The lowest level of detail to which you can apply your taste is code formatting—how you deploy a half dozen white-space and punctuation characters. Most developers have spent a ridiculous amount of time, at some point or another, arguing about details of code formatting. While the arguments are often annoying, you should still try to attune your own feelings toward what’s good formatting and what’s not because formatting elucidates the structure of the code. And consistent formatting does so more powerfully because you can rely on the formatting to consistently indicate the same thing about different pieces of code.
In my experience, good coders are very consistent and picky about code formatting. Reasonable people can adapt to different styles and follow the house style when there is one but whether following a house or personal style, they apply it very scrupulously.
The next level where taste matters is in naming things. Which is harder than you might think, a truth that has been captured in the quote that has spawned a whole genre of nerd jokes:
There are only two hard problems in computer science: cache invalidation and naming things.
(Sample jokes: “There are only two hard problems in computer science: cache invalidation, naming things, and off by one errors.” “Later we realized the 3 hardest problems in computer science were actually imposter syndrome, checking your privilege, and Dunning–Kruger.” and perhaps the final word: “There are two hard problems in computer science: we only have one joke and it’s not funny.”)
As programmers, we have to name things all the time with nothing to guide us but our sense of taste. You know you need to develop good taste because the advice you get, while sounding good is actually useless: “Pick descriptive names”, “Don’t use short names”, and “Don’t use overly long names”.
As with formatting, you can start developing your taste in naming by following and understanding local conventions. Pay attention and try to develop your own sense of what actually makes code clear. Beginner programmers often go overboard with too long names while experienced programmers may err in the other direction. However experienced programmers also tend to write better structured code in which less of the burden of comprehensibility falls on the names. They also understand that part of good taste is choosing the right length name: tasteful code uses longer and more descriptive names for the most important things and shorter names, maybe even only one letter, where the name itself is not important.
Continuing our upward progression from punctuation and naming, there’s also a larger vocabulary to programming: the kinds of language constructs we use. And again, exactly what vocabulary and grammar you use is a matter of taste. One coder’s concise, clear code is another’s obfuscated line noise and what one programmer considers lucid and easy to maintain is mindless boilerplate to another. As with writing prose, having a good vocabulary is almost certainly useful, as you always have the choice about how much of it to use. You may or may not choose to use certain vocabulary, such as the ternary operator in C-derived languages, point free style in Haskell, or implicits in Scala, but you should know about them and pay attention to the effect they have on code understandability.
Finally, we get to the larger question of how we organize our programs. Some programmers advocate a top-down organization (show, for instance, a function before the functions it calls) while others like bottom-up. Others (including Donald Knuth) advocate a kind of narrative order, telling the story of how you approached the problem.
But none of these totally determine the ordering of elements in your program, to say nothing of how you break up your problem into functions, classes, variables, macros, data, and whatever else.
As with all matters of taste, the computer doesn’t care as long as your code compiles. But given that there are N! ways you can organize a program with N elements, i.e. 3.6 million ways to organize a program with ten elements and two quintillion (i.e. two million billions) for twenty elements, it’s hard to imagine they’re all equally good.
And it’s even worse than that because programs are not just collections of elements to be ordered: they are trees or graphs that can be made of a few big chunks or lots of little chunks and coupled in all kinds of better and worse ways. Good taste will allow you to take even a seemingly gross problem and identify a coherent core that can be implemented in an extremely tasteful way and then used to solve the actual problem without the whole program turning into a total ball of mud.
Given these different dimensions in which we need to develop good taste, how, practically speaking, can we do it? There’s no short cut. You need to pay attention to how changes in these different dimensions make you feel and how they affect your ability to work with the code. Dissect a piece of code. Really understand it and then understand why it was put together the way it was.
Rewriting a piece of code is a great way to really understand it. Make sure the code continues to compute the same result and experiment with different structures, names, and formatting. How do different ways of expressing the same functionality differ. I usually find that after I’ve totally rewritten a piece of code to suit my own predilections I also understand the original code much better.
So this may seem like pretty straightforward stuff, especially if you’re an experienced programmer. But I’d argue that developing good taste matters for a far more important reason than just that it’s part of developing your craft. It’s part of your duty as one human being to all the other human beings who work on the code with you.
If you’ve been in the programming game for any time at all, you’ve probably heard of design patterns, first popularized by the book, Design Patterns, also known as the Gang of Four book after its four authors, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. You may also have strong feelings about the idea of design patterns ranging somewhere from “the greatest notion in software since the NAND gate” to “snake oil for mindless drones building enterprise junk.”
For the purposes of this essay, I’m not particularly concerned with traditional software design patterns. I want to go back to the origin of design patterns: the work of the architect Christopher Alexander.
Alexander wrote several books about what he called pattern languages in architecture. The first book, The Timeless Way of Building is a beautiful statement of his philosophy, a philosophy centered on the notion that there are deep forces that govern the relation between human beings and the physical spaces they inhabit. These forces are rooted in our evolutionary and cultural history and are derived from the ways people want to behave and interact with each other and how physical spaces support or thwart them. According to Alexander, we know, deep down, when we’re in a good physical place because it feels right. It has what Alexander calls, somewhat mystically, the Quality without a Name. Conversely, when we are in space without this quality, say a DMV waiting room, we also know it and can feel it sucking the joy out of our lives.
Alexandrian patterns each describe a way to organize physical space that meets some human needs but, almost always, leaves others unmet. Pattern languages are collections of patterns where the patterns support each other, filling in each other’s gaps. A rich pattern language gives us a coherent way of combining individual patterns to create spaces that have the Quality without a Name which arises when all the forces are balanced and human needs are met. Alexander describes the way of building that leads to the Quality without a Name in the opening paragraphs of The Timeless Way of Building:
There is one timeless way of building.
It is thousands of years old, and the same today as it has always been.
The great traditional buildings of the past, the villages and tents and temples in which man feels at home, have always been made by people who were very close to the center of this way. It is not possible to make great buildings, or great towns, beautiful places, places where you feel yourself, places you feel alive, except by following this way. And, as you will see, this way will lead anyone who looks for it to buildings which are themselves as ancient in their form, as the trees and hills, and as our faces are.
It is a process through which the order of a building or a town grows out directly from the inner nature of the people, and the animals, and plants, and matter which are in it.
It is a process which allows the life inside a person, or a family, or a town, to flourish, openly, in freedom, so vividly that it gives birth, of its own accord, to the natural order which is needed to sustain this life. (The Timeless Way of Building, p. 7)
Another interesting aspect to Alexander’s pattern languages is that they extend from the extremely local—how do you design one window in a room—to the scale of whole towns and cities.
Now we shall begin to see in detail how the rich and complex order of a town can grow from thousands of creative acts. For once we have a common pattern language in our town, we shall all have the power to make our streets and buildings live, through our most ordinary acts. The language, like a seed, is the genetic system which gives our millions of small acts the power to form a whole.
Within this process, every individual act of building is a process in which space gets differentiated. It is not a process of addition, in which preformed parts are combined to create a whole, but a process of unfolding, like the evolution of an embryo, in which the whole precedes the parts, and actually gives birth to them by splitting. (The Timeless Way of Building, p. xiii)
This is, of course, interesting to programmers because, like a city, a large software system also grows from thousands of creative acts. A large software system may be one of the few human creations that can rival a city in complexity, the number of scales involved, and the loosely coupled but ultimately interconnected way it is built.
But what about the human feelings? How can we write code that allows life to “flourish, openly, in freedom”? The Gang of Four patterns don’t have much to say about that. But I think we can draw a deeper parallel between software and architecture if we think of the code we work on as a kind of environment in which we live. The forces that govern the relation between human beings and code may not be as primal as those governing our relation to physical spaces but they are also derived from our evolutionary history, from our intellectual makeup, and from the limits on our ability to deal with complexity. Some code fits our mind and is easy to work with; other code is a nightmarish mess that makes us despair just to look at it.
Certainly we all know that some code is a joy to work on. And other code, not so much. And because we so often do not attend to these issues, a lot of code is the coding equivalent of the most inhumane DMV waiting room ever built.
If we can write the code that is a joy to work in, obviously our lives, and the lives of our co-workers, will be better, and that should be reason enough to develop good taste. The code will also be better because the things that make code feel good are the things that make us confident that we understand it and can change it to meet our needs. But we all have to work to recognize when code makes us feel alive and when it doesn’t and why.