Skip to content

Instantly share code, notes, and snippets.

@jesseschalken
Last active November 14, 2024 15:46
Show Gist options
  • Save jesseschalken/0f47a2b5a738ced9c845 to your computer and use it in GitHub Desktop.
Save jesseschalken/0f47a2b5a738ced9c845 to your computer and use it in GitHub Desktop.
Code style

My preferred code style is 2-space K&R. This is intended to provide a justification for this style.

Why K&R?

K&R style has the following properties:

  1. Provides symmetric size (in terms of screen space consumed) between the opening and closing syntax of a clode block.
  2. Forces no empty or meaningless lines, thereby avoiding artificial distance between related things that should be together.
  3. Consumes the minimum vertical space while keeping the opening and closing syntax of a block on separate lines from the content.

K&R style geometry

Symmetric block open and block close

Symmetry in size is important because it provides a visual cue that two things are similar in effect/importance.

For example, in written text, lines of a paragraph are the same space apart. The space between paragraphs is larger, but also the same between each. The space between subsections, sections and chapters are progressively larger still. The space between things indicates their relatedness, the size of the headings indicate how large of a topic change they introduce. All line spaces are the same, all paragraph breaks are the same size, all chapter headings sizes are the same, etc.

Two subsequent statements in a program are related by one happening after the other, i.e. one directly inheriting the state of the previous. Within a control structure (if, for etc), the first statement inside the body and the statement before the structure are less related than two sequential statements, and so the introducing line of the control structure increases the space between them, and specifies the different nature of their relationship (conditional, loop etc).

The finishing line of a control structure has the opposite, but equivalent role. The opener transitions fully into a block and the closer transitions fully out. Their real effect on the meaning of the code above and below each of them is of the same magnitude but in opposite directions.

In K&R, the space the opener and closer of a control structure occupy on the screen is equivalent (one line) to reflect their equivalent importance in terms of effect on the meaning of the code above and below each.

Comparison

Here is a comparison of brace styles found on Wikipedia:

Name Example Open/close
equal height
Open/close
equal indent
Open/close
don't share
lines with
content
K&R K&R ✔️ ✔️ ✔️
Allman Allman ✔️ ✔️
GNU GNU ✔️
Whitesmiths Whitesmits ✔️
Horstmann Horstmann ✔️
Pico Pico
Ratliff Ratliff ✔️ ✔️
Lisp Lisp ✔️

Consistency with other syntax

Notice the consistency that results between a stand-alone C (or a C-style language) block,

{
  foo();
  bar();
}

an if block in C (or a C-style language) (K&R style),

if (x == y) {
  foo();
  bar();
}

a PHP block-style if block,

if (x == y):
  foo();
  bar();
endif;

a Ruby if block,

if x == y
  foo()
  bar()
end

an if block in the Fish shell,

if x == y
  foo
  bar
end

some HTML/XML,

<ul style="...">
  <li>foo</li>
  <li>bar</li>
</ul>

and an array, object/struct or dictionary literal (JSON, JavaScript, Python, PHP, Ruby etc).

var things = [
  'foo',
  'bar',
];
var things = {
  foo: 'foo',
  bar: 'bar',
};

All of these follow a simple pattern:

(introducer)
  (entries)
(finisher)

One line to open, one line to close.

The visual relationship between the opener of the block and it's contents is also the same as in languages which use the off-side rule, such as CoffeeScript, YAML, Python, Haskell and SASS:

if foo():
  bar()
  baz()

boo()

Except for the lack of a finishing line, since a dedicated closer is not needed (the block is closed by returning to the previous indent).

It is also the same as a control structure lacking braces, if permitted by your code style:

if (foo())
  bar();

if (foo()) {
  bar();
}

This means that the presence or absence of braces has a less dramatic effect on the code's layout. Compare these code samples, for example:

function typeName(obj) {
  if (obj.isFoo())
    return 'foo';
  else if (obj.isBar())
    return 'bar';
  else if (obj.isBoo()) {
    log_notice('Cannot get name of a boo');
    return null;
  } else
    throw new Error();
}
function typeName(obj)
{
  if (obj.isFoo())
    return 'foo';
  else if (obj.isBar())
    return 'bar';
  else if (obj.isBoo())
  {
    log_notice('Cannot get name of a boo');
    return null;
  }
  else
    throw new Error();
}

In the Allman case, the simple fact that the obj.isBoo() branch needs two statements instead of one has caused the number of lines for the body of that branch to be four times taller than the others.

Meaningless lines

Code in K&R style has no artificial meaningless lines enforced by the style. Each line tells the reader something they wouldn't otherwise know, and they can therefore progress from line to line next gathering new information at each. Blank lines are not bad, since they are useful to group related lines together and seperate unrelated lines, and so hitting a blank line is a signpost, like a paragraph or section break in a book, that the old topic ends and a new topic starts, but whether a blank line is applicable in any given context is entirely dependant on that context, and so cannot be decided by the coding style. Under K&R, every blank line has a purpose, decided by the programmer, based on the context, to convey some informaiton about the relatedless of each line.

