[Ada Information Clearinghouse]

Ada '83 Quality and Style:

Guidelines for Professional Programmers

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

CHAPTER 4: Program Structure

4.1 High-Level Structure

Well-structured programs are easily understood, enhanced, and maintained. Poorly structured programs are frequently restructured during maintenance just to make the job easier. Many of the guidelines listed below are often given as general program design guidelines.
In this section...
4.1.1 Separate Compilation Capabilities
4.1.2 Subprograms
4.1.3 Functions
4.1.4 Packages
4.1.5 Cohesion
4.1.6 Data Coupling
4.1.7 Tasks
Summary of Guidelines from this section


4.1.1 Separate Compilation Capabilities

guideline

example

The file names below illustrate one possible file organization and associated consistent naming convention. The library unit name is used for the body. A trailing underscore indicates the specification, and any files containing subunits use names constructed by separating the body name from the subunit name with two underscores.
text_io_.ada                 -- the specification 
text_io.ada                  -- the body 
text_io__integer_io.ada      -- a subunit 
text_io__fixed_io.ada        -- a subunit 
text_io__float_io.ada        -- a subunit 
text_io__enumeration_io.ada  -- a subunit

rationale

The main reason for the emphasis on separate files in this guideline is to minimize the amount of recompilation required after each change. Typically, during software development, bodies of units are updated far more often than specifications. If the body and specification reside in the same file, then the specification will be compiled each time the body is compiled, even though the specification has not changed. Because the specification defines the interface between the unit and all of its users, this recompilation of the specification typically makes recompilation of all users necessary, in order to verify compliance with the specification. If the specifications and bodies of the users also reside together, then any users of these units will also have to be recompiled, and so on. The ripple effect can force a huge number of compilations which could have been avoided, severely slowing the development and test phase of a project. This is why we suggest placing specifications of all library units (nonnested units) in separate files from their bodies.

For the same reason, use subunits for large nested bodies, and put each subunit in its own file. This makes it possible to modify the body of the one nested unit without having to recompile any of the other units in the body. This is recommended for large units because changes are more likely to occur in large units than in small ones.

An additional benefit of using multiple separate files is that it allows different implementers to modify different parts of the system at the same time with conventional editors which do not allow multiple concurrent updates to a single file.

Finally, keeping bodies and specifications separate makes it possible to have multiple bodies for the same specification, or multiple specifications for the same body. Although Ada requires that there be exactly one specification per body in a system at any given time, it can still be useful to maintain multiple bodies or multiple specifications for use in different builds of a system. For example, a single specification may have multiple bodies, each of which implements the same functionality with a different tradeoff of time versus space efficiency. Or, for machine-dependent code, there may be one body for each target machine. Maintaining multiple package specifications can also be useful during development and test. You may develop one specification for delivery to your customer and another for unit testing. The first one would export only those subprograms intended to be called from outside of the package during normal operation of the system. The second one would export all subprograms of the package so that each of them could be independently tested.

A consistent file naming convention is recommended to make it easier to manage the large number of files which may result from following this guideline.

Language Ref Manual references: 6.1 Subprogram Declarations, 7.2 Package Specifications and Declarations, 7.3 Package Bodies, 10.1 Compilation Units - Library Units, 10.2 Subunits of Compilation Units, 10 Program Structure and Compilation Issues, F Implementation-Dependent Characteristics


4.1.2 Subprograms

guideline

example

