Thursday, June 14, 2012

Indentation Holy Wars.

One thing that surprises me to no end is the number developers I meet who are willing to take up arms and fight to the death over the indentation and whitespace style of their code. And capitalization. And other things that the compiler ignores.

This wikipedia article, which is C/C++ centric, shows the main variations popular in C/C++ programming, and in related languages that use curly-braces, including C#, Javascript, and others.

Here's my personal indentation and whitespace style:

// Warren Style (The One True Way)
procedure MyProcedure( Arg1, Arg2 : Integer; Arg3 : string );
var
  i: Integer;
begin
  if arg1 > 10 then begin
      for i := 0 to 10 do begin
         DoSomething;
      end;    
  end else begin 
      DoSomethingElse;
  end;
end;

By my count, there a great many ways that people could rewrite, or nitpick about the above very simple code. Here are but a few of the myriad whitespace options Delphi people argue over:

1.  begin starts a new line, always? (Yes/No)
2.  Do we put a begin..end around a single statement or do we leave off Begin/End where possible?
3.  Indentation levels (2,3, or 4 spaces).
4.  Initial indentation level different than sub-levels (commonly 2 character indentation after var keyword, more than 2 character indentation inside begin/end blocks.
5   Location of begin and end : Pick a style from the wikipedia list.  Allman, Banner, K&R, and others.
6.  How do you police the width of your files and formatting of large argument lists to procedures?
     One parameter per line? As many as will fit in 80 columns?

If I multiply the options above, I get somewhere between 40 and 100 different options, depending on how many choices I can manufacture at the stages 1 through 5 above.

We all know holy wars are not objective matters.  But why do holy wars break out, and why do developers have a hard time dealing with variance from their chosen style?

I would like to propose that developers do not read their code. They see shapes, and where necessary they read their code. When code is not formatted the way they like it, they find that the level of effort that they must expend is increased.  So, in the end, it is a selfish sort of argument, as all holy war topics are.    My own preferences run towards the sample above, but any of the following are acceptable to me:


// 2 & 4 space Jedi Style
procedure MyProcedure(Arg1, Arg2 : Integer; Arg3 : string);
var
  i: Integer;
begin
  if arg1 > 10 then 
  begin
      for i := 0 to 10 do 
      begin
         DoSomething;
      end;    
  end 
  else 
  begin 
      DoSomethingElse;
  end;
end;


// Save The Chars Style
procedure MyProcedure(Arg1,Arg2:Integer;Arg3:string);
var
  i:Integer;
begin
  if arg1 > 10 then
  begin 
      for i := 0 to 10 do      
        DoSomething;
  end 
  else 
      DoSomethingElse;
end;



// consistently 2-spaced Jedi Style
procedure MyProcedure(Arg1, Arg2 : Integer; Arg3 : string);
var
  i: Integer;
begin
  if arg1 > 10 then 
  begin
    for i := 0 to 10 do 
    begin
      DoSomething;
    end;    
  end 
  else 
  begin 
    DoSomethingElse;
  end;
end;




The one style I encountered firmly entrenched at a big Delphi shop, that I could not live with is this style:


// Pascal+Banner Style

procedure MyProcedure(Arg1, Arg2:Integer; Arg3: string );
var
  i:Integer;
begin
  if arg1 > 10 then 
  begin
      for i := 0 to 10 do 
      begin
         DoSomething;
         end;    
      end 
  else 
  begin 
      DoSomethingElse;
      end;
end;




Did you see what happened there? The second (green) end keyword looks to me like it goes with the for-loop's begin (blue), but it actually goes with the if statement's begin statement.  Now not only does the visual "it scans easier" argument that the fans of the banner style prefer not make any sense to me, in the example above,  begin..end blocks inside a procedure have one set of rules (the banner style) but the outermost begin and end above, the ones I have marked in bright pink above, still line up with each other.  The mental anguish that the above style caused me, during my 9 months of employment at the company where the senior developer preferred that style probably caused me more anxiety and grief than any programmer in the world could imagine whitespace causing, until you end up on the losing side of a holy war on a topic you can't win.


You see, when an application's already built, and formatted a certain way, you have, as a single guy working at a company, a bit of a professional obligation to do what you can to work with that style. I tried, and I tried to learn to read the code style above. I found I could not. I gave up on formatting my code like I was told to format it. I just couldn't do it, my brain, and 30 years of habit were rebelling against me. I asked other delphi developers and they said they couldn't live with that style either.  But neither could the company or its longest standing Delphi guys countenance any change.  


In the end, there was only one way out.  I am no longer working at that company, and I'm glad to be gone.  Indentation and whitespace weren't the only ways we were not a good fit.  But it was a bit of a case in point.  Could you rewire your brain, to accept a style that later, after you're gone from a company, you still regard as a form of brain damage?  I couldn't.  


In the end, the rule is the rule;  Companies set up shop and run for 30 years, and you, the developer need to learn to work within their rules, and if you can't, then get out, or be fired.  Fair enough.  But it still amazes me that something as insignificant (to the compiler) as whitespace, can be a make or break criteria for you keeping your job, or perhaps even, whitespace might kill you.  Stress, and anxiety over a long period of time kills and sickens human beings every day.  Even if all the stress and fighting is only over something as insignificant as whitespace.


Addendum:  The multi-assignment alignment Sub-Holy-War.


Have you ever written code like this:
        A.Size       := GetAreaSize(Arg1, Arg2);
        A.Width      := GetAreaWidth(Arg3, Arg4)
        A.PanelColor := GetThemeColor(Arg5);


When I do, I like to have the equal signs line up if all the assignments are in a group. I prefer the above style to using with.  Other people hate it when you put an extra space after A.Size.   

8 comments:

  1. "( arg1,arg2: Integer; arg3: string )" is not JEDI style. It must be "(Arg1, Arg2: Integer; Arg3: string)" (space after comma, no space after "(" and no space before ")".

    What I hate the most is if one single developer can't decide how he wants to format the code. My guess is always that such a developer has a dice next to his keyboard to find out how large the indention should be and where to put the "begin", "else" and "end".

    Example:
    .if(.boolexpr)..then...begin
    .end
    else // why no indention here?
    .begin
    .end;


    I stick to the JEDI style, not only because I (sometimes) work on the JCL and JVCL code, but also because it is related to the style that is used in the VCL and that the IDE generates.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Andreas; I stick to the JEDI style when I am working on JVCL / JCL code, as best as I can, too.

    When working in another codebase, I stick to that style. You can see how many years of trying to be nice to everybody eventually could turn you into a grumpy old man.

    I also like to stick to the style that the VCL and the IDE generates, because it makes less "rework" for me. The IDE generates begin and end blocks at the procedure level that line up. Few people change that. But in the area where the user has full control, it seems that users get passionate opinions; Leave out begin and end whenever you can, always use begin and end, but don't need extra lines for them inside procedures, or always start on a newline.

    Maybe if the Jedi formatter or the Delphi IDE code formatter could reformat just one function, a lot of these holy wars could cease. As it is, I don't trust any formatter to reformat a whole unit, it just makes a mess.

    W

    ReplyDelete
  4. I started with a Pascal Modified Banner Style based on the following look:

    IF FileExists (fn) THEN
    ___BEGIN
    ___RESULT := TRUE;
    ___EXIT;
    ___END
    ELSE
    ___FOR i := 1 TO 5 DO BEGIN
    ______Sleep (1000);
    ______IF FileExists (fn) THEN EXIT;
    ______END;

    The purpose was that if you glance at the code, you see it is immediately an IF...ELSE block.

    When I have an IF, FOR, WHILE, etc. the next line is indented and that indentations stays until that IF, FOR WHILE block is ENDed.

    You might say the IF, FOR, WHILE, etc. are sort of tabs.

    HOWEVER, while theoretically arguable, my most common PROGRAMMING ERROR is thinking I have a BEGIN...END block simply because it is indented. This usually occurs because I use the further line saving compression method of

    FOR i := 1 TO 5 DO BEGIN

    I am beginning to think that a BEGIN...END block should be just that - a Block of Code. The BEGIN, END, and everthing inside is indented the same. ONLY the entire BLOCK is indented after an IF, FOR, WHILE, etc.

    I HATE the line wasting and visual distraction off the ELSE in the following:

    ___END
    ___ELSE
    ___BEGIN

    I hate the waste of indentation in the following:

    FOR i := 1 TO 7 DO
    ___BEGIN
    ______INC(z)
    ___END

    I think the MEANINGFUL RULE is that the BEGIN must take up the first line of the BEGIN...END block so that you clearly see that you have such a block. If the BEGIN is tacked on the end of the previously, not-indented line of code, you can quickly assume you have a BEGIN...END merely by the indentation when if fact you forgot the BEGIN.

    Anyhow, they hate me where I work too!!!!

    ReplyDelete
  5. Warren:

    I almost use what you use. Only difference is I do:

    end
    else begin
    DoSomethingElse;
    end;

    The else is not part of the previous block that is ended by the "end". And this way, the else block is lined up with its own "end".

    That last statement might be my reasoning, but the reason why I do this is because that's the way I've always done it.

    Louis

    (Just added your blog to my RSS feed list. It's always nice to find out about another Canadian Delphi user - we're a rare breed.)

    ReplyDelete
  6. Indenting was lost in my above comment. The "DoSomethingElse;" should have been.

    ReplyDelete