Code formatting in C++ Part One
Code formatting or layout is one of the most religious areas of software development. With so many bloody battles fought and lost most developers learned long ago to avoid the matter altogether. They tend to do this by making consistency the only rule that matters. When in Rome do as the romans do. After all, everyone knows that it's all subjective and doesn't actually matter.
Or does it?
I'm going to present a couple of views that I hope will lead you looking at the matter once again. Some of them a little cliched. Some a little more novel.
First one of the cliches:
"code is written for humans to read - otherwise we'd all be writing assembler."
Of course high level languages are not only about human-readability. They are also about portability and economy of expression, among other things. Nonetheless human-readability is certainly a large part of it. So if we use high level languages in order to make our code more readable, should the layout of that code be irrelevant? Looked at from this perspective we may say, "it should be relevant, but code is also for other humans and its the differences in styles that create the problems - it all balances out to zero". In the context of software development is it worth fighting religious war over a zero-sum game?
What if there was a way to come to a consensus? Would there now be some advantage to looking at layout? If so, how much advantage? These are questions that I hope to answer shortly.
In the meantime, here's another cliche:
"code formatting is just personal preference. It has no intrinsic value"
Again - is it worth fighting over something that has no value? But what if it did?
Sometimes it can be useful to look at the extremes. Consider the following code:
int main(int argc,char*argv[]){printf("hello world\n")}
It's not too difficult spot the familiar "first c++ program" example, even if it's in a less familiar layout. But are you sure it's correct? It shouldn't take too long to spot the bug (and even if you don't the compiler will point it out to you), but now look at the same code expanded to a more canonical form:
int main(int argc, char* argv[])
{
printf("hello world\n")
}
I would bet that, for the majority of c/c++ developers spotting the missing ; in the second example was faster, and perhaps even more "automatic" than in the first. Even if you agree with that you may wonder, still, how much that matters. We're talking seconds, or perhaps milliseconds, difference in time. And that leads us to another cliche:
"The majority of development time is spent debugging code, rather than writing it"
This is often wheeled out when encouraging use of longer, more descriptive, identifier names, or the use of a more verbose, but explicit, way of doing something. Actually those areas are somewhat related to our topic, but here the point is: if actual coding time is a small part of overall development time, do a few milliseconds here, a second or two there, make any difference (and remember that was a fairly extreme case)?
These are all good questions. I'm now going to explore some possible answers for them. What I'm going to present here is my own view, based on my own experience and research, as well as the experience of a number of others that have tried my techniques. However the "number of others" is not statistically relevant enough (nor the conditions controlled) to call it a study, so this remains merely a theory. I encourage you to consider this material and let me know how you get on with it.
How the eyes and brain read
A few years ago I studied (or, more accurately started studying) speed reading. A significant portion of what you learn is understanding how the eye moves across the page, takes information in, and works with the brain to turn this into the experience we know as reading. By understanding these principles we can adjust our reading style to take advantage of their strengths (and play down their weaknesses). As I learnt more I wondered whether the same ideas could be applied to writing as well as reading. It seemed logical that if you write in a way that more closely matches how the eyes and brain read best then reading will be easier, and potentially faster. As I continued studying I found that this the is case. One commonplace example is newspaper and magazine text. This text is arranged in columns as columnar material is easier to digest by the eyes and brain when reading. From here I began to wonder if this knowledge should affect the way we write code.
Before we look at my conclusions I'll summarise the key speed reading insights I thought would be relevant:
Perhaps most important is that the eyes don't move across the page in a smooth, flowing, manner. Instead they jump in discrete fixations. At each fixation the eyes transmit a chunk of information to the brain. The size and content of each chunk varies and is one of the areas that a speed reader will exercise, attempting to take in more information at each fixation to avoid wasting too much "seek time". An average, untrained (in the speed reading sense), reader will take in about 2-4 words per fixation. For longer words this will decrease, and if the words are unfamiliar may drop below the one-word-per-fixation level.
Following from these is the insight that as well as several words along the same line being taken in in a single fixation, multiple lines may be taken in. But that's crazy talk! When you're reading you don't read the lines above and below, do you? (unless you're regressing, which is a bad habit that speed readers try to overcome as soon as possible). Well, speed readers will push the envelope here, but even for the rest of us we will still be taking in more information than we are consciously reading. The smooth, flowing, word-by-word reading experience that we perceive is an illusion. There is actually a disconnect between the information being captured by the eyes, transmitted to the brain, assembled and interpreted, and the perception of words flowing through our conscious minds.
We're in danger of getting too deep here. Let's stick with the knowledge that we can take several words horizontally and vertically in at each fixation. We can add to that another counter-intuitive nugget from the speed reading world. Good speed readers don't necessarily scan left-to-right then down the page (assuming a western reading context), but may scan in different directions according to different strategies - e.g., scanning down the page, then back to the top for the next column - even if they then have to mentally reassemble back into the original word order (I never got to this stage).
None of this addresses the question of whether this is even worth looking at. Are we trying to solve a problem that doesn't exist? We're constantly warned against premature optimisation but can that apply to our approach to code layout too?
Actually we have touched on one relevant principle already. I'll highlight it again here:
The smooth, flowing, word-by-word reading experience that we perceive is an illusion
Why is this relevant? Well for one it tells us that what we think is happening is not necessarily what is actually happening. A lot of processing is occurring before the material we are reading is even presented to us consciously. With practice we can get better at reading unoptimised material - so much so that we are unaware of it. That doesn't mean the processing isn't happening. Processing has a cost associated with it. It tires us - in particular it tires parts of the brain that we tend to use for other programming related activities too, such as solving certain types of problems - or extracting relevant details from a sea of information. It's almost like offloading some heavy number-crunching to a GPU then finding that your refresh rates are suffering.
Another factor is the concept of flow. When we are thinking about one problem and we are focused on it we are in a certain flow. The more we are then distracted by the mechanics of the task - consciously or subconsciously - the more it can knock us out of the the flow. Again, we may be so used to this that we hardly notice. Pay attention next time you are stuck in a problem and you find yourself losing track the more you have to hunt around through the code.
In summary, we need to get out of the trap of just looking at the numbers (a second here, a few milliseconds there). They may bear little relation to the real factors at play.
There's more we can learn from the world of speed reading and eye-brain coordination, but we now have some things to go on that can lead us to conclusions about code formatting.
Code fixation
First, the way eye fixations are able to take in multiple words both horizontally and vertically suggests that islands of related code should be readily assimilated in one or two fixation. Such islands can be created through logical grouping, and effective use of whitespace both to separate from other islands and for alignment purposes. Alignment touches on another eye-brain insight that I've not mentioned yet. Briefly, reading speeds can be enhanced by the use of a guide. This may be a moving object such as a finger or pencil), but just having a hard edge can be helpful too. However, too much of the same thing can make it harder to keep track of where we are, so if the hard edge is too long it loses some of its effectiveness. It follows, therefore, that small blocks with hard edges achieved through alignment should help the eye to more readily distinguish the associations between sections of code.
A lot of these are things we already do to some extent. Our use of code blocks and indentation help visually organise code to make it easier to take in - but can we take it further?
One good example is blocks of variable declarations. I'll be using C++ as an example here, as that has been my focus in this, but most of what I'm talking about applies to most, if not all, programming languages. I'd argued that you'll notice the difference in C++ more than most.
So, here is a typical stretch of variable declarations:
char* txt="hello";
int i = 7;
std::string txt2 = "world";
std::vector<std::string> v;
Already this is organised into a little island. If the declarations were scattered around we would lose that aspect. Whether that makes sense for your application is another matter. I'm not suggesting you lose the benefit of declaring variables closer to where they are used. What I really want to illustrate is what happens if you add a bit of whitespace for alignment purposes:
char* txt = "hello";
int i = 7;
std::string txt2 = "world";
std::vector<std::string> v;
Hopefully the use of monospaced font here was enough to preserve the alignment for you. Do let me know if it doesn't and I'll try a different way.
Now most of us have probably seen code like this. Maybe we already prefer such a style. But quite a number of developers seem to be against it, either actively (they really dislike it), or at least have concerns over the extra overhead of writing and maintaining in this style.
Well, if you're one of that number, please bear with me. There is more to get out of this yet. Also, as we'll see, I believe the big wins are actually in other areas that we're building up to.
So let's analyse the properties of this format for a moment. Firstly we have three columns here. The first column contains the types. The second the names and the third the initially assigned values, if any. One of the potential problems here is that, as each column is as wide as the longest field in that column, the more columns we have the more horizontal space we'll end up using. In C++, between templates and namespaces, this can get out of control quickly. As we'll come onto a bit more later, if you have to scroll to take in a line you'll undermine any efforts to make things more readable. Another problem is that it amplifies the objection over the writing and maintenance overhead of such a style. In this simple example we have seven points at which whitespace must be maintained for alignment purposes! A compromise is to use just two columns:
char* txt = "hello";
int i = 7;
std::string txt2 = "world";
std::vector<std::string> v;
This still allows the identifier names to be easily scanned, but at some loss of clarity with respect to the initialised values. Arguably the identifier names are the most important element here (from the perspective of fast lookup), and are the most obfuscated in the original example, so this is still a big improvement. How big? We're getting to that. At this point I just wanted to present some options and examples, with a little rationale. We'll build on these shortly.
For sake of arguments
Blocks of variable declarations are common, but spreading them out through a function is perhaps even more common - making the above examples less relevant. However there are a couple of places where we do still regularly see groups of related variable declarations in once place. One is in class definitions, where member variables are usually grouped in one place. Immediate readability of the names are probably even more important here as we tend to flip back to a class definition in a header file (in the case of C++) just for a moment to get the names.
But the other place, and where I'd like to focus further, is function parameter lists.
Parameter lists are obviously important. They define the interface between the function (or method - I'll use function here to mean both) and its caller. When looking at a function signature (usually at the prototype in a header file) a caller can see what arguments need to be passed in. However, when looking at the function body the parameter list shows you what has been past in. If these two statements sound obvious then why do we so often neglect how these things are presented - as if they are second class citizens in our code?
How often have you seen (or written) a function that takes some number of parameters, where the parameter list is all on one line and spans more than a typical screen-width? - sometimes several screen-widths! Often an attempt is made to rectify the situation by splitting the list over one or more lines, sometimes even one parameter per line (but by no means always). Even then the tendency is to place the first parameter on the same line as the function name, then try to line up the subsequent parameters with the first. Something like this:
void SomeNamespace::SomeDescriptiveClassName::LongishMethodName( int firstParameter,
const std::string& secondParameter,
const std::vector& thirdParameter,
Widget fourthParameter );
Does this look familiar? Although this might look like an extreme example, I don't know about you but I see this sort of code all the time. And remember - this is where someone has made an attempt to split across lines and use alignment! Just today I saw an example on my current project of a function signature that was 239 characters wide - and that was by no means the worst case in the project.
And we haven't even added namespaces yet (except for std)!
I think you'll agree that this is a big readability issue. The issue is made worse by a lack of consistency which often accompanies such attempts. Sometimes multiple parameters are on one line, sometimes split across several. Sometimes aligned, sometimes not. Even if the developer is following other conventions consistently this one seems to slip through the cracks - probably because it is often not defined and it can be difficult to know exactly what to do in a consistent way.
I have a theory about this state of affairs. It's an important theory (even if it turns out not to hold in this case) because it touches on a principle of why people get so religious about code formatting in the first place. I'm going to expand on that a bit later, but for now the theory is this:
Developers can't find a consistent style because the most obvious consistent style seems somehow "wrong"
The underlying principle, which I'll come back to is:
Regardless of what is objectively good, what is familiar always wins out
So what is the one true way when it comes to formatting function signatures? Well, apart from "one true way" being an overstatement, I'm going to leave my specific recommendation for a subsequent article (how else could I get you to come back?)
End of scope
Before I finish for now I want to come back to the issue of how important this all is in the first place. We touched on three areas that I believe are worthy of consideration:
The overhead of reading code "unoptimised" for reading may be more significant than we realise due to the subconscious processes that are at play - not just in time, but in energy and focus.
Even relatively small interruptions to our state of flow can have a noticeable impact on our productivity. Sometimes it can even become a serious bottleneck - think "butterfly effect".
Having a consistent style may actually speed up code writing time, but optimising that style for the way our eye-brain connection works can lead to significant increases in code reading/ comprehension time.
In the next articles I will present my recommendation for function signature formatting and return once again to the question of how much difference it makes, including some anecdotal evidence.