Indent with tabs, align with spaces

Preface

When I was much younger, I used to use tabs in my code. After some time, I started getting headaches because the code gets messed up if some editor/viewer uses different tab size. For example, this is the code I wrote assuming tab to be 3 characters:

void my_func(void)
{
   int var1          = 1;
   int another_var   = 2;
   int third_var     = 3;
 
   if (  something
      && something_else
      && maybe_something_else
      )
   {
      call_function( long_param[ long_subscript ],
                     another_param );
   }
}

And here is what it looks like if someone reads it with tab equal to 8 characters:

void my_func(void)
{
        int var1                                = 1;
        int another_var = 2;
        int third_var           = 3;
 
        if (  something
                && something_else
                && maybe_something_else
                )
        {
                call_function( long_param[ long_subscript ],
                                                        another_param );
        }
}

The same happens if I need to edit some foreign code with tabs, and tab size is assumed to be other than my settings. So, after all, I considered tabs as unconditional evil, banned them from my own code and blamed everybody else who uses tabs. My colleagues (which were more experienced than me) agreed, so I was quite sure that I'm “doing that right”.

However, recently I figured out that tabs are actually misused by a lot of people, including myself. If we use tabs correctly, they are great tool!

Indentation and alignment

The main problem is that a way too many people don't understand (or don't care about) the difference between an indentation and an alignment.

This is an indentation:

void my_func(void)
{
   do_something();
   if (everything_ok()){
      do_something_different();
   }
}

And this is an alignment:

int var1         = 0,
    another_var  = 1,
    third_var    = 2;

It's very important to only use spaces for alignment. Never, ever, use tabs for alignment. Tabs may be used for indentation only.

Let's combine previous two examples (for clarification, I indicate tabs as >--, and spaces as . (a dot)).

Tab is 3 characters:

void my_func(void)
{
>--do_something();
>--if (everything_ok()){
>-->--int var1.........= 0,
>-->--....another_var..= 1,
>-->--....third_var....= 2;
 
>-->--do_something_different();
>--}
}

Tab is 4 characters:

void my_func(void)
{
>---do_something();
>---if (everything_ok()){
>--->---int var1.........= 0,
>--->---....another_var..= 1,
>--->---....third_var....= 2;
 
>--->---do_something_different();
>---}
}

Tab is 8 characters:

void my_func(void)
{
>-------do_something();
>-------if (everything_ok()){
>------->-------int var1.........= 0,
>------->-------....another_var..= 1,
>------->-------....third_var....= 2;
 
>------->-------do_something_different();
>-------}
}

It looks fine whatever the tab size is. And that's great. Say, if I'm on the netbook with small screen, I'd probably set tab to 2 characters in order to see more code at my screen, and it will look fine. I wouldn't have this kind of convenience if code would have beed indented with spaces.

Anybody who reads the code could use his or her favorite preferences on tab size, and nothing will be mixed up. That makes tabs actually a superior to spaces, when it comes to indentation. Try it!

Vim settings

Although the indentation / alignment politics are independent of the editor being used, I decided to put some recommendations on how to set up Vim correctly so that it would be easy for you to follow the principle “Indent with tabs, align with spaces” in this editor, if you use it.

Unfortunately, at the moment Vim can't handle this automatically. It's too bad actually. Anyway, there are a couple of tips to make Vim behave better.

See your tabs and trailing spaces

In order to write code that is correctly indented and aligned, it is almost inevitable to see your tabs and spaces. To this end, put this to your vimrc:

" display tabs and trailing spaces
set list listchars=tab:>-,trail:.,extends:>,precedes:<

and you will see >-- for tabs and . for trailing spaces. I've set up my colorscheme so that it doesn't distract me from actual code.

Smart Tabs

There is a plugin “Smart Tabs”. When you hit <Tab> key at the beginning of the line, then tab character is inserted; otherwise (if there is something other than a whitespace before the cursor), the proper amount of spaces are inserted.

copyindent

I found it useful to turn on the option copyindent:

" copy indent from previous line: useful when using tabs for indentation
" and spaces for alignment
set copyindent

Then, vim will copy indentation from previous line. Consider (the | sign represents cursor position):

>-->--int var1 = 1,
>-->--....var2 = 2,|

When you press Enter, you most likely want the editor to keep amount of tabs/spaces, so that it becomes:

>-->--int var1 = 1,
>-->--....var2 = 2,
>-->--....|

This is what copyindent option is for.

Conclusion

Tabs are a decent tool when they are used properly, and they are a call for trouble if used carelessly. I consider this scale correct:

  • Use tabs for indentation, spaces for alignment: the superior approach. Code is indented and aligned properly, and indentation is flexible.
  • Use spaces for both indentation and alignment: still fine, but not flexible enough.
  • Use tabs for both indentation and alignment: completely unacceptable, leads to headaches.

Personally, in most of my projects I still use spaces only. That's mostly because we often want the editor to automatically align the code for us, but editors, unfortunately, aren't smart enough to always distinguish correctly where do we need an alignment and where do we need an indentation. I hope I'll find the way to teach my editor some day, but now at least I know how to do things right when I need to work on foreign projects that use tabs.

