Vertical code alignment

Tyler Neylon
7 min readAug 20, 2015

--

A coder named Terence Eden posted this example code:

int robert_age = 32;
int annalouise_age = 25;
int bob_age = 250;
int dorothy_age = 56;

along with this suggested change using some vertical alignment:

int robert_age     = 32;
int annalouise_age = 25;
int bob_age = 250;
int dorothy_age = 56;

I’ve previously thought of code essentially as a series of left-to-right lines, so this idea of alignment was new to me. I decided to try it out and report back on my experiences in the form of an informal vertical alignment style guide; that’s what this post is.

How much is too much?

What’s the right balance between left-to-right versus table-like readability?

Consider going one step further with the last example. We can right-justify the numbers like this:

int robert_age     =  32;
int annalouise_age = 25;
int bob_age = 250;
int dorothy_age = 56;

This has the advantage of keeping digits of the same significance in the same column. If we think of this as aligning along the decimal point, this method also works well with signed and floating-point decimal numbers.

Since we’re making things table-like, we could notice that the word “age” appears in all the variable names, and go so far as to right-justify those as well so that this similarity becomes visually clearer:

int     robert_age =  32;
int annalouise_age = 25;
int bob_age = 250;
int dorothy_age = 56;

At this point, though, if I were looking at an individual line, I may begin to feel discombobulated at all the non-standard whitespace. I think it’s possible to go overboard, and this style guide is all about which use cases improve code readability the most.

High-level goals

My philosophy on code style is based around the idea that making code readable is worth a significant amount of writing effort. In my experience, the hard part of coding is a dance between planning and understanding. You grok what behaviors you want and what interfaces you can use to achieve that. You plan based on that, and implement. Much of the remaining effort is spent adding features and debugging existing code.

In all these cases, the physical work of typing and formatting code is relatively easy. I see great code style as an investment in the easy part that pays dividends when a future coder has to reach for a precise and nuanced understanding of existing code.

When it comes to vertical alignment in particular, I see the benefit as taking a group of related tokens across multiple lines, and aligning them so that relationship becomes visually clear. This is the same idea as a well-formatted table. By bringing together similarities in a column, the reader can at once see what is being aligned, such as the equal signs in the above example, and mentally factor out the common elements: the left-hand side are newly declared variables, the right-hand side are constants with similar semantics.

As a more detailed rule of thumb, I found that it’s nice to leave the start of most lines left-aligned, and to take more liberty with the right side of lines. Typically, the first couple of tokens of a line indicate a key noun or verb about what’s happening; left-alignment clarifies these key starting tokens as well as the nesting level of the line. Things to the right are often more flexible. To to state things roughly, stuff on the left stays put while stuff on the right can be moved around with less readability cost.

We’re ready to dive into the list of specific recommendations. I’m focusing on C, but the general ideas can work in other languages as well.

1. Left-align related end-of-line comments.

Some function calls can be hard to understand because a typical call won’t clarify what the parameter values mean. Here’s a perfectly valid call to CreateWindowEx in the win32 api:

HWND hwnd = CreateWindowEx(0, CLASS_NAME, L"app",
WS_OVERLAPPEDWINDOW, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0,
hInstance, 0);

What do all those zeros do?

If there’s only a single ambiguous parameter, I prefer to add a one-off comment to clarify that single parameter. Otherwise I like to comment all of them so the formatting is consistent and everything is clear. This is a great opportunity to left-align those comments. Here’s an example of clarified parameters to an OpenGL function which specifies how the GPU will extract some vertex attribute data from a data buffer:

glVertexAttribPointer(v_position,    // attrib index
3, // num coords
GL_FLOAT, // coord type
GL_FALSE, // gpu should normalize
0, // stride
(void *)(0)); // offset

A coder with just a little knowledge of the OpenGL api can easily understand what each parameter means.

2. Decimal-point-align related numbers in a multiline initialization.

