Why Tabs are Better than Spaces

Noah Petherbridge
kirsle
Posted by Noah Petherbridge on Thursday, June 15 2017 @ 03:34:13 PM

This is going to be yet another blog post in the "tabs vs. spaces" holy war that software developers like to fight about. I generally prefer tabs over spaces, but for certain types of programming languages I do use spaces instead of tabs.

I indent my Python and CoffeeScript with spaces, but all the other languages I use (Perl, Go, JavaScript, HTML, ...) get the tab characters.

What's all this, then?

If you've been living under a rock and haven't heard of this holy war, here's an example. Here's some source code:

func main() {
    server := NewServer()

    if os.Getenv("DEBUG") != "" {
        fmt.Println("Debug mode is on")
        server.Debug = true
    }

    server.Run()
}

Programmers who prefer to use tab characters to indent their code would write the code using tab symbols for each level of indentation (two tabs for the inside of that if statement). Programmers who prefer to indent with spaces, on the other hand, would've used 4 spaces to get the above code per indent level, with the if block indented by 8 spaces (two indent levels deep).

Modern code editors can deal with both types of indents equally, so the programmer who uses 4 space indents would still hit the Tab key to insert 4 spaces at a time, and backspacing the indent would backspace 4 at a time. The real difference just comes down to what actual characters are used for the indent (a single tab character or four space characters).

My rule of thumb

My rules for determining which type of indentation I use:

  • By default, I indent with tabs.
    • Perl, HTML, JavaScript, Java, ..., most programming languages.
  • But, if a programming language already has a strong style guide that prefers spaces, I conform to the style guide.
    • Python's PEP8 style guide says to use 4 space indents.
    • YAML and CoffeeScript developers have largely standardized around 2 space indents.
    • For Markdown I use 2 spaces as preferred by GitHub Flavored Markdown.

I won't make arguments about "a tab is only one byte but spaces are 4 bytes" because that doesn't really matter and it's all the same to the compiler. Instead my arguments are for what the developer experience is when writing code and collaborating with others.

So, why tabs?

First, let's start with a "conversation tree" on how the debate over tabs vs. spaces would play out:

  • Are tabs or spaces better?
    • Use tabs. Done.
    • Use spaces.
      • Okay, how many spaces?
        • 2 spaces
        • 4 spaces
        • 3 spaces (this is my troll answer when I get to this question)

The question isn't only about tabs or spaces, it's also about how wide do you want your indent to appear in your editor? This is the "2 vs 4 vs 3" space debate listed above.

Indent Width

If you use tabs to indent, then every programmer can use their own preferred indent width. If you like your code to use 4-space indents, you can just configure your text editor to show a tab as equivalent to 4 spaces. If your coworker next to you likes 2-space indents, he can have tabs appear as 2 spaces. If that weird coworker who likes 3 spaces wants to work on your code, he can show tabs as 3 spaces. Everyone gets what they want.

If you use spaces to indent, though, you'll run into personality clashes if two developers have different preferences on the indent width. I've seen GitHub pull requests where somebody reformatted the entire file to change the space count for indents to fit their own personal preference, only for the next pull request to change all the spaces back because the next guy liked them how they were.

What about lining up your source code?

A common argument I hear from the Spaces Camp is that you can't line up your source code all nice and pretty unless you use spaces all throughout. For example:

func CreateNewUser(username string, password string, first_name string,
                   last_name string, is_admin bool) *User {
    // Create a user object.
    user := &User{}

    // Call another function with a ton of parameters.
    user.InitializeDefaults(username, password, first_name,
                            last_name, is_admin,
    )

    user.Save()
    return user
}

In this example, the argument list for CreateNewUser() is so long that I had to span multiple lines of code, and I lined up the second line so all the arguments look nice and tidy in my editor.

Later, when I call user.InitializeDefaults(), I do something similar.

The Spaces Camp would say that if you indent with tabs, then a programmer is likely to hit "tab-tab-tab-tab-space-space" to get roughly to the right indent level and use spaces for the last couple inches, so that the code looks good on their screen. But then when somebody who has a different indent width configured (e.g. 2 spaces instead of 4) views the code, the formatting is all messed up:

func CreateNewUser(username string, password string, first_name string,
       last_name string, is_admin bool) *User {
  // Create a user object.
  user := &User{}

  // Call another function with a ton of parameters.
  user.InitializeDefaults(username, password, first_name,
              last_name, is_admin,
  )

  user.Save()
  return user
}

There's an easy solution to this that still works with tabs:

  • Indent using tabs to the same indent level as the first line of code,
  • And then use spaces to line things up.

Here's a short example of what I mean. I'm showing invisible characters in this example, so the represents a tab character and a · represents a space character.

func Example(username string,
·············password string) {
→   // function body indented with tabs per usual.
→   if os.Getenv("DEBUG") != "" {
→   →   fmt.Println("Debug mode enabled")
→   }

→   // But when you need to line stuff up, you tab to the same level as
→   // the parent line of code, and space to line up from there.
→   SomeLongFunctionCall(username,
→   ·····················password)
}

Now you can still perfectly line up code when needed, and if your coworker uses a different indent width than you do, everything is still lined up! In the example above I'm using a 4-space indent width, and if you changed the width to 2 spaces:

func Example(username string,
·············password string) {
→ // Hello, world.
→ // function body indented with tabs per usual.
→ if os.Getenv("DEBUG") != "" {
→ → fmt.Println("Debug mode enabled")
→ }

→ // But when you need to line stuff up, you tab to the same level as
→ // the parent line of code, and space to line up from there.
→ SomeLongFunctionCall(username,
→ ·····················password)
}

Everything is still lined up.

tl;dr.

People have strong opinions on this and I don't expect to be able to convince anyone, but this is how I indent my code:

  • Tabs by default for all file types.
  • Spaces only for file types that have a strong style guide that suggests spaces.
  • To line up code: tab to the same indent level, and then use spaces for alignment.

But above all, Rule #1 is to use the existing coding style when you join a project. At work I usually have to use spaces for all the things because we code in Python and most people feel it's easier to also format HTML and JavaScript the same way, but for my personal projects, I follow my own rules.

Categories:

[ Blog ]

Comments

There are 0 comments on this page.

Add a Comment

Your name:
Your Email:
Message:
Comments can be formatted with Markdown, and you can use
emoticons in your comment.

If you can see this, don't touch the following fields.