Over-indenting is the enemy number one


Link to this posting

Postby Ursego » 21 Feb 2013, 10:42

Indent code fragments with as few tabs as possible.

Avoidance of over-indenting forces us to organize our functions correctly rather than create one huge unreadable and bugs-prone mess. The key word here is "forces". That will keep our functions simple and easy to work with. Indentation is like salt in a soup: it's a must, but over-use will make the product unfit for consumption. If your code has long "staircases" of closing braces (}), END IFs or other code-branching keywords, then it's a good idea to take your habits critically!

REMARK: Counting tabs, we start from the basic level of left indentation. In languages and IDEs where you open a single function in the editor (like PowerBuilder, Oracle Forms and Transact-SQL), you start writing code with zero tabs (no indentation). But if the editor opens the whole class or package, then the basic level of left indentation is already 2: one tab to indent the method inside the class or package, another - to indent the code inside the method - that what we have in Java, .Net classes and Oracle packages. So, in the last case, consider the basic level of left indentation (which is already 2) as zero tabs.

From the book "Clean Code":

...the blocks within if statements, else statements, while statements, and so on should be one line long. Probably that line should be a function call. Not only does this keep the enclosing function small, but it also adds documentary value because the function called within the block can have a nicely descriptive name. This also implies that functions should not be large enough to hold nested structures. Therefore, the indent level of a function should not be greater than one or two. This, of course, makes the functions easier to read and understand.


2 tabs are absolutely ok.

3 tabs are ok if the indented fragment it not long. If it's longer than half a screen, then consider extracting the whole fragment into a brand new function. Your 3 tabs will magically become no tabs, so, in the future, you can add a couple of indent levels without causing the wrath of the gods. If the code fragment is executed after a few conditions, checked after each other (so, each condition adds a tab), then use the principle, described in the topic Code after validations.

4 tabs are a "red light" and usually signal about incorrect organization of code structure - in most cases, the code could (and should!) be written in a more elegant way. As an exception, 4 tabs can be used rarely if the indented fragment is just a few lines.

5 tabs: you will burn in hell forever :evil: ! I remember the words of one of my first project managers (a guru of elegant programming):

Do whatever, jump over your head, but avoid indentation of 5 tabs! If I see 5 tabs in your code, you are no more in the project.


Fragments, which logically have the same level of nesting, should be indented with the same amount of tabs, if possible.

The single most important formatting convention that you can follow is to indent your programs properly, so indentation conveys the structure of the program to the reader at a glance. Indentation must enlighten, not confuse!

Here is an example of confusing code structure, seen in many-many applications. Logically, the code fragments have the same level of nesting (because, in the end of the day, only one of them will have been executed). But they are indented with different amounts of tabs, so the indentation doesn't help the reader to understand. The example is very simple, but keep in mind, that, in reality, the number of conditions can be much larger, and each code fragment can be long, with its own levels of indentation:

*** BAD code: ***

Code: Select all
if <condition 1> then
   [code fragment 1]
else
   if <condition 2> then
      [code fragment 2]
   else
      if <condition 3> then
         [code fragment 3]
      else
         [code fragment 4]
      end if
   end if
end if

The indentation, though clearly systematic, is not a help. If we eliminate unnecessary grouping, and indent to show that the fragment is basically one if construction, things clarify remarkably. Now, fragments, which logically have the same level of nesting, have the same level of indentation:

*** GOOD code: ***

Code: Select all
if <condition 1> then
   [code fragment 1]
elseif <condition 2> then
   [code fragment 2]
elseif <condition 3> then
   [code fragment 3]
else
   [code fragment 4]
end if

Now we know for certain that one, and only one, case will be executed. Reading from top down until the proper condition is met tells us which one.

In loops, use continue instead of indenting the subsequent code with one more tab.

This method is a heavy weapon in the war against over-indenting:

*** BAD code: ***

Code: Select all
while ([loop condition]) {
   if ([condition 1])  {
      if ([condition 2]) {
         if [condition 3]) {
            [code fragment with its own indenting levels]
         }
      }
   }
}

*** GOOD code: ***

