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 unconstrainedrecords
- - 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.