Chapter 15. Using Conditionals

cc2e.com/1538

Contents

Related Topics

A conditional is a statement that controls the execution of other statements; execution of the other statements is "conditioned" on statements such as if, else, case, and switch. Although it makes sense logically to refer to loop controls such as while and for as conditionals too, by convention they've been treated separately. Chapter 16, will examine while and for statements.

if Statements

Depending on the language you're using, you might be able to use any of several kinds of if statements. The simplest is the plain if or if-then statement. The if-then-else is a little more complex, and chains of if-then-else-if are the most complex.

Plain if-then Statements

Follow these guidelines when writing if statements:

Plain if-then Statements

Write the nominal path through the code first; then write the unusual cases. Write your code so that the normal path through the code is clear. Make sure that the rare cases don't obscure the normal path of execution. This is important for both readability and performance.

Make sure that you branch correctly on equality. Using > instead of >= or < instead of <= is analogous to making an off-by-one error in accessing an array or computing a loop index. In a loop, think through the endpoints to avoid an off-by-one error. In a conditional statement, think through the equals case to avoid one.

Put the normal case after the if rather than after the else. Put the case you normally expect to process first. This is in line with the general principle of putting code that results from a decision as close as possible to the decision. Here's a code example that does a lot of error processing, haphazardly checking for errors along the way:

Cross-Reference

For other ways to handle error-processing code, see "Summary of Techniques for Reducing Deep Nesting" in Taming Dangerously Deep Nesting.

Example 15-1. Visual Basic Example of Code That Processes a Lot of Errors Haphazardly

OpenFile( inputFile, status )
If ( status = Status_Error ) Then
   errorType = FileOpenError       <-- 1
Else
   ReadFile( inputFile, fileData, status )       <-- 2
   If ( status = Status_Success ) Then
      SummarizeFileData( fileData, summaryData, status )       <-- 3
      If ( status = Status_Error ) Then
         errorType = ErrorType_DataSummaryError       <-- 4
      Else
         PrintSummary( summaryData )       <-- 5
         SaveSummaryData( summaryData, status )
         If ( status = Status_Error ) Then
            errorType = ErrorType_SummarySaveError       <-- 6
         Else
            UpdateAllAccounts()       <-- 7
            EraseUndoFile()
            errorType = ErrorType_None
         End If
      End If
   Else
      errorType = ErrorType_FileReadError
   End If
End If

(1)Error case.

(2)Nominal case.

(3)Nominal case.

(4)Error case.

(5)Nominal case.

(6)Error case

(7)Nominal case.

This code is hard to follow because the nominal cases and the error cases are all mixed together. It's hard to find the path that is normally taken through the code. In addition, because the error conditions are sometimes processed in the if clause rather than the else clause, it's hard to figure out which if test the normal case goes with. In the following rewritten code, the normal path is consistently coded first and all the error cases are coded last. This makes it easier to find and read the nominal case.

Example 15-2. Visual Basic Example of Code That Processes a Lot of Errors Systematically

OpenFile( inputFile, status )
If ( status = Status_Success ) Then
   ReadFile( inputFile, fileData, status )       <-- 1
   If ( status = Status_Success ) Then
      SummarizeFileData( fileData, summaryData, status )       <-- 2
      If ( status = Status_Success ) Then
         PrintSummary( summaryData )       <-- 3
         SaveSummaryData( summaryData, status )
         If ( status = Status_Success ) Then
            UpdateAllAccounts()       <-- 4
            EraseUndoFile()
            errorType = ErrorType_None
         Else
            errorType = ErrorType_SummarySaveError       <-- 5
         End If
      Else
         errorType = ErrorType_DataSummaryError       <-- 6
      End If
   Else
      errorType = ErrorType_FileReadError       <-- 7
   End If
Else
   errorType = ErrorType_FileOpenError       <-- 8
End If

(1)Nominal case.

(2)Nominal case.

(3)Nominal case.

(4)Nominal case.

(5)Error case.

(6)Error case.

(7)Error case.

(8)Error case.

In the revised example, you can read the main flow of the if tests to find the normal case. The revision puts the focus on reading the main flow rather than on wading through the exceptional cases, so the code is easier to read overall. The stack of error conditions at the bottom of the nest is a sign of well-written error-processing code.

This example illustrates one systematic approach to handling normal cases and error cases. A variety of other solutions to this problem are discussed throughout this book, including using guard clauses, converting to polymorphic dispatch, and extracting the inner part of the test into a separate routine. For a complete list of available approaches, see "Summary of Techniques for Reducing Deep Nesting" in Taming Dangerously Deep Nesting.

Follow the if clause with a meaningful statement. Sometimes you see code like the next example, in which the if clause is null:

Most experienced programmers would avoid code like this if only to avoid the work of coding the extra null line and the else line. It looks silly and is easily improved by negating the predicate in the if statement, moving the code from the else clause to the if clause, and eliminating the else clause. Here's how the code would look after those changes:

Cross-Reference

One key to constructing an effective if statement is writing the right boolean expression to control it. For details on using boolean expressions effectively, see Boolean Expressions.

Example 15-4. Java Example of a Converted Null if Clause

if ( ! someTest ) {
   // do something
   ...
}

Consider the else clause. If you think you need a plain if statement, consider whether you don't actually need an if-then-else statement. A classic General Motors analysis found that 50 to 80 percent of if statements should have had an else clause (Elshoff 1976).

One option is to code the else clause—with a null statement if necessary—to show that the else case has been considered. Coding null elses just to show that that case has been considered might be overkill, but at the very least, take the else case into account. When you have an if test without an else, unless the reason is obvious, use comments to explain why the else clause isn't necessary, like so:

Example 15-5. Java Example of a Helpful, Commented else Clause

// if color is valid
if ( COLOR_MIN <= color && color <= COLOR_MAX ) {
   // do something
   ...
}
else {
   // else color is invalid
   // screen not written to --- safely ignore command
}

Test the else clause for correctness. When testing your code, you might think that the main clause, the if, is all that needs to be tested. If it's possible to test the else clause, however, be sure to do that.

Check for reversal of the if and else clauses. A common mistake in programming if-thens is to flip-flop the code that's supposed to follow the if clause and the code that's supposed to follow the else clause or to get the logic of the if test backward. Check your code for this common error.

Chains of if-then-else Statements

In languages that don't support case statements—or that support them only partially— you'll often find yourself writing chains of if-then-else tests. For example, the code to categorize a character might use a chain like this one:

Example 15-6. C++ Example of Using an if-then-else Chain to Categorize a Character

if ( inputCharacter < SPACE ) {
   characterType = CharacterType_ControlCharacter;
}
else if (
   inputCharacter == ' ' ||
   inputCharacter == ',' ||
   inputCharacter == '.' ||
   inputCharacter == '!' ||
   inputCharacter == '(' ||
   inputCharacter == ')' ||
   inputCharacter == ':' ||
   inputCharacter == ';' ||
   inputCharacter == '?' ||
   inputCharacter == '-'
   ) {
   characterType = CharacterType_Punctuation;
}
else if ( '0' <= inputCharacter && inputCharacter <= '9' ) {
   characterType = CharacterType_Digit;
}
else if (
   ( 'a' <= inputCharacter && inputCharacter <= 'z' ) ||
   ( 'A' <= inputCharacter && inputCharacter <= 'Z' )
   ) {
   characterType = CharacterType_Letter;
}

Cross-Reference

For more details on simplifying complicated expressions, see Boolean Expressions.

Consider these guidelines when writing such if-then-else chains:

Simplify complicated tests with boolean function calls. One reason the code in the previous example is hard to read is that the tests that categorize the character are complicated. To improve readability, you can replace them with calls to boolean functions. Here's how the example's code looks when the tests are replaced with boolean functions:

Example 15-7. C++ Example of an if-then-else Chain That Uses Boolean Function Calls

if ( IsControl( inputCharacter ) ) {
   characterType = CharacterType_ControlCharacter;
}
else if ( IsPunctuation( inputCharacter ) ) {
   characterType = CharacterType_Punctuation;
}
else if ( IsDigit( inputCharacter ) ) {
   characterType = CharacterType_Digit;
}
else if ( IsLetter( inputCharacter ) ) {
   characterType = CharacterType_Letter;
}

Put the most common cases first. By putting the most common cases first, you minimize the amount of exception-case handling code someone has to read to find the usual cases. You improve efficiency because you minimize the number of tests the code does to find the most common cases. In the example just shown, letters would be more common than punctuation but the test for punctuation is made first. Here's the code revised so that it tests for letters first:

Example 15-8. C++ Example of Testing the Most Common Case First

if ( IsLetter( inputCharacter ) ) {       <-- 1
   characterType = CharacterType_Letter;
}
else if ( IsPunctuation( inputCharacter ) ) {
   characterType = CharacterType_Punctuation;
}
else if ( IsDigit( inputCharacter ) ) {
   characterType = CharacterType_Digit;
}
else if ( IsControl( inputCharacter ) ) {       <-- 2
   characterType = CharacterType_ControlCharacter;
}

(1)This test, the most common, is now done first.

(2)This test, the least common, is now done last.

Make sure that all cases are covered. Code a final else clause with an error message or assertion to catch cases you didn't plan for. This error message is intended for you rather than for the user, so word it appropriately. Here's how you can modify the character-classification example to perform an "other cases" test:

Example 15-9. C++ Example of Using the Default Case to Trap Errors

if ( IsLetter( inputCharacter ) ) {
   characterType = CharacterType_Letter;
}
else if ( IsPunctuation( inputCharacter ) ) {
   characterType = CharacterType_Punctuation;
}
else if ( IsDigit( inputCharacter ) ) {
   characterType = CharacterType_Digit;
}
else if ( IsControl( inputCharacter ) ) {
   characterType = CharacterType_ControlCharacter;
}
else {
   DisplayInternalError( "Unexpected type of character detected." );
}

Cross-Reference

This is also a good example of how you can use a chain of if-then-else tests instead of deeply nested code. For details on this technique, see Taming Dangerously Deep Nesting.

Replace if-then-else chains with other constructs if your language supports them. A few languages—Microsoft Visual Basic and Ada, for example—provide case statements that support use of strings, enums, and logical functions. Use them—they are easier to code and easier to read than if-then-else chains. Code for classifying character types by using a case statement in Visual Basic would be written like this:

Example 15-10. Visual Basic Example of Using a case Statement Instead of an if-then-else Chain

Select Case inputCharacter
   Case "a" To "z"
      characterType = CharacterType_Letter
   Case " ", ",", ".", "!", "(", ")", ":", ";", "?", "-"
      characterType = CharacterType_Punctuation
   Case "0" To "9"
      characterType = CharacterType_Digit
   Case FIRST_CONTROL_CHARACTER To LAST_CONTROL_CHARACTER
      characterType = CharacterType_Control
   Case Else
      DisplayInternalError( "Unexpected type of character detected." )
End Select
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.11