Here is an example of the consequence of meaningless lines being enforced by the Allman style:

$foos = array();
foreach (getFoos() as $foo)
{
  $foos[] = transformFoo($foo);
}

Here, a blank line separates the initialisation of $foos and the loop header from the loop body. The blank line implies that they are unrelated, when in fact they are. The whole set of code belongs to a single topic of "array of transformed $foos", but the Allman style has artificially inserted a topic break in the middle of this.

Under K&R, the whole code block can be properly treated visually as a single topic:

$foos = array();
foreach (getFoos() as $foo) {
  $foos[] = transformFoo($foo);
}

Vertical space

K&R style minimises the amount of vertical space which the code consumes, while maintaining that the syntax of the control structure itself does not share lines with its contents.

Ensuring a control structure doesn't share lines with its content is important. Lines are the "boxes" or categories in which related syntax goes. In the case of a code block, all the syntax related to a given statement goes on the same line. This not only helps visual comprehension, but means line-wise operations are meaningful (triple-click select line, delete line, cut line, duplicate line, source code diff etc). With the Lisp-style bracing, for example, the last brace on the same line as the last statement means that the "delete line" operation cannot be used to delete the last statement, and adding a statement to the end of the block creates a "-1 lines +2 lines" diff instead of a "+1 lines" diff.

Ensuring the code minimises the amount of vertical space is important for information density, i.e. the total amount of information that is readily available, per unit of screen space. Minimising vertical space (lines) used helps the reader to get a "birds eye" view of the code without using a smaller font and without removing the indents and purposeful blank lines that give the code a visual structure. The more code that can be fit on screen without compromising its visual structure, the more readily the code can be read.

Consider you have two statements:

foo();
bar();

and you want to wrap them in a if block wrapped by a for loop. The K&R result is:

for (...; ...; ...) {
  if (...) {
    foo();
    bar();
  }
}

While the Allman result is:

for (...; ...; ...)
{
  if (...)
  {
    foo();
    bar();
  }
}

Under K&R, the overhead in consumed lines for each control structure is 2. In Allman the overhead is 3, i.e. a 50% higher cost in vertical space.

Survey

This is a survey of well known software projects and companies and their chosen brace and indent style.

Note that some codebases use K&R style for control structures but Allman style for classes and functions. These have been grouped under "K&R".

Company/Project Brace Style Indent Type Indent Size Reference
Google K&R spaces 2 Google C++ style guide, Google Java style guide
V8 (JavaScript engine) (Google) K&R spaces 2 code
HHVM (Facebook) K&R spaces 2 HHVM guidelines, code
Proxygen (Facebook) K&R spaces 2 code
Phabricator (Facebook) K&R spaces 2 Phabricator coding standards
.NET CLR (Microsoft) Allman spaces 4 code
C# Guidelines (Microsoft) Allman spaces 4 link
TypeScript (Microsoft) K&R spaces 4 code
Visual Studio Code (Microsoft) K&R tabs ? code
Intel TBB K&R spaces 4 code
Sun Java JRE/JDK (Oracle) K&R spaces 2 code
Linux Kernel K&R tabs 8 Coding Style
IntelliJ (JetBrains) K&R spaces 2 code
Nginx K&R spaces 4 code
LLVM (Apple/Google) K&R spaces 2 code
JavaScriptCore (Apple) K&R spaces 4 code
WebCore (Apple) K&R spaces 4 code
SystemD (RedHat) K&R spaces 8 code
KDE K&R spaces 4 code
GNOME GNU, K&R mixed 2, 4 C code, JS code, Vala code
RequireJS K&R spaces 4 code
Apache K&R spaces 4 code
Firefox K&R spaces 2 code
Chromium (Google) K&R spaces 2 code
LibreOffice Allman spaces 4 code
PHP K&R tabs ? code, code 2
Vim Allman mixed 4 code, code 2
Git K&R tabs ? CodingGuidelines, code

Why does what other projects do matter? Only for familiarity. Switching between code written in different styles can be jarring. With most code having been written in K&R, the code you write will feel more familiar to others and others' code will feel more familiar to you by sharing the style.

Why spaces?

  1. The good thing about tabs is that each person can configure their tools to render tabs using their preferred size.
  2. The bad thing about tabs is that each person must configure their tools to render tabs using their preferred size.

