The Art of Readable Code by D. Bowsell, T. Foucher

Right now I’m reading a book called “The Art of Readable Code”. It’s full of good thoughts and examples that I want to share. But I can’t fit everything into one post, so I decided to split the summary in 3 posts. This post covers chapters 1-4.

Chapter 1: Code Must Be Easy to Understand

TL;DR:

  • Compact is better than bulky;
  • But readable is always better than compact.

Code is more often read than written. The faster one understands unfamiliar code, the easier it is. It’s easy to find bugs in readable code and fix them without breaking the program.

Shorter code is not always better for understanding though:

// This is harder to understand:
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());

// ...than this:
bucket = FindBucket(key);
if (bucket != NULL) assert(!bucket->IsOccupied());

So strive for compactness is good, but strive for high readability is better.

Chapter 2: Choose Clear Names for Variables, Functions, and Classes

TL;DR:

  • Choose precise words;
  • Avoid general and abstract concepts;
  • Clarify meaning with suffixes and prefixes;
  • Do not make up a too long name.

For example, in the GetPage function, the word get is imprecise. It doesn’t explain whether the page is retrieved from the cache or the web. The names FetchPage or DownloadPage explain better what is happening.

In the example below, it is already clear from the body of the function that retval is the return value.

var euclidean_norm = function (v) {
  var retval = 0.0;
  for (var i = 0; i < v.length; i += 1)
    retval += v[i] * v[i]
  }

  return Math.sqrt(retval);
}

However, it is not clear what it contains, because of this it is easy to miss the error:

retval += v[i];      // You can forget what's inside and miss the error.
sum_squares += v[i]; // “The sum of the squares? But there are no squares here, there's a mistake.”

Instead of abstract names, use specific names that are tailored to the situation. Such names themselves explain what the variable or function is for. Concreteness helps to avoid ambiguity.

Prefixes and suffixes clarify the meaning. For example, measure the loading speed of a web page:

var start = (new Date()).getTime();
var elapsed = (new Date()).getTime() - start;
document.write("Loaded in: " + elapsed + " seconds");

If you are familiar with JS, you will have noticed the error—getTime returns the result in milliseconds. That’s why the code doesn’t work correctly. If you add the suffix _ms, the error will be noticeable:

var start_ms = (new Date()).getTime();
var elapsed_ms = (new Date()).getTime() - start_ms;
document.write("Loaded in: " + elapsed_ms + " seconds"); // “A-ha! Here are seconds, not milliseconds. Need to divide by 1000.”

The length of the name depends on the size of the scope. If the variable lies within a function of 3 lines, a short name is fine. But the larger the scope is, the more accurate the name must be. For example, days_since_last_update will be better than just days.

Abbreviations in names raise questions. You may not have enough knowledge to decipher the abbreviation. Also, the abbreviation is the easiest to misinterpret. So BEManager is a bad name, BackEndManager is better.

If a word doesn’t add to the meaning of the name, it’s better to throw it away. For example, convert_to_string can be replaced with to_string.

A rule of thumb for name selection: “If a new team member cannot understand the meaning of a function or variable by its name, it is a bad name.”

Chapter 3: Avoid Ambiguous Names

TL;DR:

  • The title should reflect the meaning;
  • Use prefixes and suffixes to add meaning;
  • Remove the unimportant.

Test names for misinterpretations. Ask yourself what else the name might imply. For example, the filter function is incomprehensible: it selects either values that fit the condition, or vice versa. But the names select or exclude immediately tell us what results we will get.

results = Database.all_objects.filter("year <= 2011")
// Are there records <= 2011 or >2011 in the results?

Use the prefixes max or min to precisely define the range limit. For inclusive ranges you can use the prefixes first and last.

CART_TOO_BIG_LIMIT = 10
if shopping_cart.num_items() >= CART_TOO_BIG_LIMIT:
  Error("Too many items in cart.")
// Is 10 okay? And 9?

MAX_ITEMS_IN_CART = 10
if shopping_cart.num_items() > MAX_ITEMS_IN_CART:
  Error("Too many items in cart.")
// Ah! Okay, not more than 10.

For boolean variables use the prefixes is, has, can, should, etc. Also relevant for functions that return a boolean value.

Chapter 4: Beautiful Code is Read Faster

TL;DR:

  • Use a consistent style to get the reader used to it;
  • Write similar code in a similar way;
  • Combine related pieces of code into blocks.

Get rid of inconsistencies. Use helper functions. For example, a function and a test for it:

string ExpandFullName(DatabaseConnection dc, string partial_name, string* error);

DatabaseConnection database_connection;
string error;
assert(ExpandFullName(database_connection, "Doug Adams", &error)
  == "Mr. Douglas Adams");
assert(error == "");
assert(ExpandFullName(database_connection, " Jake Brown ", &error)
  == "Mr. Jacob Brown III");
assert(error == "");
assert(ExpandFullName(database_connection, "No Such Guy", &error) == "");
assert(error == "no match found");
assert(ExpandFullName(database_connection, "John", &error) == "");
assert(error == "more than one result");

Two lines do not fit and jump to the next, the reader will get confused. This can be avoided by using an auxiliary function:

CheckFullName("Doug Adams", "Mr. Douglas Adams", "");
CheckFullName(" Jake Brown ", "Mr. Jake Brown III", "");
CheckFullName("No Such Guy", "", "no match found");
CheckFullName("John", "", "more than one result");

void CheckFullName(string partial_name,
                   string expected_full_name,
                   string expected_error) {
    string error;
    string full_name = ExpandFullName(database_connection, partial_name, &error);
    assert(error == expected_error);
    assert(full_name == expected_full_name);
}

When declaring variables, break them up into meaningful blocks:

// A mess:
class FrontendServer {
    public:
        FrontendServer();
        void ViewProfile(HttpRequest* request);
        void OpenDatabase(string location, string user);
        void SaveProfile(HttpRequest* request);
        string ExtractQueryParam(HttpRequest* request, string param);
        void ReplyOK(HttpRequest* request, string html);
        void ReplyNotFound(HttpRequest* request, string error);
        void CloseDatabase(string location);
        ~FrontendServer();
};

// Divided by blocks:
class FrontendServer {
    public:
        FrontendServer();
        ~FrontendServer();

        // Handlers
        void ViewProfile(HttpRequest* request);
        void SaveProfile(HttpRequest* request);

        // Request/Reply Utilities
        string ExtractQueryParam(HttpRequest* request, string param);
        void ReplyOK(HttpRequest* request, string html);
        void ReplyNotFound(HttpRequest* request, string error);

        // Database Helpers
        void OpenDatabase(string location, string user);
        void CloseDatabase(string location);
};

The code itself can also be broken up into “paragraphs”. If the function consists of several steps, highlight those steps in the code with blank lines between them.

What’s Next

In the next post we’ll talk about chapters 5-7. It will cover:

  • Writing (or not writing) comments;
  • Formatting terms;
  • Checking loops for readability.