This is a generalization of right-aligning integer values as mentioned in first example. This has the advantage of visually clarifying how numbers relate to each other in magnitude, and keeps the code looking uncluttered.

Here’s an easy example where standard spacing would not align the digits or the commas:

GLfloat vertices[] = {
-1, -1, -1,
-1, 1, -1,
1, 1, -1,
1, -1, -1
};

3. Far-right-align line continuation marks in macro definitions.

Multiline macros in C require a trailing backslash before the newline. Naive spacing would put each trailing backslash in its own column, which adds visual clutter. The code is much easier to read if those backslashes are not only aligned, but pushed far from the code.

In this example, note how the far-right backslashes stay out of the way:

#define pixel_at(origin, x_off, y_off, chan)                     \
(origin->pixels + \
(origin->y_min + y_off) * (4 * origin->x_size) + \
(origin->x_min + x_off) * (4) + chan)

The details of this macros are beside the point, but for the curious: this macro accepts an originstruct with a buffer pointing to raw pixel values and an (x, y) offset into that buffer; it translates this into a pointer to that particular pixel’s data. The chan parameter is the RGBA channel number, 0–3.

4. Align related parameters to sequential function calls.

If there are several calls in a row to the same function, these calls can often be clarified by aligning related parameters. For example:

add_label( small_font, "Dinah",   left_align, x1, y1);
add_label( big_font, "Ivy", right_align, x2, y2);
add_label(medium_font, "Moira", center_align, x3, y3);

In this case, I’ve alternated between right- and left-alignment based on context. I’ve tried to maximize alignment of similar substrings, defaulting to left-alignment otherwise.

Some coders would suggest that repeated function calls like this may benefit from an application of the don’t-repeat-yourself — DRY — principle:

// --- A code alternative that I don't like. ---
// ... several lines of label_THING initialization code ...
for (int i = 0; i < 3; ++i) {
add_label(label_font[i], label_str[i], label_alignment[i],
label_x[i], label_y[i]);
}

I give this code credit for keeping the abstract meaning of the parameters clear. However, I think the clarity cost of setting up the label_THING arrays and the for loop outweight the benefit of having a single add_label line. There is a point where too many add_label calls in a row would be better as a for loop, but I think blind adherence to the DRY principle can hurt readability, especially when your code style can work well with a little repetition.

5. Left-align hanging indents in comments.

My code tends to have a fair number of multiline comments. Some of these comments, such as TODO items, are either temporary or asides compared to the more permanent, focused comments around them. In those cases, using a hanging indent provides a cue to see those comment lines as grouped and separate.

An example:

// TODO  This code has been known to make mistakes from time to
// time. I recommend adding unit tests with a new mock flux
// capacitor that simulates realistic failure conditions
// below 88 mph.

More experimental ideas

Those five rules are the cases that all felt like obvious wins to me. Even for those, it took me some time to get used to any kind of vertical alignment, simply because I was so familiar with primarily left-to-write code.

I did consider several other rules that may be interesting but didn’t make the cut as consistent wins:

  • Aligning binary operators: This one, the example that started it all, sometimes works and sometimes doesn’t. It’s so context-dependent that I don’t think it fits into a style guide. I occasionally use this rule if the resulting code feels nicer.
  • Aligning variable names on sequential lines: By which I mean to notice when the same or almost-the-same variable name is almost aligned already. Why not fully align it? I found this to be a weird rule in practice, however.
  • Aligning left parens of sequential function calls: This one might make sense if you have many similar calls in a row. For example, my game code often has a number of OpenGL calls on sequential lines, and the initial parameters often mean the same thing. It feels a bit arbitrary to make this a general rule, though. It sometimes felt and looked strange when I tried it.

Try it out

Try on these style tips and see which ones you like. It is extra work, but work that adds great value for the future coders — including a future you — that will read what you’ve written. Beyond readability, visually clear code is a sheer delight to work with.

--

--

Tyler Neylon

Founder of Unbox Research. Machine learning engineer. Previously at Primer, Medium, Google.