72
if   i>0 : return sqrt(i)  
elif i==0: return 0  
else     : return 1j * sqrt(-i)

VS

if i>0:  
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

Given the above examples, I don't understand why I virtually never see the first style in code bases. To me, you turn the code into a tabular format that shows clearly what you want. First column can virtually be ignored. Second column identifies the condition and the third column gives you the output you want. It seems, at least to me, straight-forward and easy to read. Yet I always see this simple kind of case/switch situation come out in the extended, tab indented format. Why is that? Do people find the second format more readable?

The only case where this could be problematic is if the code changes and gets longer. In that case, I think it's perfectly reasonable to refactor the code into the long, indented format. Does everyone do it the second way simply because it's the way it always was done? Being a devil's advocate, I guess another reason might be because people find two different formats depending upon complexity of the if/else statements to be confusing? Any insight would be appreciated.

horta
  • 840

10 Answers10

133

It's more readable. A few reasons why:

  • Nearly every language uses this syntax (not all, most - your example appears to be Python, though)
  • isanae pointed out in a comment that most debuggers are line based (not statement based)
  • It starts looking even more ugly if you have to inline semicolons or braces
  • It reads top to bottom more smoothly
  • It looks horribly unreadable if you have anything other than trivial return statements
    • Any indenting meaningful syntax is lost when you skim code as the conditional code is no longer visually separated (from Dan Neely)
    • This will be particularly bad if you continue to patch/add items into the 1-line if statements
  • It's only readable if all your if checks are about the same length
  • It means you cannot format complicated if statements into multiline statements, they will have to be oneliners
  • I am far more likely to notice bugs/logicflow when reading vertically line by line, not trying to parse multiple lines together
  • Our brains read narrower, taller text MUCH faster than long, horizontal text

The minute you try to do any of this you will end up rewriting it into multiline statements. Which means you just wasted time!

Also people inevitably add something like:

if i>0:  
   print('foobar')
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

It doesn't take very often doing this before you decide this format is a lot better than your alternative. Ah, but you could inline it all in one line! enderland dies on the inside.

Or this:

if   i>0 : return sqrt(i)  
elif i==0 and bar==0: return 0  
else     : return 1j * sqrt(-i)

Which is really, really annoying. No one likes formatting things like this.

And last, you will start the holy war of "how many spaces for tabs" problem. What renders perfectly on your screen as a table format may not render on mine depending on settings.

Readability should not depend on IDE settings anyways.

enderland
  • 12,201
  • 4
  • 53
  • 64
93

One reason may be that you're not using languages where it's popular.

A few counter-examples:

Haskell with guards and with patterns:

sign x |  x >  0        =   1
       |  x == 0        =   0
       |  x <  0        =  -1

take  0     _           =  []
take  _     []          =  []
take  n     (x:xs)      =  x : take (n-1) xs

Erlang with patterns:

insert(X,Set) ->
    case lists:member(X,Set) of
        true  -> Set;
        false -> [X|Set]
    end.

Emacs lisp:

(pcase (get-return-code x)
  (`success       (message "Done!"))
  (`would-block   (message "Sorry, can't do it now"))
  (`read-only     (message "The shmliblick is read-only"))
  (`access-denied (message "You do not have the needed rights"))
  (code           (message "Unknown return code %S" code)))

Generally I see the table format is pretty popular with functional languages (and in general expression based ones), while breaking the lines is most popular in others (mostly statement based).

viraptor
  • 814
54

I am a firm believer in 'code is read many times, written few -- so readability is very important.'

A key thing that helps me when I read other people's code is that is follows the 'normal' patterns that my eyes are trained to recognize. I can read the indented form most easily because I've seen it so many times that it registers almost automatically (with little cognitive effort on my part). It's not because it's 'prettier' -- it's because it follows the conventions that I'm used to. Convention beats 'better' ...

Art Swri
  • 667
16

Along with the other drawbacks already mentioned, tabular layout increases the odds of version control merge conflicts requiring manual intervention.

When a block of tabularly alined code needs to be realigned, the version control system will treat each of those lines as having been modified:

diff --git a/foo.rb b/foo.rb
index 40f7833..694d8fe 100644
--- a/foo.rb
+++ b/foo.rb
@@ -1,8 +1,8 @@
 class Foo

   def initialize(options)
-    @cached_metadata = options[:metadata]
-    @logger          = options[:logger]
+    @metadata = options[:metadata]
+    @logger   = options[:logger]
   end

 end

Now suppose that in the mean time, in another branch, a programmer has added a new line to the block of aligned code:

diff --git a/foo.rb b/foo.rb
index 40f7833..86648cb 100644
--- a/foo.rb
+++ b/foo.rb
@@ -3,6 +3,7 @@ class Foo
   def initialize(options)
     @cached_metadata = options[:metadata]
     @logger          = options[:logger]
+    @kittens         = options[:kittens]
   end

 end

Merging that branch will fail:

wayne@mercury:/tmp/foo$ git merge add_kittens
Auto-merging foo.rb
CONFLICT (content): Merge conflict in foo.rb
Automatic merge failed; fix conflicts and then commit the result.

If the code being modified had not used tabular alignment, the merge would have succeeded automatically.

