[Ada Information Clearinghouse]

Ada '83 Quality and Style:

Guidelines for Professional Programmers

Copyright 1989, 1991,1992 Software Productivity Consortium, Inc., Herndon, Virginia.

CHAPTER 8: Reusability

8.4 Independence

A reusable part should be as independent as possible from other reusable parts. A potential user is less inclined to reuse a part if that part requires the use of other parts which seem unnecessary. The "extra baggage" of the other parts wastes time and space. A user would like to be able to reuse only that part which is perceived as useful.

Note that the concept of a "part" is intentionally vague here. A single package does not need to be independent of each other package in a reuse library, if the "parts" from that library which are typically reused are entire subsystems. If the entire subsystem is perceived as providing a useful function, the entire subsystem is reused. However, the subsystem should not be tightly coupled to all the other subsystems in the reuse library, so that it is difficult or impossible to reuse the subsystem without reusing the entire library. Coupling between reusable parts should only occur when it provides a strong benefit perceptible to the user.

Language Ref Manual references: 10.4 The Program Library

In this section...
8.4.1 Using Generic Parameters to Reduce Coupling
8.4.2 Coupling Due to Pragmas
8.4.3 Part Families
8.4.4 Conditional Compilation
8.4.5 Table-Driven Programming
Summary of Guidelines from this section


8.4.1 Using Generic Parameters to Reduce Coupling

guideline

example

A procedure like the following:
------------------------------------------------------------------------ 
with Package_A; 
procedure Produce_And_Store_A is 
   ...
   
begin  -- Produce_And_Store_A 
   ... 
   Package_A.Produce (...);
   
   ... 
   Package_A.Store (...);
   
   ... 
end Produce_And_Store_A; 
------------------------------------------------------------------------

can be rewritten as a generic unit:
------------------------------------------------------------------------ 
generic

   with procedure Produce (...); 
   with procedure Store   (...);
   
procedure Produce_And_Store;

------------------------------------------------------------------------ 
procedure Produce_And_Store is 
   ...
   
begin  -- Produce_And_Store 
   ... 
   Produce (...);
   
   ... 
   Store   (...);
   
   ... 
end Produce_And_Store; 
------------------------------------------------------------------------

and then instantiated:
------------------------------------------------------------------------ 
with Package_A; 
with Produce_And_Store; 
procedure Produce_And_Store_A is 
        new Produce_And_Store (Produce => Package_A.Produce, 
                               Store   => Package_A.Store); 
------------------------------------------------------------------------

rationale

Context (with) clauses specify the names of other units upon which this unit depends. Such dependencies cannot and should not be entirely avoided, but it is a good idea to minimize the number of them which occur in the specification of a unit. Try to move them to the body, leaving the specification independent of other units so that it is easier to understand in isolation. Also, organize your reusable parts in such a way that the bodies of the units do not contain large numbers of dependencies on each other. Partitioning your library into independent functional areas with no dependencies spanning the boundaries of the areas is a good way to start. Finally, reduce dependencies by using generic formal parameters instead of with statements, as shown in the example above. If the units in a library are too tightly coupled, then no single part can be reused without reusing most or all of the library.

The first (nongeneric) version of Produce_And_Store_A above is difficult to reuse because it depends on Package_A which may not be general purpose or generally available. If the operation Produce_And_Store has reuse potential which is reduced by this dependency, a generic unit and an instantiation should be produced as shown above. Note that the with clause for Package_A has been moved from the Produce_And_Store generic procedure which encapsulates the reusable algorithm to the Produce_And_Store_A instantiation. Instead of naming the package which provides the required operations, the generic unit simply lists the required operations themselves. This increases the independence and reusability of the generic unit.

This use of generic formal parameters in place of with clauses also allows visibility at a finer granularity. The with clause on the nongeneric version of Produce_And_Store_A makes all of the contents of Package_A visible to Produce_And_Store_A, while the generic parameters on the generic version make only the Produce and Store operations available to the generic instantiation.

Language Ref Manual references: 10.1.1 Context Clauses - With Clauses, 12.1 Generic Declarations


8.4.2 Coupling Due to Pragmas

guideline

example

------------------------------------------------------------------------
generic
   ...
   
package Stack is  
   ...
   
end Stack;

------------------------------------------------------------------------
with Stack;
pragma Elaborate (Stack); -- in case the body is not yet elaborated
package My_Stack is
        new Stack (...);
        
------------------------------------------------------------------------
package body Stack is  
begin
   ...
end Stack;
---------------------------------------------------------------------

rationale

