Indent with spaces, not tabs. Perl Best Practices (Conway 2005 p20)

Conway converted me from an 8-column-tab-man to 4 columns but he could not dissuade me from hard tabs. I'm aware that his is the majority opinion and I was willing to convert from tabs to spaces whenever making my code obviously public. But I said, It can do no harm in code that only I see.

My argument in favor of hard tabs is that I really like things to line up. I don't just entab; I delete existing tabs, frequently. A little spasm goes through my fingers when I see a section of code where a lot of stuff is all lined up nicely -- but not on a tab stop! Also, I'm annoyed when outdenting if I must hit the backspace key four times per tab stop.

My editor of choice, Geany, is very good about this but not perfect. Given:

line1 line2

...a single backspace keystroke will fix line2. But:

a line1 a line2

... requires four keystrokes. There's the risk that, if one is shortening a line not to match the previous line but in some anticipation, one will end up with, say, a trailing comment not on a tab stop.

In case you're among the unconverted, let me say that most editors will allow you to set the tab key to produce as many spaces as are needed to bring you up to the next tab stop. It's removing them that's been my major issue.

But, also, for the multiple space insertion to work correctly, one must position the cursor correctly at the start of the following token. Otherwise, the spaces between the cursor and the token are gleefully pushed out -- the correct number of spaces are inserted to position the cursor, not the text. My old fingers tend to wobble a bit and I end up clicking one space off; and there, it's messed up.

I have filtered my code before posting on PerlMonks or pastebin; I have accepted funky output from various shell commands that insist on antique 8-column tabs. I felt the price not too little to pay.

Conway encourages his readers to think for themselves and not blindly follow his injunctions; so I did.


My current work on a source filter has me running generated code through the Perl debugger. With hard tabs in my code, dumps look like this:

DB<8> y caller_id assertion report_code $assertion = '$x < \'1\'' $caller_id = 1 $report_code = " unless(\$x < '1') {\cI\cI\cI\cI\cI\cI\cI\cI \cISmart +::Comments::Any::Warn_for( \cI\cI\cI\cI\cI\cI\cI\cI\cI\cI \cI\cI\cI1, + \cI\cI\cI\cI\cI\cI\cI\cI \cI\cI\"\\n\", \cI\cI\cI\cI\cI\cI\cI\cI\cI +\cI \cI\cI\cIq{### \$x < '1' was not true} \cI\cI\cI\cI \cI\cI);\cI\c +I\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI \cI Smart::Comments::Any::Dump_for(\ +cI\cI\cI\cI\cI\cI\cI \cI-caller_id\cI=> 1,\cI\cI \cI-prefix\cI\cI=> + q{ \$x was:},\cI \cI-varref\cI\cI=> [\$x], \cI\cI\cI \cI-no_new +line\cI=> 1\cI\cI\cI\cI );\cI\cI\cI\cI\cI\cI\cI\cI\cI;\cI\cI\cI\cI\c +I\cI\cI \cI die \"\\n\"\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI +\cI }\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI\cI"

Trimming off the trailing tabs doesn't fix it, either; tabs internal to the code are still escaped in this peculiar and unreadable fashion. Geany will convert a whole source file from tabs to spaces -- correctly -- but there's no obvious way to convert just the code generation strings. Find-and-replace from 1 tab to 4 spaces fails, since each tab represents a variable amount of spaces.

Having had it demonstrated for all time that I was wrong, I'm now sold. No hard tabs in code: none.


My need to be sure I'm lining up on tab stops is still strong. My workaround is in the form of this scratch snippet:

# --- TAB STOPS --- 1 1 1 1 1 1 1 1 + 1 1# # 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 + 8 9# # | | | | | | | | | | | | | | | | | + | |#

This may look wrong onscreen but it's exactly 78 columns wide. It can be pasted anywhere in code to check if all is correct.


Some Monks have fallen into the error of hard tabs "only for indentation". Indenting is a logical concept but it is not always clear what is indentation and what is spacing internal to the line. Consider this (abbreviated) real example:

1: # This is not a subroutine but a call to Filter::Simple::FILTER 2: # with its single argument being its following block. 3: # 4: FILTER { 5: ##### |--- Start of filter ---| 6: ##### @_ 7: 8: # Assertions... 9: s{ ^ $hws* $intro [ \t] $check : \s* (.*?) $optcolon $hws* $ } 10: { _decode_assert($caller_id, $1) }egmx; 11: 12: # Undocumented feature: 13: # # Anything else is a literal string... 14: # s{ ^ $hws* $intro $hws* (.*) } 15: # {Dump_for(-prefix=>q{$1});$DBX}gmx; 16: 17: #~ say "---| Source after filtering:\n", $_, '|--- END SOURCE CODE +'; #~ 18: 19: }; 20:

My interpretation:

Which of these can/should be replaced by hard tabs? I say none.

- the lyf so short, the craft so long to lerne -