(This answer was "plagiarized" from my own article discouraging tabular alignment in code).

Wayne Conrad
  • 1,148
8

Tabular formats can be very nice if things always fit within the allotted width. If something exceeds the allotted width, however, then it often becomes necessary to either have part of the table that isn't lined up with the rest of it, or else adjust the layout of everything else in the table to fit match the long item.

If source files were edited using programs that were designed to work with tabular-format data and could handle overly long items by using a smaller font size, splitting them into two lines within the same cell, etc. then it might make sense to use tabular formats more often, but most compilers want source files that are free of the kinds of markup that such editors would need to store in order to maintain formatting. Using lines with variable amounts of indent but no other layout isn't as nice as tabular formatting in the best case, but it doesn't cause nearly as many problems in the worst case.

supercat
  • 8,629
6

There is the 'switch' statement that provides this kind of thing for special cases, but I guess that's not what you're asking about.

I have seen if statements in table format, but there has to be a large number of conditions to make it worthwhile. 3 if statements are best shown in the traditional format, but if you had 20, then its much easier to display them in a big block that's formatted to make it clearer.

And there's the point: clarity. If it makes it easier to see (and your first example is not easy to see where the : delimiter is) then format it to suit the situation. Otherwise, stick with what people expect, as that's always easier to recognise.

gbjbaanb
  • 48,749
  • 7
  • 106
  • 173
1

Tabular layout can be useful in few limited cases, but there are few times it is useful with if.

In simple cases ?: may be a better choice. In medium cases a switch is often a better fit (if your language has one). In complicated cases you might find that a call tables are a better fit.

There have been many times when refactoring code that I have rearranged it to be tabular to make it's patern obvious. It is seldom the case that I leave it that way in that in most cases there is a better way to solve the problem once you understand it. Occasionally a coding practice or layout standard prohibits it, in which case a comment is useful.

There was some questions about ?:. Yes it is the ternary operator (or as I like to think of it the value if). on first blush this example is a little complicated for ?: (and overusing ?: does not help readability but hurts it), but with some thought The example can be rearranged as below, But I think in this case a switch is the most readable solution.

if i==0: return 0
return i>0?sqrt(i):(1j*sqrt(-i))
hildred
  • 759
1

If your expression really is that easy most programming languages offer the ?: branching operator:

return  ( i > 0  ) ? sqrt( i)
      : ( i == 0 ) ? 0
        /* else */ : 1j * sqrt( -i )

This is a short readable tabular format. But the important part is: I see at one glance what the "major" action is. This is a return statement! And the value is decided by certain conditions.

If on the other hand you have branches which execute different code, I find it a lot more readable to indent these blocks. Because now there are different "major" actions depending on the if-statement. In one case we throw, in one case we log and return or just return. There is a different program flow depending on the logic, so code-blocks encapsulate the different branches and make them more prominent for the developer (e.g. speed-reading a function to grasp the program flow)

if ( i > 0 )
{
    throw new InvalidInputException(...);
}
else if ( i == 0 )
{
    return 0;
}
else
{
    log( "Calculating sqrt" );
    return sqrt( -i );
}
Falco
  • 1,299
1

As enderland has already said, you're assuming you only ever have one "return" as the action, and that you can tag that "return" onto the end of the condition. I'd like to give some extra detail for why this isn't going to be successful.

I don't know what your preferred languages are, but I've been coding in C for a long time. There are a number of coding standards around which aim to avoid some standard coding errors by disallowing code constructions which are prone to errors, either in initial coding or during later maintenance. I'm most familiar with MISRA-C, but there are others, and generally they all have similar rules because they're addressing the same problems in the same language.

One popular error which coding standards often address is this little gotcha:-

if (x == 10)
    do_something();
    do_something_else();

This does not do what you think it does. As far as C is concerned, if x is 10 then you call do_something(), but then do_something_else() gets called regardless of the value of x. Only the action immediately following the "if" statement is conditional. This may be what the coder intended, in which case there's a potential trap for maintainers; or it may not be what the coder intended, in which case there's a bug. It's a popular interview question.

The solution in coding standards is to mandate braces around all conditional actions, even if they're single-line. We now get

if (x == 10)
{
    do_something();
    do_something_else();
}

or

if (x == 10)
{
    do_something();
}
do_something_else();

and now it works correctly and is clear for maintainers.

You will notice that this is entirely incompatible with your table-style format.

Some other languages (e.g. Python) looked at this problem and decided that since coders were using whitespace to make layout clear, it would be a good idea to use whitespace instead of braces. So in Python,

if x == 10:
    do_something()
    do_something_else()

makes the calls to both do_something() and do_something_else() conditional on x == 10, whereas

if x == 10:
    do_something()
do_something_else()

means that only do_something() is conditional on x, and do_something_else() is always called.

It's a valid concept, and you'll find a few languages use it. (I first saw it in Occam2, way back when.) Again though, you can easily see that your table-style format is incompatible with the language.

Graham
  • 2,062
-3

I don't see anything wrong with the table format. Personal preference, but I would use a ternary like this:

return i>0  ? sqrt(i)       :
       i==0 ? 0             :
              1j * sqrt(-i)

No need to repeat return every time :)

Navin
  • 97
  • 1
  • 6