Ada 95 Quality and Style Guide Chapter 8
CHAPTER 8
Reusability
Reusability is the extent to which code can be used in different
applications with minimal change. As code is reused in a new application,
that new application partially inherits the attributes of that
code. If the code is maintainable, the application is more maintainable.
If it is portable, then the application is more portable. So this
chapter's guidelines are most useful when all of the other guidelines
in this book are also applied.
Several guidelines are directed at the issue of maintainability.
Maintainable code is easy to change to meet new or changing requirements.
Maintainability plays a special role in reuse. When attempts are
made to reuse code, it is often necessary to change it to suit
the new application. If the code cannot be changed easily, it
is less likely to be reused.
There are many issues involved in software reuse: whether to reuse
parts, how to store and retrieve reusable parts in a library,
how to certify parts, how to maximize the economic value of reuse,
how to provide incentives to engineers and entire companies to
reuse parts rather than reinvent them, and so on. This chapter
ignores these managerial, economic, and logistic issues to focus
on the single technical issue of how to write software parts in
Ada to increase reuse potential. The other issues are just as
important but are outside of the scope of this book.
One of the design goals of Ada was to facilitate the creation
and use of reusable parts to improve productivity. To this end,
Ada provides features to develop reusable parts and to adapt them
once they are available. Packages, visibility control, and separate
compilation support modularity and information hiding (see guidelines
in
Sections 4.1, 4.2, 5.3, and 5.7). This allows the separation of
application-specific parts of the code, maximizes the general
purpose parts suitable for reuse, and allows the isolation of
design decisions within modules, facilitating change. The Ada
type system supports localization of data definitions so that
consistent changes are easy to make. The Ada inheritance features
support type extension so that data definitions and interfaces
may be customized for an application. Generic units directly support
the development of general purpose, adaptable code that can be
instantiated to perform specific functions. The Ada 95 improvements
for object-oriented techniques and abstraction support all of
the above goals. Using these features carefully and in conformance
to the guidelines in this book, produces code that is more likely
to be reusable.
Reusable code is developed in many ways. Code may be scavenged
from a previous project. A reusable library of code may be developed
from scratch for a particularly well-understood domain, such as
a math library. Reusable code may be developed as an intentional
byproduct of a specific application. Reusable code may be developed
a certain way because a design method requires it. These guidelines
are intended to apply in all of these situations.
The experienced programmer recognizes that software reuse is much
more a requirements and design issue
than a coding issue. The guidelines in this section are intended
to work within an overall method for developing reusable code.
This section will not deal with artifacts of design, testing,
etc. Some research into reuse issues related specifically to the
Ada language can be found in AIRMICS (1990), Edwards (1990), and
Wheeler (1992).
Regardless of development method, experience indicates that reusable
code has certain characteristics, and this chapter makes the following
assumptions:
- Reusable parts must be understandable. A reusable part should
be a model of clarity. The requirements for commenting reusable
parts are even more stringent than those for parts specific to
a particular application.
- Reusable parts must be of the highest possible quality. They
must be correct, reliable, and robust. An error or weakness in
a reusable part may have far-reaching consequences, and it is
important that other programmers can have a high degree of confidence
in any parts offered for reuse.
- Reusable parts must be adaptable. To maximize its reuse potential,
a reusable part must be able to adapt to the needs of a wide variety
of users.
- Reusable parts should be independent. It should be possible
to reuse a single part without also adopting many other parts
that are apparently unrelated.
In addition to these criteria, a reusable part must be easier
to reuse than to reinvent, must be efficient, and must be portable.
If it takes more effort to reuse a part than to create one from
scratch or if the reused part is simply not efficient enough,
reuse does not occur as readily. For guidelines on portability,
see Chapter 7.
This chapter should not be read in isolation. In many respects,
a well-written, reusable component is simply an extreme example
of a well-written component. All of the guidelines in the previous
chapters and in Chapter 9 apply to reusable components as well
as components specific to a single application. As experience
increases with the 1995 revision to the Ada standard, new guidelines
may emerge while others may change. The guidelines listed here
apply specifically to reusable components.
Guidelines in this chapter are frequently worded "consider
. . ." because hard and fast rules cannot apply in all situations.
The specific choice you can make in a given situation involves
design tradeoffs. The rationale for these guidelines is intended
to give you insight into some of these tradeoffs.
8.1 UNDERSTANDING AND CLARITY
It is particularly important that parts intended for reuse should
be easy to understand. What the part does, how to use it, what
anticipated changes might be made to it in the future, and how
it works are facts that must be immediately apparent from inspection
of the comments and the code itself. For maximum readability of
reusable parts, follow the guidelines in Chapter 3, some of which
are repeated more strongly below.
8.1.1 Application-Independent Naming
guideline
- Select the least restrictive names possible for reusable parts
and their identifiers.
- Select the generic name to avoid conflicting
with the naming conventions of instantiations
of the generic.
- Use names that indicate the behavioral characteristics of the
reusable part, as well as its abstraction.
example
rationale
8.1.2 Abbreviations
guideline
- Do not use abbreviations in identifier
or unit names.
example
rationale
notes
8.1.3 Generic Formal Parameters
guideline
- Document the expected
behavior of generic formal parameters just as you document any
package specification.
example
rationale
8.2 ROBUSTNESS
The following guidelines improve the robustness of Ada code. It
is easy to write code that depends on an assumption that you do
not realize that you are making. When such a part is reused in
a different environment, it can break unexpectedly. The guidelines
in this section show some ways in which Ada code can be made to
automatically conform to its environment and some ways in which
it can be made to check for violations of assumptions. Finally,
some guidelines are given to warn you about errors that Ada does
not catch as soon as you might like.
8.2.1 Named Numbers
guideline
- Use named numbers and static
expressions to allow multiple dependencies to be linked to a small
number of symbols.
example
rationale
8.2.2 Unconstrained Arrays
guideline
- Use unconstrained array types
for array formal parameters
and array return values.
- Make the size of local variables
depend on actual parameter size, where appropriate.
example
rationale
8.2.3 Minimizing and Documenting Assumptions
guideline
- Minimize the number of assumptions made by a unit.
- For assumptions that cannot be avoided, use subtypes or constraints
to automatically enforce conformance.
- For assumptions that cannot be automatically enforced by subtypes,
add explicit checks to the code.
- Document all assumptions.
- If the code depends upon the implementation of a specific Special
Needs Annex for proper operation, document this assumption in
the code.
example
rationale
notes
8.2.4 Subtypes in Generic Specifications
guideline
- Use first subtypes when declaring generic formal objects of
mode in out.
- Beware of using subtypes as subtype marks when declaring parameters
or return values of generic formal subprograms.
- Use attributes rather than literal
values.
example
rationale
notes
8.2.5 Overloading in Generic Units
guideline
- Be careful about overloading the names
of subprograms exported
by the same generic package.
example
rationale
8.2.6 Hidden Tasks
guideline
- Within a specification, document
any tasks that would be activated by with'ing the specification
and by using any part of the specification.
- Document which generic
formal parameters are accessed from a task hidden inside the generic
unit.
- Document any multithreaded components.
rationale
8.2.7 Exceptions
guideline
- Propagate exceptions
out of reusable parts. Handle
exceptions within reusable parts only when you are certain that
the handling is appropriate in all circumstances.
- Propagate exceptions raised by generic
formal subprograms after performing any cleanup necessary to the
correct operation of future invocations of the generic instantiation.
- Leave state variables in a valid state
when raising an exception.
- Leave parameters unmodified
when raising an exception.
example
rationale
notes
8.3 ADAPTABILITY
Reusable parts often need to be changed before they can be used
in a specific application. They should be structured so that change
is easy and as localized as possible. One way of achieving adaptability
is to create general parts with complete functionality, only a
subset of which might be needed in a given application. Another
way to achieve adaptability is to use Ada's generic construct
to produce parts that can be appropriately instantiated with different
parameters. Both of these approaches avoid the error-prone process
of adapting a part by changing its code but have limitations and
can carry some overhead.
Anticipated changes, that
is, changes that can be reasonably foreseen by the developer of
the part, should be provided for as far as possible. Unanticipated
changes can only be accommodated by carefully structuring a part
to be adaptable. Many of the considerations pertaining to maintainability
apply. If the code is of high quality, clear, and conforms to
well-established design principles such as information
hiding, it is easier to adapt in unforeseen ways.
8.3.1 Complete Functionality
guideline
- Provide core functionality in a reusable part or set of parts
so that the functionality in this abstraction can be meaningfully
extended by its reusers.
- More specifically, provide initialization
and finalization procedures
for every data structure that may contain dynamic
data.
- For data structures needing initialization and finalization,
consider deriving them, when possible, from the types Ada.Finalization.Controlled
or Ada.Finalization.Limited_Controlled.
example
rationale
notes
8.3.2 Generic Units
guideline
- Use generic units
to avoid code duplication.
- Parameterize generic units for maximum adaptability.
- Reuse common instantiations of
generic units, as well as the generic units themselves.
rationale
8.3.3 Formal Private and Limited Private Types
guideline
- Consider using a limited private type for a generic formal type
when you do not need assignment on objects of the type inside
the generic body.
- Consider using a nonlimited private type for a generic formal
type when you need normal assignment on objects of the type inside
the body of the generic.
- Consider using a formal tagged type derived from Ada.Finalization.Controlled
when you need to enforce special assignment semantics on objects
of the type in the body of the generic.
- Export the least restrictive type that maintains the integrity
of the data and abstraction while allowing alternate implementations.
- Consider using a limited private abstract type for generic formal
types of a generic that extends a formal private tagged type.
example
rationale
notes
8.3.4 Using Generic Units to Encapsulate Algorithms
guideline
- Use generic units to encapsulate algorithms independently of
data type.
example
rationale
Chapter 8 Continued