Your program is required to output text to many types of devices. Because the devices would accept a variety of character sets, the proper way to do this is to write a subprogram to convert to the required character set. This way, the output subprogram has one purpose and the conversions are described elsewhere.
... 
---------------------------------------------------------------------- 
procedure Dispatch_To_Device 
      (Output : in     Text; 
       Device      : in     Device_Name; 
       Status      :    out Error_Codes) is
       
   Upper_Case_Output : Text (1 .. Output'Length); 
   ...
   
begin  -- Dispatch_To_Device

   ... 
   case Device.Character_Set is
   
      when Limited_ASCII => 
         Convert_To_Upper_Case(Original   => Output, 
                               Upper_Case => Upper_Case_Output); 
         ...
         
      when Extended_ASCII => 
         ...
         
      when EBCDIC => 
         ...
         
   end case;  -- Device_Type.Character_Set
   
   ...
   
end Dispatch_To_Device; 
----------------------------------------------------------------------

rationale

Subprograms are an extremely effective and well-understood abstraction technique. Subprograms increase program readability by hiding the details of a particular activity. It is not necessary that a subprogram be called more than once to justify its existence.

note

Dealing with the overhead of subroutine calls is discussed in Guideline 9.1.1.

Language Ref Manual references: 6 Subprograms


4.1.3 Functions

guideline

example

Although reading a character from a file will change what character is read next, this is accepted as a minor side effect compared to the primary purpose of the following function:
function Next_Character return Character is separate;

However, the use of a function like this should could lead to a subtle problem. Any time the order of evaluation is undefined, the order of the values returned by the function will effectively be undefined. In this example, the order of the characters placed in Word and the order that the following two characters are given to the Suffix parameters is unknown. No implementation of the Next_Character function can guarantee which character will go where:
   Word : constant String := String'(1 .. 5 => Next_Character);

begin  -- Start_Parsing

   Parse(Keyword => Word, 
         Suffix1 => Next_Character, 
         Suffix2 => Next_Character); 
end Start_Parsing;

Of course, if the order is unimportant (as in a random number generator), then the order of evaluation is unimportant.

rationale

A side effect is a change to any variable that is not local to the subprogram. This includes changes to variables by other subprograms and entries during calls from the function if the changes persist after the function returns. Side effects are discouraged because they are difficult to understand and maintain. Additionally, the Ada language does not define the order in which functions are evaluated when they occur in expressions or as actual parameters to subprograms. Therefore, a program which depends on the order in which side effects of functions occur is erroneous. Avoid using side effects anywhere.

Language Ref Manual references: 6.4 Subprogram Calls, 6.5 Function Subprograms, 8.3 Visibility


4.1.4 Packages

guideline

example

A package called Backing_Storage_Interface could contain type and subprogram declarations to support a generalized view of an external memory system (such as a disk or drum). Its internals may, in turn, depend on other packages more specific to the hardware or operating system.

rationale

Packages are the principal structuring facility in Ada. They are intended to be used as direct support for abstraction, information hiding, and modularization. For example, they are useful for encapsulating machine dependencies as an aid to portability. A single specification can have multiple bodies isolating implementation-specific information so other parts of the code do not need to change.

Encapsulating areas of potential change helps to minimize the effort required to implement that change by preventing unnecessary dependencies among unrelated parts of the system.

note

The most prevalent objection to this guideline usually involves performance penalties. See Guideline 9.1.1 for a discussion about subprogram overhead.

Language Ref Manual references: 3 Declarations and Types, 6.1 Subprogram Declarations, 7 Packages, 8.3 Visibility, 13 Representation Clauses and Implementation-Dependent Features, B Predefined Language Pragmas


4.1.5 Cohesion

guideline

example

As a bad example, a package named Project_Definitions is obviously a "catch all" for a particular project and is likely to be a jumbled mess. It probably has this form to permit project members to incorporate a single with clause into their software.

Better examples are packages called Display_Format_Definitions, containing all the types and constants needed by some specific display in a specific format, and Cartridge_Tape_Handler, containing all the types, constants, and subprograms which provide an interface to a special purpose device.

rationale

See also Guideline 5.4.1 on Heterogeneous Data.

The degree to which the entities in a package are related has a direct impact on the ease of understanding packages and programs made up of packages. There are different criteria for grouping, and some criteria are less effective than others. Grouping the class of data or activity (e.g., initialization modules) or grouping data or activities based on their timing characteristics is less effective than grouping based on function or need to communicate through data (Charette 1986 paraphrased).

note

Traditional subroutine libraries often group functionally unrelated subroutines. Even such libraries should be broken into a collection of packages each containing a logically cohesive set of subprograms.

Language Ref Manual references: 3.2 Objects and Named Numbers, 3.3 Types and Subtypes, 6.1 Subprogram Declarations, 7 Packages


4.1.6 Data Coupling

guideline

example

This is part of a compiler. Both the package handling error messages and the package containing the code generator need to know the current line number. Rather than storing this in a shared variable of type Natural, the information is stored in a package that hides the details of how such information is represented, and makes it available with access routines.
------------------------------------------------------------------------- 
package Compilation_Status is 
   type Line_Range is range 1 .. 2_500_000; 
   function Source_Line_Number return Line_Range; 
end Compilation_Status;

-------------------------------------------------------------------------

with Compilation_Status; 
package Error_Message_Processing is 
   -- Handle compile-time diagnostic. 
end Error_Message_Processing;

-------------------------------------------------------------------------

with Compilation_Status;

package Code_Generation is 
   -- Operations for code generation. 
end Code_Generation; 
-------------------------------------------------------------------------

rationale

Strongly coupled program units can be difficult to debug and very difficult to maintain. By protecting shared data with access functions, the coupling is lessened. This prevents dependence on the data structure and access to the data can be controlled.

note

The most prevalent objection to this guideline usually involves performance penalties. When a variable is moved to the package body, subprograms to access the variable must be provided and the overhead involved during each call to those subprograms is introduced. See Guideline 9.1.1 for a discussion about subprogram overhead.

Language Ref Manual references: 3.2.1 Object Declarations, 7.2 Package Specifications and Declarations, 8.2 Scope of Declarations


4.1.7 Tasks

guideline

rationale

The rationale for this guideline is given under Guideline 6.1.1. Chapter 6 discusses tasking in more detail.

Language Ref Manual references: 9 Tasks, 13.5.1 Interrupts


Back to document index