Careless or convoluted use of statements can make a program hard to read and maintain even if its global structure is well organized. You should strive for simple and consistent use of statements to achieve clarity of local program structure. Some of the guidelines in this section counsel use or avoidance of particular statements. As pointed out in the individual guidelines, rigid adherence to those guidelines would be excessive, but experience has shown that they generally lead to code with improved reliability and maintainability.
- Minimize the depth of nested expressions (Nissen and Wallis 1984 ).
- Minimize the depth of nested control structures (Nissen and Wallis 1984 ).
- Try using simplification heuristics (see the following Notes ).
- Do not nest expressions or control structures beyond a nesting level of five.
The following section of code:
if not Condition_1 then
if Condition_2 then
else -- not Condition_2
else -- Condition_1
can be rewritten more clearly and with less nesting as:
if Condition_1 then
elsif Condition_2 then
else -- not (Condition_1 or Condition_2)
Deeply nested structures are confusing, difficult to understand, and
difficult to maintain. The problem lies in the difficulty of determining
what part of a program is contained at any given level. For expressions,
this is important in achieving the correct placement of balanced
grouping symbols and in achieving the desired operator precedence. For
control structures, the question involves what part is controlled.
Specifically, is a given statement at the proper level of nesting, that
is, is it too deeply or too shallowly nested, or is the given statement
associated with the proper choice, for example, for
statements? Indentation helps, but it is not a panacea. Visually
inspecting alignment of indented code (mainly intermediate levels) is an
uncertain job at best. To minimize the complexity of the code, keep the
maximum number of nesting levels between three and five.
Ask yourself the following questions to help you simplify the code:
- Can some part of the expression be put into a constant or variable?
- Does some part of the lower nested control structures represent a significant and, perhaps, reusable computation that I can factor into a subprogram ?
- Can I convert these nested
ifstatements into a
- Am I using
else ifwhere I could be using
- Can I reorder the conditional expressions controlling this nested structure?
- Is there a different design that would be simpler?
If deep nesting is required frequently, there may be overall design decisions for the code that should be changed. Some algorithms require deeply nested loops and segments controlled by conditional branches. Their continued use can be ascribed to their efficiency, familiarity, and time-proven utility. When nesting is required, proceed cautiously and take special care with the choice of identifiers and loop and block names.
- Use slices rather than a loop to copy part of an array.
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
Markov_Probabilities(I)(Index'Pred(I) .. Index'Succ(I));
Diagonal_Data(Index'Last)(First .. Second) :=
Markov_Probabilities(Index'Last)(Index'Pred(Index'Last) .. Index'Last);
Diagonal_Data(Index'Last)(Third) := Null_Value;
An assignment statement with slices is simpler and clearer than a loop and helps the reader see the intended action. See also Guideline 10.5.7 regarding possible performance issues of slice assignments versus loops.
- Minimize the use of an
otherschoice in a
- Do not use ranges of enumeration literals in
casestatements rather than
if/elsifstatements, wherever possible.
- Use type extension and dispatching rather than
casestatements if, possible.
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
statement. If the choices had been enumerated explicitly, as
when Red | Green | Blue => instead of
Red .. Blue =>, then the
statement would not have compiled. This would have forced the maintainer
to make a conscious decision about what to do in the case of
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
handles errors in user input. The less readable alternative with the
if/elsif statement is shown after the
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
end loop Do_Menu_Choices_1;
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
end loop Do_Menu_Choices_2;
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
when the subtype of the case expression has many values, for example,
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
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
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 .
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 ..
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;
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'.
forloops, whenever possible.
whileloops when the number of iterations cannot be calculated before entering the loop but a simple continuation condition can be applied at the top of the loop.
- Use plain loops with
exitstatements for more complex situations.
- Minimize the number of ways to
To iterate over all elements of an array:
for I in Array_Name'Range loop
To iterate over all elements in a linked list:
Pointer := Head_Of_List;
while Pointer /= null loop
Pointer := Pointer.Next;
Situations requiring a "loop and a half" arise often. For this, use:
exit P_And_Q_Processing when Condition_Dependent_On_P;
end loop P_And_Q_Processing;
while not Condition_Dependent_On_P loop
for loop is bounded, so it cannot be an "infinite loop." This is
enforced by the Ada language, which requires a finite range in the loop
specification and does not allow the loop counter of a
forloop to be
modified by a statement executed within the loop. This yields a
certainty of understanding for the reader and the writer not associated
with other forms of loops. A
for loop is also easier to maintain
because the iteration range can be expressed using attributes of the
data structures upon which the loop operates, as shown in the example
above where the range changes automatically whenever the declaration of
the array is modified. For these reasons, it is best to use the
loop whenever possible, that is, whenever simple expressions can be used
to describe the first and last values of the loop counter.
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
for loop but when there is a simple Boolean expression describing
the conditions under which the loop should continue, as shown in the
The plain loop statement should be used in more complex situations, even
if it is possible to contrive a solution using a
in conjunction with extra flag variables or
exit statements. The
criteria in selecting a loop construct are to be as clear and
maintainable as possible. It is a bad idea to use an
from within a
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
There are some familiar looping situations that are best achieved with
the plain loop statement. For example, the semantics of the Pascal
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
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 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
statements for all of them, rather than adding an
exit statement to a
exitstatements to enhance the readability of loop termination code (NASA 1987).
exit when ...rather than
if ... then exitwhenever possible (NASA 1987).
See the examples in Guidelines 5.1.1 and Guidelines 5.6.4.
It is more readable to use
exit statements than to try to add Boolean
flags to a
while loop condition to simulate exits from the middle of a
loop. Even if all
exit statements would be clustered at the top of the
loop body, the separation of a complex condition into multiple
statements can simplify and make it more readable and clear. The
sequential execution of two
exit statements is often more clear than
the short-circuit control forms.
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 construct. The
if ... then exit form is needed only in
the case where other statements, in addition to the
must be executed conditionally. For example:
if Status = Done then
end loop Process_Requests;
Loops with many scattered
exit statements can indicate fuzzy thinking
regarding the loop's purpose in the algorithm. Such an algorithm might
be coded better some other way, for example, with a series of loops.
Some rework can often reduce the number of
exit statements and make
the code clearer.
See also Guidelines 5.1.3 and 5.6.4.
Recursion and Iteration Bounds
- Consider specifying bounds on loops .
- Consider specifying bounds on recursion .
Establishing an iteration bound:
Safety_Counter := 0;
exit when Current_Item = null;
Current_Item := Current_Item.Next;
Safety_Counter := Safety_Counter + 1;
if Safety_Counter > 1_000_000 then
end loop Process_List;
Establishing a recursion bound:
subtype Recursion_Bound is Natural range 0 .. 1_000;
procedure Depth_First (Root : in Tree;
Safety_Counter : in Recursion_Bound
:= Recursion_Bound'Last) is
if Root /= null then
if Safety_Counter = 0 then
Depth_First (Root => Root.Left_Branch,
Safety_Counter => Safety_Counter - 1);
Depth_First (Root => Root.Right_Branch,
Safety_Counter => Safety_Counter - 1);
... -- normal subprogram body
Following are examples of this subprogram's usage. One call specifies a maximum recursion depth of 50. The second takes the default (1,000). The third uses a computed bound:
Depth_First(Root => Tree_1, Safety_Counter => 50);
Depth_First(Root => Tree_3, Safety_Counter => Current_Tree_Height);
Recursion, and iteration using structures other than
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.
If a loop uses the
for iteration scheme (Guideline 5.6.4), it follows
Embedded control applications have loops that are intended to be infinite. Only a few loops within such applications should qualify as exceptions to this guideline. The exceptions should be deliberate (and documented ) policy decisions.
This guideline is most important to safety critical systems. For other systems, it may be overkill.
Do not use
goto statement is an unstructured change in the control flow. Worse,
the label does not require an indicator of where the corresponding
goto statement(s) are. This makes code unreadable and makes its
correct execution suspect.
Other languages use
goto statements to implement loop exits and
exception handling. Ada's support of these constructs makes the
statement extremely rare.
If you should ever use a
goto statement, highlight both it and the
label with blank space. Indicate at the label where the corresponding
goto statement(s) may be found.
- Minimize the number of
returnstatementsfrom a subprogram (NASA 1987).
returnstatements with comments or white space to keep them from being lost in other code.
The following code fragment is longer and more complex than necessary:
if Pointer /= null then
if Pointer.Count > 0 then
else -- Pointer.Count = 0
else -- Pointer = null
It should be replaced with the shorter, more concise, and clearer equivalent line:
return Pointer /= null and then Pointer.Count > 0;
Excessive use of returns can make code confusing and unreadable. Only
return statements where warranted. Too many returns from a
subprogram may be an indicator of cluttered logic. If the application
requires multiple returns, use them at the same level (i.e., as in
different branches of a
case statement), rather than scattered
throughout the subprogram code. Some rework can often reduce the number
return statements to one and make the code more clear.
Do not avoid
return statements if it detracts from natural structure
and code readability.
- Use blocks to localize the scope of declarations.
- Use blocks to perform local renaming.
- Use blocks to define local exception handlers.
function Maximum_Velocity return Motion.Velocity is
Cumulative : Motion.Velocity := 0.0;
begin -- Maximum_Velocity
-- Initialize the needed devices
use type Motion.Acceleration;
Current : Motion.Acceleration := 0.0;
Time_Delta : Duration;
begin -- Calculate_Velocity_From_Sample_Data
for I in 1 .. Accelerometer_Device.Sample_Limit loop
when Constraint_Error =>
null; -- Continue trying
when Accelerometer_Device.Failure =>
exit when Current <= 0.0; -- Slowing down
use type Motion.Velocity;
use type Motion.Acceleration;
Cumulative := Cumulative + Current * Time_Delta;
when Constraint_Error =>
Blocks break up large segments of code and isolate details relevant to each subsection of code. Variables that are only used in a particular section of code are clearly visible when a declarative block delineates that code.
Renaming may simplify the expression of algorithms and enhance
readability for a given section of code. But it is confusing when a
renames 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 that uses that abbreviation.
Guideline 5.7.1 discusses a similar guideline concerning the
Local exception handlers can catch exceptions close to the point of origin and allow them to be either handled, propagated, or converted.
- Use an aggregate instead of a sequence of assignments to assign values to all components of a record.
- Use an aggregate instead of a temporary variable when building a record to pass as an actual parameter.
- Use positional association only when there is a conventional ordering of the arguments.
It is better to use aggregates:
Employee_Record := (Number => 42,
Age => 51,
Department => Software_Engineering);
than to use consecutive assignments or temporary variables:
Temporary_Position.X := 100;
Temporary_Position.Y := 200;
Employee_Record.Number := 42;
Employee_Record.Age := 51;
Employee_Record.Department := Software_Engineering;
Using aggregates during maintenance is beneficial. If a record structure is altered, but the corresponding aggregate is not, the compiler flags the missing field in the aggregate assignment. It would not be able to detect the fact that a new assignment statement should have been added to a list of assignment statements.
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.
See Guideline 10.4.5 for the performance impact of aggregates.