Pragma Elaborate controls the order of elaboration of one unit with respect to
another. This is another way of coupling units and should be avoided when
possible in reusable parts, because it restricts the number of configurations
in which the reusable parts can be combined.

However, as more compilers begin to allow generics to be instantiated before
the bodies are compiled, elaboration orders that generally follow compilation
order may result in program errors. By forcing the compiler to elaborate the
generic before the instantiation, this error can be avoided or possibly
identify a problem of circularity (see 10.5 of Department of Defense 1983).

Pragma Priority controls the priority of a task relative to all other tasks in
a particular system. It is inappropriate in a reusable part which does not
know anything about the requirements and importance of other parts of the
systems in which it is reused. Give careful consideration to a reusable part
which claims that it can only be reused if its embedded task has the highest
priority in the system. No two such parts can ever be used together.

note

It is not possible to parameterize tasks with a priority to be specified at
instantiation or elaboration. However, a library of reusable parts that
contain tasks can be designed to depend on a single package of named numbers.
These named numbers can then be easily updated to fit the application's need
with the simple procedure of recompiling any library units that depend on the
named numbers. The configuration management implications of such an approach
are heavily dependent on the Ada development environment and compilation
system.

Language Ref Manual references: 2.8 Pragmas, 9.1 Abort Statements, 9.8 Priorities, 10.1.1 Context Clauses - With Clauses, 10.5 Elaboration of Library Units


8.4.3 Part Families

guideline

example

The Booch parts (Booch 1987) are an example of the application of this guideline.

rationale

Different versions of similar parts (e.g., bounded versus unbounded stacks) may be needed for different applications or to change the properties of a given application. Often, the different behaviors required by these versions cannot be obtained using generic parameters. Providing a family of parts with similar specifications makes it easy for the programmer to select the appropriate one for the current application or to substitute a different one if the needs of the application change.

note

A reusable part which is structured from subparts which are members of part families is particularly easy to tailor to the needs of a given application by substitution of family members.

Language Ref Manual references: 12.1 Generic Declarations


8.4.4 Conditional Compilation

guideline

example

------------------------------------------------------------------------ 
package Matrix_Math is

   ... 
   type Algorithm is (Gaussian, Pivoting, Choleski, Tri_Diagonal);
   
   generic 
      Which_Algorithm : in     Algorithm := Gaussian; 
   procedure Invert ( ... );
   
end Matrix_Math;

------------------------------------------------------------------------ 
package body Matrix_Math is 
   ...
   
   --------------------------------------------------------------------- 
   procedure Invert ( ... ) is 
      ... 
   begin  -- Invert 
      case Which_Algorithm is 
         when Gaussian =>     ... ; 
         when Pivoting =>     ... ; 
         when Choleski =>     ... ; 
         when Tri_Diagonal => ... ; 
      end case;
      
   end Invert; 
   ---------------------------------------------------------------------
   
end Matrix_Math; 
------------------------------------------------------------------------

rationale

Some compilers omit object code corresponding to parts of the program which they detect can never be executed. Constant expressions in conditional statements take advantage of this feature where it is available, providing a limited form of conditional compilation. When a part is reused in an implementation that does not support this form of conditional compilation, this practice produces a clean structure which is easy to adapt by deleting or commenting out redundant code where it creates an unacceptable overhead.

This feature should be used when other factors prevent the code from being separated into separate subunits. In the above example, it would be preferable to have a different procedure for each algorithm. But the algorithms may differ in slight but complex ways so as to make separate procedures difficult to maintain.

caution

Be aware of whether your implementation supports dead code removal, and be prepared to take other steps to eliminate the overhead of redundant code if necessary.

Language Ref Manual references: 10.6 Program Optimization, 12.3 Generic Instantiation


8.4.5 Table-Driven Programming

guideline

example

The epitome of table-driven reusable software is a parser generation system. A specification of the form of the input data and of its output, along with some specialization code, is converted to tables that are to be "walked" by pre-existing code using predetermined algorithms in the parser produced. Other forms of "application generators" work similarly.

rationale

Table-driven (sometimes known as data-driven) programs have behavior that depends on data with'ed at compile time or read from a file at run-time. In appropriate circumstances, table-driven programming provides a very powerful way of creating general-purpose, easily tailorable, reusable parts.

note

Consider whether differences in the behavior of a general-purpose part could be defined by some data structure at compile- or run-time and, if so, structure the part to be table-driven. The approach is most likely to be applicable when a part is designed for use in a particular application domain but needs to be specialized for use in a specific application within the domain. Take particular care in commenting the structure of the data needed to drive the part.
Back to document index