The requirement of #2, that all software displaying the code be configured to have the correct tab size, is at best inconvenient, at worst impossible. The problem is there is a large and diverse range of software that will be involved in displaying your code, including:

  • Debuggers (gdb, lldb, hphpd, Firefox/Chrome JavaScript debugger...)
  • Error reporting systems (Bugsnag, Rollbar, FailWhale, Whoops!...)
  • Source code browsers (GitHub, Bitbucket, Upsource...)
  • Code review tools (GitHub, Bitbucket, Upsource, Crucible...)
  • Diff tools (git diff, GitHub app, Meld, TortoiseGit, KDiff...)
  • Editors (including both IDEs and simple text editors (Vim, Gedit...) used to quicktly view files and make small changes)

Not to mention code samples that are put in emails, chat messages, code review/bug tracker issues/comments, blog posts and presentation slides.

Each tool will have its own default display size for a tab (usually 8) and each tool may or may not let you change it. GitHub, for example, renders tabs with 8 spaces, which can only temporarily be changed by adding ?ts=... to the URL and reloading the page. Consequently, using tabs, you are destined to find yourself reading your code with the wrong indent size (usually 8), whether you like it or not, and depending on the tool, you may not be able to do anything about it.

It is simply not possible to impose the requirement of user-configurable tab sizes on all the software which happens to render your code.

Spaces impose no such requirement. By embedding the correct indent size directly in the code, the code is rendered correctly everywhere, even if you cut and paste it into an email.

Why 2 spaces?

Characters in most monospace fonts are approximately twice as high as they are wide, and as such a 2 space indent is approximately the same width as the height of a blank line. If a blank line is the correct size for grouping and spacing things vertically, then it follows that 2 spaces should be appriximately the correct size for groping and spacing things horizontally.

This also means that a line that follows the lead of nested control structures and the line of closing braces are both at a 45° angle.

function foo() {
  if (bar) {
    while (baz) {
      if (zap) {
        return zoo;
        // ↖ this line has a 45° angle to "function foo() {"
        // ↙ line of closing braces is also at a 45° angle
      }
    }
  }
}

2 spaces is also the minimum possible indent size that is larger than the individual spaces that are used to separate words within a line.

2 spaces conserves more horizontal space than the alternative 4 or 8 spaces. This is important because just as there is a cost in vertical space, there is a cost in horizontal space. The higher the indent size, the less horizontal space the body of a control structure has between the indent on the left and the margin on the right. Deeply nested control structures (which are sometimes appropriate) become more feasible the smaller the indent.

From Steve McConnell's Code Complete Second Edition chapter on Layout and Style:

Subjects scored 20 to 30 percent higher on a test of comprehension when programs had a two-to-four-spaces indentation scheme than they did when programs had no indentation at all. The same study found that it was important to neither under-emphasize nor over emphasize a program’s logical structure. The lowest comprehension scores were achieved on programs that were not indented at all. The second lowest were achieved on programs that used six-space indentation. The study concluded that two-to-four-space indentation was optimal. Interestingly, many subjects in the experiment felt that the six-space indentation was easier to use than the smaller indentations, even though their scores were lower. That’s probably because six space indentation looks pleasing. But regardless of how pretty it looks, six-space indentation turns out to be less readable. This is an example of a collision be tween aesthetic appeal and readability.

@RMHaley
Copy link

RMHaley commented May 2, 2023

This is golden. K&R is the first and only!

@yanpeng
Copy link

yanpeng commented Oct 25, 2023

Others can have reasons why use Allman brace style. The projects you selected are mostly using K&R, you didn't select more allman style projects. Some famouse projects are using allman style, such as Microsoft Office, CRYENGINE, Unreal Engine, PostgreSQL, Vim.

@harsyd
Copy link

harsyd commented Mar 9, 2024

You have of course right to your opinion but I think it is based on false arguments. "Open/Close equal height" for instance is based on your interpretation that the line with the conditional statement is included in the opening line. I prefer to think that conditional statement is not part of opening line which in your chart would make Allman the "winner". In most C like languages it is perfectly normal to have a { } block without if/while statement,

Secondly, your example snippet with "obj.isBoo()" goes against a rule accepted by most K&R adopters; Never have a single line if/while block without curly brackets. This rule is of course adopted by many Allman style coders too, but I'll show you why it is even more important for K&R.

if (!obj.copyUserProperties(user) || obj.doSomethingElse()) // try or fail
return 1001;

if (!obj.copyUserProperties(user) || obj.doSomethingElse()) { // try or fail
return 1001;

You see that the only difference is that tiny, almost invisible, "{" character and source of countless bugs.

Now, while this is a good practice in Allman style too it is far less error prone.

if (!obj.copyUserProperties(user) || obj.doSomethingElse()) // try or fail
return 1001;

if (!obj.copyUserProperties(user) || obj.doSomethingElse()) // try or fail
{
return 1001;

In Allman style, whenever you see a statement directly after a conditional it is immediately clear that it MUST be single line block because all multiline blocks begins with a "{" on the next line. Infact, I have been writing code this way for almost 30 years and I have never had a bug like this. By following these rules, suddenly Allman takes actually less vertical space in many situation while still be imho more readable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment