Ada 95 Quality and Style Guide Chapter 5

Chapter 5: Programming Practices - TOC - 5.6 STATEMENTS

5.6.3 Case Statements

guideline

  • Minimize the use of an others choice in a case statement.
  • Do not use ranges of enumeration literals in case statements.
  • Use case statements rather than if/elsif statements, wherever possible.
  • Use type extension and dispatching rather than case statements if, possible.

  • example
    type Color is (Red, Green, Blue, Purple);
    Car_Color : Color := Red;
    ...
    case Car_Color is
       when Red .. Blue => ...
       when Purple      => ...
    end case;  -- Car_Color
    

    Now consider a change in the type:

    type Color is (Red, Yellow, Green, Blue, Purple);
    

    This change may have an unnoticed and undesired effect in the case statement. If the choices had been enumerated explicitly, as when Red | Green | Blue => instead of when Red .. Blue =>, then the case statement would not have compiled. This would have forced the maintainer to make a conscious decision about what to do in the case of Yellow.

    In the following example, assume that a menu has been posted, and the user is expected to enter one of the four choices. Assume that User_Choice is declared as a Character and that Terminal_IO.Get handles errors in user input. The less readable alternative with the if/elsif statement is shown after the case statement:

    Do_Menu_Choices_1:
       loop
          ...
      
          case User_Choice is
             when 'A'    => Item := Terminal_IO.Get ("Item to add");
             when 'D'    => Item := Terminal_IO.Get ("Item to delete");
             when 'M'    => Item := Terminal_IO.Get ("Item to modify");
             when 'Q'    => exit Do_Menu_Choices_1;
      
             when others => -- error has already been signaled to user
                null;
          end case;
       end loop Do_Menu_Choices_1;
    
    
    Do_Menu_Choices_2:
       loop
          ...
    
          if User_Choice = 'A' then
             Item := Terminal_IO.Get ("Item to add");
    
          elsif User_Choice = 'D' then
             Item := Terminal_IO.Get ("Item to delete");
    
          elsif User_Choice = 'M' then
             Item := Terminal_IO.Get ("Item to modify");
    
          elsif User_Choice = 'Q' then
             exit Do_Menu_Choices_2;
    
          end if;
       end loop Do_Menu_Choices_2;
    

    rationale

    All possible values for an object should be known and should be assigned specific actions. Use of an others clause may prevent the developer from carefully considering the actions for each value. A compiler warns the user about omitted values if an others clause is not used.

    You may not be able to avoid the use of others in a case statement when the subtype of the case expression has many values, for example, universal_integer, Wide_Character, or Character). If your choice of values is small compared to the range of the subtype, you should consider using an if/elsif statement. Note that you must supply an others alternative when your case expression is of a generic type.

    Each possible value should be explicitly enumerated. Ranges can be dangerous because of the possibility that the range could change and the case statement may not be reexamined. If you have declared a subtype to correspond to the range of interest, you can consider using this named subtype.

    In many instances, case statements enhance the readability of the code. See Guideline 10.5.3 for a discussion of the performance considerations. In many implementations, case statements may be more efficient.

    Type extension and dispatching ease the maintenance burden when you add a new variant to a data structure. See also Guidelines 5.4.2 and 5.4.4.

    notes

    Ranges that are needed in case statements can use constrained subtypes to enhance maintainability. It is easier to maintain because the declaration of the range can be placed where it is logically part of the abstraction, not buried in a case statement in the executable code:

    subtype Lower_Case is Character range 'a' .. 'z';
    subtype Upper_Case is Character range 'A' .. 'Z';
    subtype Control    is Character range Ada.Characters.Latin_1.NUL ..
                                          Ada.Characters.Latin_1.US;
    subtype Numbers    is Character range '0' .. '9';
    ...
    case Input_Char is
       when Lower_Case => Capitalize(Input_Char);
       when Upper_Case => null;
       when Control    => raise Invalid_Input;
       when Numbers    => null;
       ...
    end case;
    

    exceptions

    It is acceptable to use ranges for possible values only when the user is certain that new values will never be inserted among the old ones, as for example, in the range of ASCII characters: 'a' .. 'z'.


    < Previous Page Search Contents Index Next Page >
    1 2 3 4 5 6 7 8 9 10 11
    TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC
    Appendix References Bibliography