The XVIII century French naturalist, Georges-Louis Leclerc, wrote in his Discours sur le style: “Writing well consists of thinking, feeling and expressing well, of clarity of mind, soul and taste… The style is the man himself.” Programmers write code in a rigid language that puts bounds on our expression, but on the flip side it makes our intent understandable to computers. However, there is still room left for a touch of individual flair between the lines, and after doing software development for a couple years, you get a subjective “feel” for what constitues well-written code (or so you tell yourself), if not exactly the ability to tell at a glance who wrote it - but such cases are also not unheard of. I’m going to discuss the importance of style, but also how I think enforcing an oppressive style can negatively influence your project.

Now, computers do not care about any kind of style. Some programming languages might actually use indentation as part of their syntax (Python, Fortran), but as long as the code accomplishes the desired task, it could be a convoluted mess, with no hint of structure whatsoever. Indeed, that is usually what happens after the program code is compiled into executable (binary) form, especially with modern, optimizing compilers. Your nicely written code will be broken down, rearranged, transformed beyond recognition, because that form will actually be the most efficient for the machine to execute. That is why reverse engineering is so hard and why automatic decompilation is not feasible outside of some limited cases.

What we call “style” is therefore any means of structuring the code (either logically of visually) that is not strictly required by the machine, but which we add anyway in the hope it will make it easier to understand for humans. And, as with any other human preference, the actual guidelines as to what is “easier”, are extremely subjective, and ultimately it is a waste of time to argue whether one convention is better than another. It will depend on who you ask, and people will come up with ridiculous “objective” justifications for their preference, then defend if zealously. So, if ever possible, avoid being drawn into these types of dicussions.

Some projects will not prescribe any style. I’ve worked on one where management required developers to submit their code in Word documents, so that managers would be able to open the code and “see” what the programmers were doing. Managers don’t have editors that can open souce code files installed on their computers, but they do have Word, so that was the way things were done. I’m not sure what they were expecting to gather from that code, but perhaps they could count the lines and determine which developers are the most profilic? In any case, that code had to be pasted back into a source file and fed into the compiler at some point, and nobody could be bothered to properly align it, and using an automated linter was beyond the realm of conceptualization for the people in the organization - nobody had the time, or cared. In the end, the code looked something like this:

 class GetInterger {
              int getInterger()
              {
doSomething_completelyUn_related(

      );
  if   (x  ==1) return 1;
   else  if  (x== 3)
{
   return 2;
      }
    else { return x; }

}
void setInterger(int value)       
  { x  =  value; }
      private: int x;
                              };

I really wish I was kidding. But, if nothing else, this example showcases why it’s hard to read and understand code with inconsistent formatting. I tried fixing some of it up as I was working on it, but there was so much code (probably because the managers were counting the lines), nobody else cared, and introducing changes into the source control or build system like running a linter was met with trepidation (“you will break the things!”), that I gave up. After fixing the indentation, making the bracing consistent and fixing the spelling mistakes, the code could look like this:

class GetInteger {
private: 
    int x;

public:
    int doSomethingAndGetInteger() {
        doSomethingCompletelyUnrelated();
        if (x == 1) {
            return 1;
        }
        else if (x == 3) {
            // I have no idea why, but I need to preserve the original behaviour
            // while cleaning up, so it's risky to change.
            return 2; 
        }
        else { 
            return x; 
        }
    }

    void setInteger(int value) {
        x = value;
    }
};

This is a lot of work, is error prone, nobody will thank you for it if it works, and you will get in trouble if you break things by mistake. So don’t bother and just move on, unless you have no choice. Ultimately, this code is completely useless, and depending on the exact circumstances, it could probably be replaced with a global integer value that they are wrapping in a class for no reason and then getting and setting just as it was a simple value:

int numberOfThings;

// here's how we use it
void somewhereElse() {
    doSomethingCompletelyUnrelated();
    // get value from the global variable
    int thingCount = numberOfThings;
    // process the special case
    if (thingCount == 3) {
        // is this really still necessary?
        thingCount = 2;
    }
    // do something with the value
    processThings(thingCount);
    // set the global value to something else,
    // presumably we have processed all the things
    numberOfThings = 0;
}

There, done. It may also be questionable, but at least it’s better than the original. Or is it? Again, depends on who you ask. To the people who have been working on this project for a long time, and getting their “Intergers” that way for years, you may just as well be the devil himself, a troublemaking outsider at the worst, or a clueless neophyte at best if you dare touch it. Style, the eternal subjective subject of fervent debate. Yaaawn.

