Tab v Space

Many years ago, a work colleague/friend and I had a disagreement over the use of tabs and spaces in software source code. At the time, my preference was to indent my code with two spaces as I often had complex nested operations that would vanish on the right-hand side of my screen if I were using the conventional 8 spaces.

Indenting with 2 spaces:

while ( unopenedThingsExist() ) {
  for ( eachThing in theCollection ) {
    with ( opened ( eachThing ) ) {
      if ( lookInside ( eachThing ) == something ) {
        display ( nameOf ( eachThing ) );
      }
    }
  }
}

Indenting with 8 spaces

while ( unopenedThingsExist() ) {
        for ( eachThing in theCollection ) {
               with ( opened ( eachThing ) ) {
                        if ( lookInside ( eachThing ) == something ) {
                                display ( nameOf ( eachThing ) );
                        }
                }
        }
}

His well-reasoned argument for using tabs was that other people with different width screens could simply tell their development tool of choice to render tabs as 4 or 8 spaces, or whatever was their preference. I adjusted my tools to replace the double-spaces with tabs, and all looked well, until it was pointed out to me that after the adjustment, comments to the right were a mess:

while ( unopenedThingsExist() ) {                    # guaranteed to end
  for ( eachThing in theCollection ) {               # collection is not empty
    with ( opened ( eachThing ) ) {                  # once opened, stays opened
      if ( lookInside ( eachThing ) == something ) {
        display ( nameOf ( eachThing ) );            # guaranteed not blank
      }
    }
  }
}

becomes:

while ( unopenedThingsExist() ) {                                        # guaranteed to end
    for ( eachThing in theCollection ) {                              # collection is not empty
        with ( opened ( eachThing ) ) {                                    # once opened, stays opened
            if ( lookInside ( eachThing ) == something ) {
                display ( nameOf ( eachThing ) );                        # guaranteed not blank
            }
        }
    }
}

Thus came about my general rule about tabs and spaces in source code:

Tabs on the left, spaces on the right.

To be precise, all the blank space from the left up to the start of the code should be tabs, and from that point on, all blanks are spaces. With this rule we get the following:

while ( unopenedThingsExist() ) {                    # guaranteed to end
    for ( eachThing in theCollection ) {               # collection is not empty
        with ( opened ( eachThing ) ) {                  # once opened, stays opened
            if ( lookInside ( eachThing ) == something ) {
                display ( nameOf ( eachThing ) );            # guaranteed not blank
            }
        }
    }
}

While this approach produces a better, albeit indented, alignment of the comments on the right, it doesn’t maintain the precise vertical arrangement of the original. Is it possible to preserve the vertical alignment of the comments? Yes, but not without some awkward mix of tabs and spaces. To achieve a vertical alignment that can survive a change of tab width, first you have to determine the maximum indenting expected in the source code. In the example, this maximum is 4 tabs (the “display” line). Next you have to ensure that this many tabs are present on every line, with the first tab(s) before the source code, and the remaining tabs after the last character of the source code (before the spaces that occur before the comment). For example, the second line of the source would be arranged as follows:

[**]for ( eachThing in theCollection ) {[**][**][**]        # collection is not empty

That’s one tab on the left, and three tabs to the right of the final “{“. With this use of tabs, the vertical alignment of the comments will be preserved regardless of how you set the tab/space equivalence.

while ( unopenedThingsExist() ) {                            # guaranteed to end
    for ( eachThing in theCollection ) {                     # collection is not empty
        with ( opened ( eachThing ) ) {                      # once opened, stays opened
            if ( lookInside ( eachThing ) == something ) {
                display ( nameOf ( eachThing ) );            # guaranteed not blank
            }
        }
    }
}

However, this would require a level of discipline with the input of invisible characters that I doubt any coder could achieve.

Optimal solution

There is an optimal and far superior solution, but it involves getting rid of two ideas: 1) tabs can be replaced by N spaces and 2) tabs align vertically with a multiple of N spaces. These two ideas come from the old fixed-width printers and screens. The printer tabs were a particular influence, and are not the equivalent of “tab stops” as found on typewriters, which are more like the tabbing positions you find in wordprocessing applications (e.g. MS Word).

By using a tab stop approach, you can continue to use tabs on the left for indenting, but on the right the tab character is interpreted semantically to indicate that you are moving to a new “block” of content. The tab is used more like an indication to move into the next cell of a table, rather than moving a certain number of characters to the right. This approach, outlined in detail by Nick Gravgaard in the mid-2000s, was adopted in Go’s tabwriter and a Notepad++ plugin, and Nick’s own extension for VS, but most IDEs don’t yet have this feature. Eclipse doesn’t have the underlying rendering features to support tab stops, while Netbeans could support it via its Swing components but nobody has created a plugin for it.

So, until tab stops come to all the IDEs that I use, I’ll stick with “tabs to the left, spaces to the right” and hope for the best.

Categorised as: Coding

Comment Free Zone

Comments are closed.