If you use spaces in your code for indentation, I hope that after reading this article you will at least think of giving tabs a chance. And if you already use tabs, I really hope you use them right!

Discussion

real name, 2015/09/21 20:14

On some lines, you've got a ';' where I think you should have a ','. :P

Dmitry Frank, 2015/09/21 20:24

Oh, true! You know, untested copy-pasted code. :) Thanks! Fixed.

isomorphismes, 2017/06/24 14:03

:set tabstop, :set shiftwidth — spend 5 seconds on foreign tab usage and everyone can use tabs however they want. I like them as column separators (.tsv), for example.

If you use \t you can achieve all kinds of great things, like:

f( g( h(x) ) )

not getting lost in parentheses *or* taking up multiple lines for something simple.

(Although really we should move beyond paren-matching. https://twitter.com/isomorphisms/status/853015567929925632 http://isomorphism.es/post/54761824472/an-illustration-i-made-for-michiexiles-a-for What a waste of time for humans!)

IDE's are here to help us. It is their job to make people's text readable. The user is right. If the user wanted to use long lines, UTF-8 characters, tabs however s/he pleases, etc —- it's very possible, and reasonable to ask, for the IDE to wrap / read / align that text for the next user.

Brian Dayhoff, 2017/09/22 17:16

Editors aren't smart enough to tell between mixed tabs and spaces because they shouldn't. Mixing the two causes potential for merge conflicts in git/svn/subversion, confuse diffs with trivial spacing edits and attribute the actual logic to the wrong person (because they changed the indent when they auto-formatted. A bigger problem for svn than git because git tracks character edits while svn only tracks line edits, but still, this is unneccessary clutter either way) busted formatting based on individual tab settings mixed with the spacing which can't be guaranteed to look good universally, and numerous other issues that cannot be visibly distinguished at a glance, because there is no visible distinction between a tab and a set of spaces without checking with your cursor, and any program that converts one to the other will further convolute this, and force you to do complicated greps and preg_matches to find the issue for the non-printing characters (\t, \s, ,\n vs \r\n, etc). That is a lot of potential problems for the minor gain of not scrolling your screen as much. The trade off of risk vs reward is simply not stacked in your favor.

If screen size is an issue for you, get a bigger screen, that is no one elses problem, and should never snowball into being their problem. Inflicting your own visual preference on the rest of the team you are working with if there is any collaboration whatsoever causes everyone else pain for your convenience. Don't do that. Pick team tab or team space and stick with it, or alternately don't ever work on a project with other people, and don't check in to any repo that has anyone other than only yourself working on it. They will in turn auto format which will break your schema, and yours will break theirs, and both of you will waste unnecessary time fiddling with something that should be a non-issue.

PS: Your captcha is almost impossible to resolve visibly due to the line height of the pre tags (line-height: 150%, inherited from body, needs to be overridden to line-height: 0 at #plugin__captcha). Please fix that. Anyone who cannot manually fiddle with css can't really comment at all, and it's a chore for anyone that can.

Dmitry Frank, 2017/09/22 17:47

You seem to have totally missed the point. Indenting with tabs and aligning with spaces is not about mixing styles, it's about using the right tool for the job. There will be no merge conflicts if editors (or whatever formatting tools collaborators use) handle that automatically. And using the same formatting tool for the whole team is not a big deal: just set on-commit hook in git, and that's it. And that's what we do, actually.

Jed Mao, 2020/11/24 22:09

Brian, using tabs for indentation and spaces for alignment isn't the same thing as mixing them. If you were to put tabs in alignment regions and/or spaces in indentation regions, THAT would be mixng and that would definitely be bad.

If an editor or tool needs to understand code then it should be parsing the code into AST, not reading raw text; otherwise, it will be prone to issues dealing with multi-line comments and strings.

Alan Kilborn, 2018/11/19 13:22

So the only problem with your discussion is that IT DOESN'T WORK. As an example, in your “tab is 4 characters” section, change the “third_var” line to have one more leading tab (new total of 3). Add some more spaces before the = signs on the 3 lines that are supposed to have alignment and get them all “pretty”. Save the file and now open it with different tab settings (e.g. 8). The alignment is now not so aligned.

Dmitry Frank, 2018/11/19 15:33
change the “third_var” line to have one more leading tab (new total of 3).

Why would I add one more leading tab there? There are as many leading tabs as needed: the indentation level is 2, that's why there are 2 tabs. The rest of spacing is alignment, so we use spaces there. No, sorry, I'm not going to add more leading tabs there.

real name, 2021/01/21 22:40
(if there is something other than a whitespace before the cursor), the proper amount of spaces are inserted.

You probably meant “if there is something other than a tab character before the cursor” here. Otherwise, that plugin is broken.

Enter your comment (please, English only). Wiki syntax is allowed:
  __  __   ____   ___    ____   ____
 / / / /  / __/  / _ \  /  _/  / __/
/ /_/ /  _\ \   / ___/ _/ /   / _/  
\____/  /___/  /_/    /___/  /___/
 
articles/indent_with_tabs_align_with_spaces.txt · Last modified: 2015/09/21 20:23 by dfrank
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0