Ada 95 Quality and Style Guide Chapter 5
Back to Sections 5.0 through 5.6.3
5.6.4 Loops
guideline
- Use for loops, whenever possible.
- Use while loops 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 exit
statements for more complex situations.
- Avoid exit statements in
while and for
loops.
- Minimize the number of ways to exit a loop.
example
rationale
5.6.5 Exit Statements
guideline
- Use exit statements to enhance the readability of loop
termination code (NASA 1987).
- Use exit when ... rather than if ... then exit
whenever possible (NASA 1987).
- Review exit statement placement.
example
rationale
5.6.6 Recursion and Iteration Bounds
guideline
- Consider specifying bounds on loops.
- Consider specifying bounds on recursion.
example
rationale
notes
exceptions
5.6.7 Goto Statements
guideline
- Do not use goto statements.
rationale
notes
5.6.8 Return Statements
guideline
- Minimize the number of return statements from
a subprogram (NASA 1987).
- Highlight return statements with comments
or white space to keep them
from being lost in other code.
example
rationale
exceptions
5.6.9 Blocks
guideline
- Use blocks to localize the scope of
declarations.
- Use blocks to perform local renaming.
- Use blocks to define local exception
handlers.
example
rationale
5.6.10 Aggregates
guideline
- 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.
example
rationale
5.7 VISIBILITY
As noted in Guideline 4.2, Ada's ability to enforce information
hiding and separation of concerns through its visibility controlling
features is one of the most important advantages of the language.
Subverting these features, for example, by too liberal use of
the use clause, is wasteful and dangerous.
5.7.1 The Use Clause
guideline
- When you need to provide visibility to operators, use the use
type clause.
- Avoid/minimize the use of the use clause (Nissen and
Wallis 1984).
- Consider using a package renames clause rather than
a use clause for a package.
- Consider using the use clause in the following situations:
- When standard packages are
needed and no ambiguous
references are introduced
- When references to enumeration
literals are needed
- Localize the effect of all
use clauses.
example
rationale
notes
automation notes
5.7.2 The Renames Clause
guideline
- Limit the scope of a renaming declaration to the minimum necessary
scope.
- Rename a long, fully qualified
name to reduce the complexity if it becomes unwieldy (see
Guideline 3.1.4).
- Use renaming to provide the body of a subprogram if this subprogram
merely calls the first subprogram.
- Rename declarations for visibility
purposes rather than using the use clause,
except for operators (see Guideline 5.7.1).
- Rename parts when your code interfaces to reusable
components originally written with nondescriptive or inapplicable
nomenclature.
- Use a project-wide standard list of abbreviations
to rename common packages.
- Provide a use type rather than a renames clause
to provide visibility to operators.
example
rationale
notes
5.7.3 Overloaded Subprograms
guideline
- Limit overloading to widely used subprograms
that perform similar actions on arguments
of different types (Nissen and Wallis 1984).
example
rationale
notes
5.7.4 Overloaded Operators
guideline
- Preserve the conventional meaning of overloaded operators (Nissen
and Wallis 1984).
- Use "+" to identify adding, joining, increasing,
and enhancing kinds of functions.
- Use "-" to identify subtraction, separation,
decreasing, and depleting kinds of functions.
- Use operator overloading sparingly and uniformly when applied
to tagged types.
example
rationale
notes
5.7.5 Overloading the Equality Operator
guideline
- Define an appropriate equality operator for private
types.
- Consider redefining the equality operator for a private type.
- When overloading the equality operator for types,
maintain the properties of an algebraic equivalence relation.
rationale
- Reflexive: a = a
- Symmetric: a = b ==> b = a
- Transitive: a = b and b = c ==> a = c
In redefining equality, you are not required to have a result
type of Standard.Boolean. The Rationale
(1995, §6.3) gives two examples where your result type is
a user-defined type. In a three-valued logic abstraction, you
redefine equality to return one of True, False,
or Unknown. In a vector processing application, you can
define a component-wise equality operator that returns a vector
of Boolean values. In both these instances, you should also redefine
inequality because it is not the Boolean complement of the equality
function.
5.8 USING EXCEPTIONS
Ada exceptions are a reliability-enhancing language feature designed
to help specify program behavior in the presence of errors or
unexpected events. Exceptions are not intended to provide a general
purpose control construct. Further, liberal use of exceptions
should not be considered sufficient for providing full software
fault tolerance (Melliar-Smith and Randell 1987).
This section addresses the issues of how and when to avoid raising
exceptions, how and where to handle them, and whether to propagate
them. Information on how to use exceptions as part of the interface
to a unit includes what exceptions to declare and raise and under
what conditions to raise them. Other issues are addressed in the
guidelines in Sections 4.3 and 7.5.
5.8.1 Handling Versus Avoiding Exceptions
guideline
- When it is easy and efficient to do so, avoid
causing exceptions to be raised.
- Provide handlers for exceptions that cannot be avoided.
- Use exception handlers to enhance readability by separating
fault handling from normal execution.
- Do not use exceptions and exception handlers as goto
statements.
- Do not evaluate the value of an object (or a part of an object)
that has become abnormal because of the failure of a language-defined
check.
rationale
5.8.2 Handlers for Others
guideline
- When writing an exception handler for others, capture
and return additional information about the exception through
the Exception_Name, Exception_Message, or Exception_Information
subprograms declared in the predefined package Ada.Exceptions.
- Use others only to catch exceptions you cannot enumerate
explicitly, preferably only to flag a potential abort.
- During development, trap others, capture the exception
being handled, and consider adding an explicit handler for that
exception.
example
rationale
notes
5.8.3 Propagation
guideline
- Handle all exceptions, both user
and predefined.
- For every exception that might be raised, provide a handler
in suitable frames to protect against undesired propagation
outside the abstraction.
rationale
5.8.4 Localizing the Cause of an Exception
guideline
- Do not rely on being able to identify the fault-raising, predefined,
or implementation-defined
exceptions.
- Use the facilities defined in Ada.Exceptions to capture
as much information as possible about an exception.
- Use blocks to associate localized
sections of code with their own exception
handlers.
example
rationale
notes
5.9 ERRONEOUS EXECUTION AND BOUNDED ERRORS
Ada 95 introduces the category of bounded errors. Bounded errors
are cases where the behavior is not deterministic but falls within
well-defined bounds (Rationale 1995, §1.4). The consequence
of a bounded error is to limit the behavior of compilers so that
an Ada environment is not free to do whatever it wants in the
presence of errors. The Ada Reference Manual (1995) defines a
set of possible outcomes for the consequences of undefined behavior,
as in an uninitialized value or a value outside the range of its
subtype. For example, the executing program may raise the predefined
exception Program_Error, Constraint_Error, or
it may do nothing.
An Ada program is erroneous when it generates an
error that is not required to be detected by the compiler or
>
run-time environments. As stated in the Ada Reference Manual (1995,
§1.1.5), "The effects of erroneous execution are unpredictable."
If the compiler does detect an instance of an erroneous program,
its options are to indicate a compile time error; to insert the
code to raise Program_Error,
possibly to write a message to that effect; or to do nothing at
all.
Erroneousness is not a concept unique to Ada. The guidelines below
describe or explain some specific instances of erroneousness defined
in the Ada Reference Manual (1995). These guidelines are not intended
to be all-inclusive but rather emphasize some commonly overlooked
problem areas. Arbitrary order dependencies are not, strictly
speaking, a case of erroneous execution; thus, they are discussed
in Guideline 7.1.9 as a portability issue.
5.9.1 Unchecked Conversion
guideline
- Use Ada.Unchecked_Conversion only with the utmost care
(Ada Reference Manual 1995, §13.9).
- Consider using the 'Valid attribute to check the validity
of scalar data.
- Ensure that the value resulting from Ada.Unchecked_Conversion
properly represents a value of the parameter's subtype.
- Isolate the use of Ada.Unchecked_Conversion in package
bodies.
example
rationale
- Data produced through an unchecked conversion
- Input data
- Parameter values returned from a foreign language interface
- Aborted assignment (during asynchronous transfer of control
or execution of an abort statement)
- Disrupted assignment from failure of a language-defined check
- Data whose address has been specified with the 'Address
attribute
An access value should not be assumed to be correct when obtained
without compiler or run-time checks. When dealing with access
values, use of the 'Valid attribute helps prevent the
erroneous dereferencing that might occur after using Ada.Unchecked_Deallocation,
Unchecked_Access, or Ada.Unchecked_Conversion.
In the case of a nonscalar object used as an actual parameter
in an unchecked conversion, you should ensure that its value on
return from the procedure properly represents a value in the subtype.
This case occurs when the parameter is of mode out or
in out. It is important to check the value when interfacing
to foreign languages or using a language-defined input procedure.
The Ada Reference Manual (1995, §13.9.1) lists the full rules
concerning data validity.
5.9.2 Unchecked Deallocation
guideline
- Isolate the use of Ada.Unchecked_Deallocation in package
bodies.
- Ensure that no dangling reference to the local object exists
after exiting the scope of the local object.
rationale
5.9.3 Unchecked Access
guideline
- Minimize the use of the attribute Unchecked_Access,
preferably isolating it to package
bodies.
- Use the attribute Unchecked_Access only on data whose
lifetime/scope is "library level."
rationale
exceptions
5.9.4 Address Clauses
guideline
- Use address clauses to map variables and entries
to the hardware device or memory, not
to model the FORTRAN "equivalence"
feature.
- Ensure that the address specified in an attribute definition
clause is valid and does not conflict with the alignment.
- If available in your Ada environment, use the package Ada.Interrupts
to associate handlers with interrupts.
- Avoid using the address clause for nonimported program units.
example
rationale
5.9.5 Suppression of Exception Check
guideline
- Do not suppress exception checks during development.
- If necessary, during operation, introduce blocks
that encompass the smallest range of statements that can safely
have exception checking removed.
rationale
5.9.6 Initialization
guideline
- Initialize all objects prior to use.
- Use caution when initializing access values.
- Do not depend on default initialization that is not part of
the language.
- Derive from a controlled type and override the primitive procedure
to ensure automatic initialization.
- Ensure elaboration of an entity before using
it.
- Use function calls in declarations
cautiously.
example
rationale
notes
5.9.7 Direct_IO and Sequential_IO
guideline
- Ensure that values obtained from Ada.Direct_IO and
Ada.Sequential_IO are in range.
- Use the 'Valid attribute to check the validity of scalar
values obtained through Ada.Direct_IO and Ada.Sequential_IO.
rationale
notes
5.9.8 Exception Propagation
guideline
- Prevent exceptions from propagating outside any user-defined
Finalize or Adjust procedure by providing handlers
for all predefined and user-defined exceptions at the end of each
procedure.
rationale
5.9.9 Protected Objects
guideline
- Do not invoke a potentially blocking operation within a protected
entry, a protected procedure, or a protected function.
rationale
- Select statement
- Accept statement
- Entry-call statement
- Delay statement
- Abort statement
- Task creation or activation
- External call on a protected subprogram (or an external requeue)
with the same target object as that of the protected action
- Call on a subprogram whose body contains a potentially blocking
operation
Invoking any of these potentially blocking operations could lead
either to a bounded error being detected or to a deadlock situation.
In the case of bounded error, the exception Program_Error
is raised. In addition, avoid calling routines within a protected
entry, procedure, or function that could directly or indirectly
invoke operating system primitives or similar operations that
can cause blocking that is not visible to the Ada run-time system.
5.9.10 Abort Statement
guideline
- Do not use an asynchronous select statement within
abort-deferred operations.
- Do not create a task that depends on a master that is included
entirely within the execution of an
abort-deferred operation.
rationale
- Protected entry, protected procedure, or protected function
- User-defined Initialize procedure used as the last
step of a default initialization of a controlled object
- User-defined Finalize procedure used in finalization
of a controlled object
- User-defined Adjust procedure used in assignment of
a controlled object
The Ada Reference Manual (1995, §9.8) states that the practices
discouraged in the guidelines result in bounded error. The exception
Program_Error is raised if the implementation detects
the error. If the implementation does not detect the error, the
operations proceed as they would outside an abort-deferred operation.
An abort statement itself may have no effect.
5.10 SUMMARY
optional parts of the syntax
- Associate names with loops when they are nested
(Booch 1986, 1987).
- Associate names with any loop that contains an exit
statement.
- Associate names with blocks when they are nested.
- Use loop names on all exit statements
from nested loops.
- Include the defining program unit name at the end of a package
specification and body.
- Include the defining identifier at the end of a task
specification and body.
- Include the entry identifier at the end of an accept
statement.
- Include the designator at the end of a subprogram
body.
- Include the defining identifier at the end of a protected unit
declaration.
parameter lists
- Nameformal parametername
formal parameters descriptively to
reduce the need for comments.
- Use named parameter association in calls of infrequently used
subprograms
or entries with many formal
parameters.
- Use named association when instantiating
generics.
- Use named association for clarification when the actual
parameter is any literal or
expression.
- Use named association when supplying a nondefault value to an
optional parameter.
- Provide default parameters to allow for occasional, special
use of widely used subprograms
or entries.
- Place default parameters at the end of the formal parameter
list.
- Consider providing default values to new
parameters added to an existing subprogram.
- Show the mode indication of all procedure
and entry parameters
(Nissen and Wallis 1984).
- Use the most restrictive parameter mode applicable to your application.
types
- Use existing types as building blocks by deriving new types
from them.
- Use range constraints on subtypes.
- Define new types, especially derived types, to include the largest
set of possible values, including boundary
values.
- Constrain the ranges of derived types with subtypes, excluding
boundary values.
- Use type derivation rather than type extension when there are
no meaningful components to add to the type.
- Avoid anonymous array types.
- Use anonymous array types for array
variables only when no suitable type exists or can be created
and the array will not be referenced as a whole (e.g., used as
a subprogram parameter).
- Use access parameters and access discriminants to guarantee
that the parameter or discriminant is treated as a constant.
- Derive from controlled types in preference to using limited
private types.
- Use limited private types in preference to private types.
- Use private types in preference to nonprivate types.
- Explicitly export needed operations rather than easing restrictions.
- Use access-to-subprogram types for indirect access to subprograms.
- Wherever possible, use abstract tagged types and dispatching
rather than access-to-subprogram types to implement dynamic selection
and invocation of subprograms.
data structures
- When declaring a discriminant, use as constrained a subtype
as possible (i.e., subtype with as specific a range constraint
as possible).
- Use a discriminated record rather than a constrained array to
represent an array whose actual values are unconstrained.
- Use records to
group heterogeneous but related data.
- Consider records to map to I/O
device data.
- Use access types to class-wide types to implement heterogeneous
polymorphic data structures.
- Use tagged types and type extension rather than variant records
(in combination with enumeration types and case statements).
- Record structures should not always be flat. Factor out common
parts.
- For a large record structure, group related components into
smaller subrecords.
- For nested records, pick element names
that read well when inner elements are referenced.
- Consider using type extension to organize large data structures.
- Differentiate between static and dynamic data.
Use dynamically allocated objects with caution.
- Use dynamically allocated data structures
only when it is necessary to create and destroy them dynamically
or to be able to reference them by different names.
- Do not drop pointers to undeallocated
objects.
- Do not leave dangling references to
deallocated objects.
- Initialize all access variables
and components within a
record.
- Do not rely on memory deallocation.
- Deallocate explicitly.
- Use length
clauses to specify total allocation size.
- Provide handlers for Storage_Error.
- Use controlled types to implement private types that manipulate
dynamic data.
- Avoid unconstrained record objects unless your run-time environment
reliably reclaims dynamic heap storage.
- Unless your run-time environment reliably reclaims dynamic heap
storage, declare the following items only in the outermost, unnested
declarative part of either a library package, a main subprogram,
or a permanent task:
- Access types
- Constrained composite objects with nonstatic bounds
- Objects of an unconstrained composite type other than unconstrained
records
- Composite objects large enough (at compile time) for the compiler
to allocate implicitly on the heap
- Unless your run-time environment reliably reclaims dynamic heap
storage or you are creating permanent, dynamically allocated tasks,
avoid declaring tasks in the following situations:
- Unconstrained array subtypes whose components are tasks
- Discriminated record subtypes containing a component that is
an array of tasks, where the array size depends on the value of
the discriminant
- Any declarative region other than the outermost, unnested declarative
part of either a library package or a main subprogram
- Arrays of tasks that are not statically constrained
- Minimize the use of aliased variables.
- Use aliasing for statically created, ragged arrays (Rationale
1995, §3.7.1).
- Use aliasing to refer to part of a data structure when you want
to hide the internal connections and bookkeeping information.
- Use access discriminants to create self-referential data structures,
i.e., a data structure one of whose components points to the enclosing
structure.
- Use modular types rather than a Boolean arrays when you create
data structures that need bit-wise operations, such as and
and or.
expressions
- Use 'First or 'Last
instead of numeric literals
to represent the first or last values of a range.
- Use 'Range or the subtype
name of the range instead of 'First .. 'Last.
- Use array attributes 'First,
'Last, or 'Length
instead of numeric literals
for accessing arrays.
- Use the 'Range of the array
instead of the name of the index subtype to express a range.
- Use 'Range instead of 'First .. 'Last to express
a range.
- Use parentheses to specify the order of subexpression evaluation
to clarify expressions (NASA 1987).
- Use parentheses to specify the order
of evaluation for subexpressions
whose correctness depends on left to right evaluation.
- Avoid names and constructs that rely on the
use of negatives.
- Choose names of flags so they represent
states that can be used in positive
form.
- Use short-circuit forms of the logical operators to specify
the order of conditions when the failure of one condition means
that the other condition will raise an exception.
- Use <= and >= in relational
expressions with real
operands instead of =.
statements
- 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.
- Use slices rather than a loop
to copy part of an array.
- 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.
- Use for loops, whenever possible.
- Use while loops 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 exit
statements for more complex situations.
- Avoid exit statements in
while and for
loops.
- Minimize the number of ways to exit a loop.
- Use exit statements to enhance the readability of loop
termination code (NASA 1987).
- Use exit when ... rather than if ... then exit
whenever possible (NASA 1987).
- Review exit statement placement.
- Consider specifying bounds on loops.
- Consider specifying bounds on recursion.
- Do not use goto statements.
- Minimize the number of return statements from
a subprogram (NASA 1987).
- Highlight return statements with comments
or white space to keep them
from being lost in other code.
- Use blocks to localize the scope of
declarations.
- Use blocks to perform local renaming.
- Use blocks to define local exception
handlers.
- 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.
visibility
- When you need to provide visibility to operators, use the use
type clause.
- Avoid/minimize the use of the use clause (Nissen and
Wallis 1984).
- Consider using a package renames clause rather than
a use clause for a package.
- Consider using the use clause in the following situations:
- When standard packages are
needed and no ambiguous
references are introduced
- When references to enumeration
literals are needed
- Localize the effect of all
use clauses.
- Limit the scope of a renaming declaration to the minimum necessary
scope.
- Rename a long, fully qualified
name to reduce the complexity if it becomes unwieldy.
- Use renaming to provide the body of a subprogram if this subprogram
merely calls the first subprogram.
- Rename declarations for visibility
purposes rather than using the use clause,
except for operators.
- Rename parts when your code interfaces to reusable
components originally written with nondescriptive or inapplicable
nomenclature.
- Use a project-wide standard list of abbreviations
to rename common packages.
- Provide a use type rather than a renames clause
to provide visibility to operators.
- Limit overloading to widely used subprograms
that perform similar actions on arguments
of different types (Nissen and Wallis 1984).
- Preserve the conventional meaning of overloaded operators (Nissen
and Wallis 1984).
- Use "+" to identify adding, joining, increasing,
and enhancing kinds of functions.
- Use "-" to identify subtraction, separation,
decreasing, and depleting kinds of functions.
- Use operator overloading sparingly and uniformly when applied
to tagged types.
- Define an appropriate equality operator for private
types.
- Consider redefining the equality operator for a private type.
- When overloading the equality operator for types,
maintain the properties of an algebraic equivalence relation.
using exceptions
- When it is easy and efficient to do so, avoid
causing exceptions to be raised.
- Provide handlers for exceptions that cannot be avoided.
- Use exception handlers to enhance readability by separating
fault handling from normal execution.
- Do not use exceptions and exception handlers as goto
statements.
- Do not evaluate the value of an object (or a part of an object)
that has become abnormal because of the failure of a language-defined
check.
- When writing an exception handler for others, capture
and return additional information about the exception through
the Exception_Name, Exception_Message, or Exception_Information
subprograms declared in the predefined package Ada.Exceptions.
- Use others only to catch exceptions you cannot enumerate
explicitly, preferably only to flag a potential abort.
- During development, trap others, capture the exception
being handled, and consider adding an explicit handler for that
exception.
- Handle all exceptions, both user
and predefined.
- For every exception that might be raised, provide a handler
in suitable frames to protect against undesired propagation
outside the abstraction.
- Do not rely on being able to identify the fault-raising, predefined,
or implementation-defined
exceptions.
- Use the facilities defined in Ada.Exceptions to capture
as much information as possible about an exception.
- Use blocks to associate localized
sections of code with their own exception
handlers.
erroneous execution and bounded errors
- Use Ada.Unchecked_Conversion only with the utmost care
(Ada Reference Manual 1995, §13.9).
- Consider using the 'Valid attribute to check the validity
of scalar data).
- Ensure that the value resulting from Ada.Unchecked_Conversion
properly represents a value of the parameter's subtype.
- Isolate the use of Ada.Unchecked_Conversion in package
bodies.
- Isolate the use of Ada.Unchecked_Deallocation in package
bodies.
- Ensure that no dangling reference to the local object exists
after exiting the scope of the local object.
- Minimize the use of the attribute Unchecked_Access,
preferably isolating it to package
bodies.
- Use the attribute Unchecked_Access only on data whose
lifetime/scope is "library level."
- Use address clauses to map variables and entries
to the hardware device or memory, not
to model the FORTRAN "equivalence"
feature.
- Ensure that the address specified in an attribute definition
clause is valid and does not conflict with the alignment.
- If available in your Ada environment, use the package Ada.Interrupts
to associate handlers with interrupts.
- Avoid using the address clause for nonimported program units.
- Do not suppress exception checks during development.
- If necessary, during operation, introduce blocks
that encompass the smallest range of statements that can safely
have exception checking removed.
- Initialize all objects, including
access values, prior to use.
- Use caution when initializing access values.
- Do not depend on default initialization that is not part of
the language.
- Derive from a controlled type and override the primitive procedure
to ensure automatic initialization.
- Ensure elaboration of an entity before using
it.
- Use function calls in declarations
cautiously.
- Ensure that values obtained from Ada.Direct_IO and
Ada.Sequential_IO are in range.
- Use the 'Valid attribute to check the validity of scalar
values obtained through Ada.Direct_IO and Ada.Sequential_IO.
- Prevent exceptions from propagating outside any user-defined
Finalize or Adjust procedure by providing handlers
for all predefined and user-defined exceptions at the end of each
procedure.
- Do not invoke a potentially blocking operation within a protected
entry, a protected procedure, or a protected function.
- Do not use an asynchronous select statement within abort-deferred
operations.
- Do not create a task that depends on a master that is included
entirely within the execution of an
abort-deferred operation.