In this section...
5.6.3 Case Statements
5.6.5 Exit Statements
5.6.6 Recursion and Iteration Bounds
5.6.7 Goto Statements
5.6.8 Return Statements
|Summary of Guidelines from this section|
Language Ref Manual references: 5 Statements
if not Condition_1 then if Condition_2 then Action_A; else -- not Condition_2 Action_B; end if; else -- Condition_1 Action_C; end if;
can be rewritten more clearly and with less nesting as:
if Condition_1 then Action_C; elsif Condition_2 then Action_A; else -- not (Condition_1 or Condition_2) Action_B; end if;
else ifwhere I could be using
Language Ref Manual references: 5.1 Simple and Compound Statements - Sequences of Statements, 5.3 If Statements, 5.4 Case Statements, 5.5 Loop Statements, 5.6 Block Statements, 9.5 Entries, Entry Calls, and Accept Statements, 9.7 Select Statements
First : constant Index := Index'First; Second : constant Index := Index'Succ(First); Third : constant Index := Index'Succ(Second); type Vector is array (Index range <>) of Element; subtype Column_Vector is Vector (Index); type Square_Matrix is array (Index) of Column_Vector; subtype Small_Range is Index range First .. Third; subtype Diagonals is Vector (Small_Range); type Tri_Diagonal is array (Index) of Diagonals; Markov_Probabilities : Square_Matrix; Diagonal_Data : Tri_Diagonal; ... -- Remove diagonal and off diagonal elements. Diagonal_Data(Index'First)(First) := Null_Value; Diagonal_Data(Index'First)(Second .. Third) := Markov_Probabilities(Index'First)(First .. Second); for I in Second .. Index'Pred(Index'Last) loop Diagonal_Data(I) := Markov_Probabilities(I)(Index'Pred(I) .. Index'Succ(I)); end loop; Diagonal_Data(Index'Last)(First .. Second) := Markov_Probabilities(Index'Last) (Index'Pred(Index'Last) .. Index'Last); Diagonal_Data(Index'Last)(Third) := Null_Value;
Language Ref Manual references: 3.6.2 Operations of Array Types, 4.1.2 Slices, 5.2 Assignment Statement, 5.5 Loop Statements
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 =>
when Red .. Blue =>, then the case statement would have not have
compiled. This would have forced the maintainer to make a conscious decision
about what to do in the case of
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.
'a' .. 'z'.
subtype Lower_Case is Character range 'a' .. 'z'; subtype Upper_Case is Character range 'A' .. 'Z'; subtype Control is Character range ASCII.Nul .. ASCII.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;
for 1 in Array_Name'Range loop ... end loop;
Pointer := Head_Of_List; while Pointer /= null loop ... Pointer := Pointer.Next; end loop;
Situations requiring a "loop and a half" arise often. For this use:
P_And_Q_Processing: loop P; exit P_And_Q_Processing when Condition_Dependent_On_P; Q; end loop P_And_Q_Processing;
P; while not Condition_Dependent_On_P loop Q; P; end loop;
The while loop has become a very familiar construct to most programmers. At a glance it indicates the condition under which the loop continues. Use the while loop whenever it is not possible to use the for loop, but there is a simple boolean expression describing the conditions under which the loop should continue, as shown in the example above.
The plain loop statement should be used in more complex situations, even if it is possible to contrive a solution using a for or while loop in conjunction with extra flag variables or exit statements. The criteria in selecting a loop construct is to be as clear and maintainable as possible. It is a bad idea to use an exit statement from within a for or while loop because it is misleading to the reader after having apparently described the complete set of loop conditions at the top of the loop. A reader who encounters a plain loop statement expects to see exit statements.
There are some familiar looping situations which are best achieved with the plain loop statement. For example, the semantics of the Pascal repeat until loop, where the loop is always executed at least once before the termination test occurs, are best achieved by a plain loop with a single exit at the end of the loop. Another common situation is the "loop and a half" construct, shown in the example above, where a loop must terminate somewhere within the sequence of statements of the body. Complicated "loop and a half" constructs simulated with while loops often require the introduction of flag variables, or duplication of code before and during the loop, as shown in the example. Such contortions make the code more complex and less reliable.
Minimize the number of ways to exit a loop in order to make the loop more understandable to the reader. It should be rare that you need more than two exit paths from a loop. When you do, be sure to use exit statements for all of them, rather than adding an exit statement to a for or while loop.
Language Ref Manual references: 5.5 Loop Statements, 5.7 Exit Statements
exit when ...rather than
if ... then exitwhenever possible (NASA 1987).
exit when form is preferable to the
if ... then, exit form because it
makes the word
exit more visible by not nesting it inside of any control
if ... then exit form is needed only in the case where other
statements, in addition to the exit statement, must be executed conditionally.
if Status = Done then Shut_Down; return; end if;
See also Guidelines 5.1.3 and 5.6.4.
Language Ref Manual references: 5.3 If Statements, 5.7 Exit Statements
Safety_Counter := 0; Process_List: loop exit when Current_Item = null; ... Current_Item := Current_Item.Next; ... Safety_Counter := Safety_Counter + 1; if Safety_Counter > 1_000_000 then raise Safety_Error; end if; end loop Process_List;
procedure Depth_First (Root : in Tree; Safety_Counter : in Recursion_Bound := Recursion_Bound'Last) is begin if Root /= null then if Safety_Counter = 0 then raise Recursion_Error; end if; Depth_First (Root.Left_Branch, Safety_Counter - 1); -- recursive call Depth_First (Root.Right_Branch, Safety_Counter - 1); -- recursive call ... -- normal subprogram body end if; end Depth_First;
Depth_First(Root, 50); Depth_First(Root); Depth_First(Root, Current_Tree_Height);
forstatements, can be infinite because the expected terminating condition does not arise. Such faults are sometimes quite subtle, may occur rarely, and may be difficult to detect because an external manifestation might be absent or substantially delayed.
By including counters and checks on the counter values, in addition to the loops themselves, you can prevent many forms of infinite loops. The inclusion of such checks is one aspect of the technique called Safe Programming (Anderson and Witty 1978).
The bounds of these checks do not have to be exact, just realistic. Such counters and checks are not part of the primary control structure of the program but a benign addition functioning as an execution-time "safety net" allowing error detection and possibly recovery from potential infinite loops or infinite recursion.
foriteration scheme (Guideline 5.6.4), it follows this guideline.
This guideline is most important to safety critical systems. For other systems, it may be overkill.
Language Ref Manual references: 5.5 Loop Statements, 6.1 Subprogram Declarations, 6.4 Subprogram Calls
Other languages use goto statements to implement loop exits and exception handling. Ada's support of these constructs makes the goto statement extremely rare.
Language Ref Manual references: 2.7 Comments, 5.9 Goto Statements
if Pointer /= null then if Pointer.Count > 0 then return True; else -- Pointer.Count = 0 return False; end if; else -- Pointer = null return False; end if;
return Pointer /= null and then Pointer.Count > 0;
Language Ref Manual references: 2.7 Comments, 5.8 Return Statements
with Motion; with Accelerometer_Device; ... --------------------------------------------------------------------- function Maximum_Velocity return Motion.Velocity is Cumulative : Motion.Velocity := 0.0; begin -- Maximum_Velocity -- Initialize the needed devices ... Calculate_Velocity_From_Sample_Data: declare Current : Motion.Acceleration := 0.0; Accelerometer : Accelerometer_Device.Interface; Time_Delta : Duration; begin -- Calculate_Velocity_From_Sample_Data for I in 1 .. Accelerometer_Device.Sample_Limit loop Get_Samples_And_Ignore_Invalid_Data: begin Accelerometer.Value(Current, Time_Delta); exception when Numeric_Error | Constraint_Error => null; -- Continue trying when Accelerometer_Device.Failure => raise Accelerometer_Device_Failed; end Get_Samples_And_Ignore_Invalid_Data; exit when Motion."<"(Current, 0.0); -- Slowing down Update_Velocity: declare use Motion; -- for infix operators and exceptions; begin Cumulative := Cumulative + Current * Time_Delta; exception when Numeric_Error | Constraint_Error => raise Maximum_Velocity_Exceeded; end Update_Velocity; end loop; end Calculate_Velocity_From_Sample_Data; return Cumulative; end Maximum_Velocity; --------------------------------------------------------------------- ...
Renaming may simplify the expression of algorithms and enhance readability for
a given section of code. But it is confusing when a
rename clause is visually
separated from the code to which it applies. The declarative region allows
the renames to be immediately visible when the reader is examining code which
uses that abbreviation. Guideline 5.7.1 discusses a similar guideline
concerning the `use' clause.
Local exception handlers can catch exceptions close to the point of origin and allow them to either be handled, propagated, or converted.
Language Ref Manual references: 5.6 Block Statements, 8.4 Use Clauses, 8.5 Renaming Declarations, 11.2 Exception Handlers
Set_Position((X, Y)); Employee_Record := (Number => 42, Age => 51, Department => Software_Engineering);
Temporary_Position.X := 100; Temporary_Position.Y := 200; Set_Position(Temporary_Position); Employee_Record.Number := 42; Employee_Record.Age := 51; Employee_Record.Department := Software_Engineering;
Aggregates can also be a real convenience in combining data items into a record or array structure required for passing the information as a parameter. Named component association makes aggregates more readable.
Language Ref Manual references: 4.3.1 Record Aggregates