Ada 95 Quality and Style Guide Chapter 5
CHAPTER 5
Programming Practices
Software is always subject to change. The need for this change,
euphemistically known as "maintenance" arises from a
variety of sources. Errors need to be corrected as they are discovered.
System functionality may need to be enhanced in planned or unplanned
ways. Inevitably, the requirements change over the lifetime of
the system, forcing continual system evolution. Often, these modifications
are conducted long after the software was originally written,
usually by someone other than the original author.
Easy and successful modification requires that the software be
readable, understandable, and structured according to accepted
practice. If a software component cannot be easily understood
by a programmer who is familiar with its intended function, that
software component is not maintainable. Techniques that make code
readable and comprehensible enhance its maintainability. Previous
chapters presented techniques such as consistent use of naming
conventions, clear and well-organized commentary, and proper modularization.
This chapter presents consistent and logical use of language features.
Correctness is one aspect of reliability. While style guidelines
cannot enforce the use of correct algorithms, they can suggest
the use of techniques and language features known to reduce the
number or likelihood of failures. Such techniques include program
construction methods that reduce the likelihood of errors or that
improve program predictability by defining behavior in the presence
of errors.
5.1 OPTIONAL PARTS OF THE SYNTAX
Parts of the Ada syntax, while optional, can enhance the readability
of the code. The guidelines given below concern use of some of
these optional features.
5.1.1 Loop Names
guideline
- Associate names with loops when they are nested
(Booch 1986, 1987).
- Associate names with any loop that contains an exit
statement.
example
rationale
5.1.2 Block Names
guideline
- Associate names with blocks when they are nested.
example
rationale
5.1.3 Exit Statements
guideline
- Use loop names on all exit statements
from nested loops.
example
rationale
5.1.4 Naming End Statements
guideline
- 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.
example
rationale
5.2 PARAMETER LISTS
A subprogram or entry parameter list is the interface to the abstraction
implemented by the subprogram or entry. It is important that it
is clear and that it is expressed in a consistent style. Careful
decisions about formal parameter naming and ordering can make
the purpose of the subprogram easier to understand, which can
make it easier to use.
5.2.1 Formal Parameters
guideline
- Nameformal parametername
formal parameters descriptively to
reduce the need for comments.
example
rationale
5.2.2 Named Association
guideline
- 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.
instantiation
- Use named parameter association in calls of subprograms or entries
called from less than five places in a single source file or with
more than two formal parameters.
example
rationale
notes
caution
5.2.3 Default Parameters
guideline
- 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.
example
rationale
exceptions
5.2.4 Mode Indication
guideline
- Show the mode indication of all procedure
and entry parameters
(Nissen and Wallis 1984).
- Use the most restrictive parameter mode applicable to your application.
example
rationale
exceptions
5.3 TYPES
In addition to determining the possible values for variables and
subtype names, type distinctions can be very valuable aids in
developing safe, readable, and understandable code. Types clarify
the structure of your data and can limit or restrict the operations
performed on that data. "Keeping types distinct has been
found to be a very powerful means of detecting logical mistakes
when a program is written and to give valuable assistance whenever
the program is being subsequently maintained" (Pyle 1985).
Take advantage of Ada's strong typing capability in the form of
subtypes, derived types, task types, protected types, private
types, and limited private types.
The guidelines encourage much code to be written to ensure strong
typing. While it might appear that there would be execution penalties
for this amount of code, this is usually not the case. In contrast
to other conventional languages, Ada has a less direct relationship
between the amount of code that is written and the size of the
resulting executable program. Most of the strong type checking
is performed at compilation time rather than execution time, so
the size of the executable code is not greatly affected.
For guidelines on specific kinds of data structures and tagged
types, see Guidelines 5.4 and 9.2.1, respectively.
5.3.1 Derived Types and Subtypes
guideline
- 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.
example
rationale
notes
5.3.2 Anonymous Types
guideline
- 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.
example
rationale
notes
exceptions
5.3.3 Private Types
guideline
- 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.
example
rationale
notes
5.3.4 Subprogram Access Types
guideline
- 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.
example
rationale
5.4 DATA STRUCTURES
The data structuring capabilities of Ada are a powerful resource;
therefore, use them to model the data as closely as possible.
It is possible to group logically related data and let the language
control the abstraction and operations
on the data rather than requiring the programmer or maintainer
to do so. Data can also be organized in a building block fashion.
In addition to showing how a data structure is organized (and
possibly giving the reader an indication as to why it was organized
that way), creating the data structure from smaller components
allows those components to be reused. Using the features that
Ada provides can increase the maintainability of your code.
5.4.1 Discriminated Records
guideline
- 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.
example
rationale
5.4.2 Heterogeneous Related Data
guideline
- Use records to
group heterogeneous but related data.
- Consider records to map to I/O
device data.
example
rationale
notes
exceptions
5.4.3 Heterogeneous Polymorphic Data
guideline
- 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).
example
rationale
exceptions
5.4.4 Nested Records
guideline
- 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.
example
rationale
notes
5.4.5 Dynamic Data
guideline
- 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
example
rationale
exceptions
5.4.6 Aliased Objects
guideline
- 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.
example
rationale
5.4.7 Access Discriminants
guideline
- Use access discriminants to create self-referential data structures,
i.e., a data structure one of whose components points to the enclosing
structure.
example
rationale
5.4.8 Modular Types
guideline
- Use modular types rather than Boolean arrays when you create
data structures that need bit-wise operations, such as and
and or.
example
rationale
5.5 EXPRESSIONS
Properly coded expressions can enhance the readability and understandability
of a program. Poorly coded expressions can turn a program into
a maintainer's nightmare.
5.5.1 Range Values
guideline
- 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.
example
rationale
caution
5.5.2 Array Attributes
guideline
- 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.
example
rationale
5.5.3 Parenthetical Expressions
guideline
- 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.
example
rationale
5.5.4 Positive Forms of Logic
guideline
- 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.
example
rationale
exceptions
5.5.5 Short Circuit Forms of the Logical Operators
guideline
- 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.
example
rationale
notes
5.5.6 Accuracy of Operations With Real Operands
guideline
- Use <= and >= in relational
expressions with real
operands instead of =.
example
rationale
notes
exceptions
5.6 STATEMENTS
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.
5.6.1 Nesting
guideline
- 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).
instantiation
example
rationale
notes
- 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 if
statements into a case statement?
- Am I using else if where I
could be using elsif?
- Can I reorder the conditional expressions controlling this nested
structure?
- Is there a different design that
would be simpler?
exceptions
5.6.2 Slices
guideline
- Use slices rather than a loop
to copy part of an array.
example
rationale
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
rationale
notes
exceptions
Chapter 5 continued