So, we may agree that some degree of style is desirable. If everybody is required to follow a common set of rules, at least we will avoid having our code look like Swiss cheese, and hopefully that will make it easier to read, modify and maintain. Savvy teams will create their own style guide, or pick a well-known set of reasonable rules like the Google style guide for their programming language. It may be a little annoying, but modern tools like a good code editor or IDE help maintain proper indentation, some can even be programmed with a set of more complicated rules so you can make your bracing etc. compliant with a push of a button. You could even make the templates for the most commonly used editors available for download. Also, as previously mentioned, you could put a linter on the receiving end of your revision control and sanitize all code that’s coming in. Progress and profit for everyone!

Question is, can you overdo it? Can you make your style guide needlessly oppressive, have the rules be hard or impossible to automate, and make your code look like it’s been written in the 1970s? Well, sure you can! I realize all of this is of course subjective, but here are some of my favourite pet peeves. I discuss them from the point of view of C/C++, but so many languages borrow bits of C-like syntax that I imagine at least some of these to be applicable to most languages in use today. Here are a bunch of rule types that you might find in the style guide of a software project, that I think are particularly oppressive for no good reason, especially in modern times.

No modern language features

The rate at which more crap is being added into C++ after 2011 is just bonkers, and these days I just gave up on trying to follow, instead just picking and choosing things that look nice or useful. On the polar opposite, not much has been added to C since 2011. But in either case, if the style guide for a project forbids me to use any language feature that wasn’t around in 1989, that tells me that people who work on it (and who have enough decision power to enforce such policies) have not bothered to sharpen their saws ever since they learned to code when Back to the Future II came out.

Sometimes they will say “no C++” on a C-only project, and they usually will have some reasonable-sounding “reasons” why and the odd cautionary tale, that, when scratched, turn out to be a bunch of outdated baloney. Again, there might be legitimate reasons to use one language instead of another, but saying “weeeelll, actually I tried using a vector<T> back in 2001 and it’s just to slow for us here, heh” is just embarassing to hear.

Line length

int someFunction() {
    /* We really like
     * our lines to 
     * be short.
     */
    becauseWe(cannot);
    afford("monitors"
        "that display"
        "more than 30"
        "characters "
        "in one line");
    if (we_need_to) {
        goDeeper(this,
            "becomes"
            "even"
            "more"
            "difficu"
            "lt");
    }
}

When limited by a style guide, this will usually be 80 characters, because that was the typical width of a text terminal in the 70s or something, but sometimes it can go lower. This makes it hard to maintain longer strings, comments and more complicated conditions, especially at deeper indentation levels - you will need to break your lines, and rewrap them every time you make a change (which is why it’s best to apply the style as the last step before pushing the code to the repository). Usually the justification given (if any) is that Some People™ are still editing code in 80-char wide terminal windows, or Some Tools™ only support lines of limited length. Well, I should say those people need to catch up with the times, and those tools need to be updated or eliminated. Don’t make me bounce off an invisible line that isn’t there since circa 1990 while I’m trying to get my work done.

None or limited comments

My wife is a huge fan of Margaret Hamilton, and enjoys sharing bits of knowledge about the Apollo project with me, including the delightful “BURN BABY BURN” or “PLEASE (…)” comments that are scattered across its code. Theoretically, because comments are not meant for the machine, and there are hardly any syntax restrictions placed on them, it is the one place where one could really unleash their creative passion. But we are programmers, not novelists, so we only engage in prose writing when we have a good reason. Well okay, sometimes it’s just to vent, or have some fun, but the longer form is usually reserved for legitimate technical reasons:

// WHEEEE! <- short comment for questionable "fun"
wrapExternalLibrary() {
    result = externalLibrary->callAPI();
    /* 
     * There is a bug in "externalLibrary",
     * that only happens on a Monday, such that it will
     * return a result of 2 instead the correct 3.
     * Because we cannot modify externalLibrary to fix
     * the problem, and the vendor claims it is working
     * as expected, we need to apply a correction
     * before passing the result further.
     */
    if (today == MONDAY && result == 2) {
        result = 3;
    }
    return result;
}

Without the comment, the code above would be vexing, and somebody who joins the project later and needs to work on this might waste a week or more figuring out the reason why. So instead you “waste” a little bit of time in typing out your reasons, as a good deed to the next poor soul that visits here. In an ironic plot twist, that poor sould could be you in 5 years! Maybe the programmers in the 1970s were all geniuses who could memorize the entire code and the reason behind every line, but I’m a dimwit, the people who will take over the maintenance of this in a couple years will be clueless, we all need to know C, C++, Python, bash, sockets, threads, SQL, Docker, Kubernetes, git, gdb, Makefiles, not to mention the domain-specific knowledge for the actual thing you are working on and a bajilion other different things around the coding itself that weren’t around back in those days, so I’m going to take every bit of help I can get.

Now some people, including some high-profile and venerated programmers will tell you that comments are unnecessary, and instead you should write code that is “self-explanatory”. Well, good riddance. This is elitist gatekeeping from a person who has been working on the same project for 40 years, so sure they know it inside and out. It’s your fault for wanting/writing comments, you should have also joined your project 40 years ago! Sometimes they will say that it’s “impossible” to have good comments, because when inevitably the code changes, nobody is going to bother to change the comment, and sure, that’s possible, especially when the person doing the change is not in the habit of reading or writing comments. But I’m not going to let a hypothetical person’s lazyness and selfishness get in the way of doing my job properly, ya know?

I have personally never worked on a project that banned comments outright, but a lot of devs will produce none, which will turn into a de facto “rule” if they are the ones whom the rest look up to for guidance. Such projects usually have mysteriously high time and cost overruns, and while I certainly am not saying this is because of lack of comments alone, I think that it might be one of the symptoms of deeper problems with the organization’s culture, specifically effective communication, and the comments are obviously a form of that.

Double indentation of continuation lines

A problem appears when the condition in an if statement is too long and the line needs to be broken, because then the following code is indistinguishable from the continued condition just by looking at the indentation alone:

void someFunction() {
    if (someLongCondition 
        && forcesALineBreak) {
        thenThisConditionalCode();
        willBeIndentedAlongWithTheCondition();
    }

    // some people really hate the matching indentation
    // of the conditional code and the condition itself, 
    // so they will insist on 2x the amount of indentation
    // on the "continuation" part of the condition 
    // to make it stand out:
    if (someLongCondition 
            && forcesALineBreak) { // double indentation
        thenThisConditionalCode();
        willBeIndentedSeparately();
    }
}

This does not look unreasonable. In fact, the point is, none of these example rules are objectively nonsense. But I challenge you to find an automatic linter that lets you do this with a rule. You will need to take care of this manually everywhere, and may Donald Knuth help you if you mess up once. The whitespace police will descend upon you and make you regret the day you decided to pick up a keyboard and a copy of K&R.

Variables declared at beginning of block

int someFunction() {
    int all, variables, need, 
        to, be, declared, at, the, top;
        as, it, was, in_the, days, of, old;

    doSomeStuff();
    // [...] a lot of code, so you forget and can't see what the variables are

    // and when we actually need to use our variables later...
    all = 1; // I need to look it up above
    useValue(all);
}

This is a stupid throwback to 1989 and ANSI C. The compilers were only able to generate code manipulating the stack frame at the opening brace of the function, so all variables had to be declared in advance. This limitation was removed in C99. That is the year 1999, 24 years ago when I’m writing this. Now you can do this:

int someFunction() {
    do();
    some();
    stuff();

    // oh, I need a variable!
    int value = 1;
    useValue(value);
}

This puts the variables closer to the place where they are going to be used, so you do not need to jump around the function (especially if it’s long) to peek at the declaration, and also it will not be possible to use that variable (especially uninitialized, i.e. without being assigned a value) before it makes sense to - it will be a compile-time error. Hardly anyone uses a pre-C99 compiler these days, so this rule is just there to piss you off, or possibly because some senior dev gets irked when they see code that reminds them it’s not the 80s anymore, they now have a sizable paunch and that moustache is not doing it for the ladies as it used to.

Single return from a function

Back in the bad old days, when high level language (e.g. C) compilers did not exist or were just horrible and you had to write everything in assembly, it was easy to end up with something called spaghetti code. In assembly, you can write code like this (pseudocode):

start:
    do something
    jump_to end
middle:
    do something,else
    jump_to start
end:
    compare day, MONDAY
    if_equal_jump_to start
    jump_to middle

Essentially, assembly allows you to jump to an arbitrary location in the flow of the code at any time, and some code would really abuse this capability, to the detriment of readability. It is sometimes next to impossible to follow the entangled spaghetti strands and figure out what the code is doing when reading a flow with multiple jumps back and forth that cross each other. It was a real and legitimate problem.

In an effort to contain this madness, people came up with the idea of structured programming, a concept which is sometimes belittled as just “the idea of programming with subroutines and/or structure data types”, but there is actually a surprisingly deep theoretical framework behind it. Basically the goal was to give up some freedom in exchange for more order, kind of like functional programming tried to accomplish, albeit by a different route. Soon, languages supporting the new paradigm were appearing (including C) but as far as I understand, almost none of them were purely structured (again, similar to functional lanugages). As usual, the theory dictates rules that are a little to draconian for the real world, so practical implementations need to cheat a little bit in order to be able to get stuff done. Among others, structured programming bans arbitrary jumps (instead prescribing use of control structures such as if-then-else) and multiple exit points from a subroutine.