Code: Select all
while ([loop condition]) {
   if (![condition 1]) continue;
   if (![condition 2]) continue;
   if (![condition 3]) continue;
   [code fragment with its own indenting levels]
}

If one long line is broken to a few lines, or a complicated expression is written in many lines, the indentation is defined by the first line.

It's a good idea to use a lot of tabs inside a single expression for the sake of better readability - indentation inside the expression is not counted. We are talking about a single expression - not about a fragment which is made up of a few expressions!

The following example is taken from my real Kotlin program (I only added comments). At first glance it seems that I used crazy levels of indentation. But if you look carefully, you will see that all the expressions have zero indentation!

Code: Select all
    private fun retrieveStats(rowsLimit: String): Stats { // ##################### 1st tab is not counted - it indents functions inside class
        val sqlSelect = "SELECT " + // ##################### 2nd tab is not counted too - it indents code inside function; so, this expression has the indentation of zero!!!
            // #####################  All subsequent tabs are not counted too - they are inside an expression; their goal is to make code more readable:
            "ROUND(AVG((STRFTIME('%s', ${DbColumn.BETWEEN_MEALS_START}) - STRFTIME('%s', ${DbColumn.MEAL_1_START})) / 60)) AS ${DbColumn.AVG_MEAL_1}, " +
            "ROUND(AVG((STRFTIME('%s', ${DbColumn.MEAL_2_START}) - STRFTIME('%s', ${DbColumn.BETWEEN_MEALS_START})) / 60)) AS ${DbColumn.AVG_BETWEEN_MEALS}, " +
            "ROUND(AVG((STRFTIME('%s', ${DbColumn.FASTING_START}) - STRFTIME('%s', ${DbColumn.MEAL_2_START})) / 60)) AS ${DbColumn.AVG_MEAL_2}, " +
            "ROUND(AVG((STRFTIME('%s', ${DbColumn.FASTING_START}) - STRFTIME('%s', ${DbColumn.MEAL_1_START})) / 60)) AS ${DbColumn.AVG_EW}, " +
            "COUNT(1) AS ${DbColumn.MEAL_1_COUNT}, " + // can also be used as total cycles count
            "(" +
                "SELECT COUNT(1) " +
                  "FROM ${DbTable.CYCLE} " +
                 "WHERE ${DbColumn.ID} IN (SELECT ${DbColumn.ID} " + // limit by the same condition as the overall query...
                                            "FROM ${DbTable.CYCLE} " +
                                           "WHERE ${DbColumn.FASTING_START} IS NOT NULL " +
                                        "ORDER BY ${DbColumn.ID} DESC " +
                                           "LIMIT $rowsLimit) " +
                   "AND ${DbColumn.ID} IN (SELECT ${DbColumn.ID} " + // ...and exclude OMADs from that population
                                            "FROM ${DbTable.CYCLE} " +
                                           "WHERE ${DbColumn.MEAL_2_START} IS NOT NULL)" +
            ") AS ${DbColumn.MEAL_2_COUNT} " +
            "FROM ${DbTable.CYCLE} " +
            "WHERE ${DbColumn.ID} IN (SELECT ${DbColumn.ID} " +
                                       "FROM ${DbTable.CYCLE} " +
                                      "WHERE ${DbColumn.FASTING_START} IS NOT NULL " +
                                   "ORDER BY ${DbColumn.ID} DESC " +
                                      "LIMIT $rowsLimit)"

        val s = crudHelper.retrieveOne<Stats>(sqlSelect, required = true)!!

        s.avgMeal = (
                        (s.avgMeal1!! * s.meal1Count!! + s.avgMeal2!! * s.meal2Count!!).toDouble()
                        /
                        (s.meal1Count!! + s.meal2Count!!).toDouble()
                    ).roundToInt()
        s.omadsCount = s.meal1Count!! - s.meal2Count!!
        s.omadsPct = (s.omadsCount!!.toDouble() / s.meal1Count!!.toDouble() * 100).roundToInt()

        return s
    }
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33



Ketones are a more high-octane fuel for your brain than glucose. Become a biohacker and upgrade yourself to version 2.0!



cron
Traffic Counter

eXTReMe Tracker