- Make each clause independent, and remove the "else" keyword.
Even when the conditionals in a chained if-else construct are all testing the same dimension, the clauses may not be mutually exclusive. This causes the reader to bear in mind the negation of all of the preceding clauses. It also makes the code fragile with respect to the introduction of a new clause. These are actually two views of the same problem -- action at a distance -- which should generally be avoided in robust programming.
Let's look at a simple snippet that tests whether an variable is valid decimal digit:
int digit = ... ;Now suppose we want to do something special when the digit is exactly zero. So we add an "else if" clause after the "if" clause:
if (digit < 0) printf("Your digit is too small.\n");
else if (digit > 9) print("Your digit is too large.\n");
else printf("You entered a valid digit.\n");
int digit = ... ;
if (digit < 0) printf("Your digit is too small.\n");
else if (digit == 0) printf("You entered zero.\n");
else if (digit > 9) print("Your digit is too large.\n");
else printf("You entered a valid digit.\n");
The problem is that introduction of the new clause has eroded the subspace captured by the "else" clause. We really want "You entered a valid digit.\n" to be printed even when the digit is zero.
This may not seem like much of a problem in this toy example. The programmer making the change can easily scan forward and see that the "else" clause will now capture only {1..9}. He can then verify whether that is the intended behavior. However, if the chained if ... else contains many clauses and spans several pages, the programmer may not see that his change will introduce a bug. How can this be avoided? By removing the "else" keywords.
We started out with two clauses that were mutually exclusive, but the third is dependent on the other two. The first step is to rewrite the construct so that all of the clauses are mutually exclusive:
int digit = ... ;Now that the clauses are mutually exclusive, the "else" keywords can be removed without changing the behavior of the code snippet:
if (digit < 0) printf("Your digit is too small.\n");
else if (digit > 9) print("Your digit is too large.\n");
else if (digit >= 0 && digit <=9) printf("You entered a valid digit.\n");
int digit = ... ;Observe that it is no longer important whether the clauses are mutually exclusive or not. We have converted a dependent form (where the last clause executes only if the preceding two do not, etc.) into an imperative form. Whenever the condition of the first clause is true, its body executes; whenever the condition on the second clause is true, its body executes, etc.
if (digit < 0) printf("Your digit is too small.\n");
if (digit > 9) print("Your digit is too large.\n");
if (digit >= 0 && digit <=9) printf("You entered a valid digit.\n");
This kind of imperative programming is very useful in writing robust code, because the behavior of the snippet no longer depends on the order of the clauses, and is not disrupted by the addition of another clause. Now, adding the new clause in the same style obtains the expected behavior:
int digit = ... ;
if (digit < 0) printf("Your digit is too small.\n");
if (digit == 0) printf("You entered zero.\n");
if (digit > 9) print("Your digit is too large.\n");
if (digit >= 0 && digit <=9) printf("You entered a valid digit.\n");
Someone will immediately point out that the tests in the third clause are redundant with the tests in the first two. Imagine how much processing time will be wasted! To be concerned about that is an example of early optimization. In general the benefits of writing imperative code outweigh any runtime efficiency consideration. And where optimization is required, the imperative style actually helps to achieve that goal. That will be the topic of the next installment.
Although it can help with maintainability, this style of coding isn't always ideal. For example, it may still be necessary to use a chained if-else construct where you want to capture an unhandled case and flag an error. I also use simple if-else statements (containing no "else if" clauses) quite a bit, because forming the negation of the conditional of the "if" statement would be verbose and error-prone.
No comments:
Post a Comment