Now C is not a purely structured language, most obviously because it contains the goto statement which is essentially an unstructured jump, and hence you can still write spaghetti code in C. It also allows for multiple exit points, which is nice because it lets me sometimes handle an error situation easily:

void someFunction() {
    if (someAction() == FAILED)
        return; // early exit in case of an error

    doSomethingElse();

    if (otherAction() == ALSO_FAILED)
        return; // another early exit

    makeABarrelRoll();
    // normal exit
}

Unfortunately, some people are still traumatized from the spaghetti days, and will absolutely insist that a function may only have one return, no matter how many hoops you need to jump through to reach it:

void someFunction() {
    if (someAction() == FAILED)
        goto out; // not an exit point

    doSomethingElse();

    if (otherAction() == ALSO_FAILED)
        goto out; // also not an exit point

    makeABarrelRoll();

out:
    return; // single exit point
}

It is pretty ironic that in order to banish a structured programming violation, you are asked to use a goto, which is an even worse violation of its tenets. Also, this hides the intent of the code (especially if the label has a non-obvious name), introduces non-linear execution patterns for no good reason, and is hard to follow in more complicated functions. Also, it makes it awkward and easy to make mistakes at the end of the function if there is an error label, and a regular out label, extra care needs to be taken not to fall into one or the other unexpectedly.

It is worth nothing that goto-s still have some legitimate uses, particularly for jumping out of deeply nested control structures to handle an error.

int someFunction() {
    for (something) {
        while (somethingElse) {
            switch (whatever) {
            case error:
                /* if we are deep in some nested hierarchy, 
                 * it might make sense to just use goto 
                 * to jump out if needed */
                goto handle_error;
            }
        }
    }

    return OK;

handle_error:
    doSomeErrorHandling();
    return ERROR;
}

Disallowed combined assign and check

In C, an assignment such as a = b is an expression, so in addition to assigning the value of b to a, it also itself has a value (b) that can be checked in the same breath as doing the assignment:

    memory mem;
    // assign the result of allocating 100 bytes to "mem", 
    // and check if it was successful at the same time
    if ((mem = allocate(100)) == ERROR) {
        handle_allocation_error();
    }

    // some people hate the brevity, and will insist on this:
    memory mem;
    // additional slap in the face, this could have at least been combined
    // with the line above ("memory mem = allocate(100)")
    mem = allocate(100); 
    if (mem == ERROR) {
        handle_allocation_error();
    }

C is already such a ridiculously verbose language, that allocating and filling a string takes a hundred lines (don’t quote me on that), so breaking simple idioms like this into even more lines just makes for more reading.

So what is your point, exactly?

Now, this list is not exhaustive, and as I already mentioned, each item on it might make a good, useful rule in some specific context. However, when your style guide is overpresctiptive with a hundred rules concerning whitespace conventions, that does not inspire confidence in the technical leadership in the project. When you submit your code up for review, and you get no useful feedback about actual problems, but instead people seem to take pleasure in pointing out spots where you missed a space, then it damages the trust you have in your peers. When you ask senior people on the project for reasons why a particular rule is in place, and they have no clue (but you still need to follow it), or “it was always like this”, or they tell you some made up “technical” reason that might have been true 20 years ago, then your confidence in the project, the team, and the organization is degraded, you are demoralized, and either become a resentful mean spirit, or grow a thick skin and stop caring - I mean, if the company’s #1 priority is really to have their spaces neatly aligned, then who am I to argue?

And here is where I will try to finally make a point of this rant: having an outdated, draconian style guide is a real detriment to a project’s health. We need to enforce a degree of style, but there should be as few rules as necessary to achieve the desired effect, all of them should be backed up (in writing) by legitimate technical reasons, and perhaps most importantly, they should be periodically reviewed to make sure we did not get stuck in the stone age and failed to notice.

As with most things, there is a delicate balance to be struck here - which I realize is easier said than done. We need to make sure our code is readable, and avoid a free-for-all in which a complete lack of rules (because we’re Agile Innovators™!) destroys comprehensibility and cohesion. But we also need to minimize developer frustration and make sure the style guide does not become a handy prop which a self-appointed style guardian can use to whack people on the head with, in order to make themselves look busy and important.

I think a good style guide should be tailored to a particular project (don’t just blindly adopt Google’s) and gravitate more towards either technical guidelines that are lessons learned the hard way in battle (“don’t allocate dynamic memory inside the inner loop”), or guidelines that are related how the code is to be logically structured (“don’t repeat code”, “all return values should be checked for errors except (…)”). A bad style guide will contain no useful information and a lot of rules about formatting (“put 2 tabs before every line”, “lines no longer than 82 chars”).

But that is just my opinion, and everybody’s got one.