[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.2 Visibility

Ada's ability to enforce information hiding and separation of concerns through its visibility controlling features is one of the most important advantages of the language, particularly when "pieces of a large system are being developed separately." Subverting these features, for example by excessive reliance on the use clause, is wasteful and dangerous. See also Guideline 5.7.

Language Ref Manual references: 8 Visibility Rules

In this section...
4.2.1 Minimization of Interfaces
4.2.2 Nested Packages
4.2.3 Restricting Visibility
4.2.4 Hiding Tasks
Summary of Guidelines from this section


4.2.1 Minimization of Interfaces

guideline

example

------------------------------------------------------------------------- 
package Telephone_Book is

   type Listing is limited private;
   
   procedure Set_Name (New_Name : in     String; 
                       Current  : in out Listing);
                       
   procedure Insert (Additional : in     Listing); 
   procedure Delete (Obsolete   : in     Listing);
   
private

   type Information; 
   type Listing is access Information;
   
end Telephone_Book;

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

package body Telephone_Book is

   -- Full details of record for a listing 
   type Information is 
      record 
         ... 
         Next : Listing; 
      end record;
      
   First : Listing;
   
   procedure Set_Name (New_Name  : in     String; 
                       Current   : in out Listing) is separate; 
   procedure Insert  (Additional : in     Listing) is separate; 
   procedure Delete  (Obsolete   : in     Listing) is separate;
   
end Telephone_Book; 
-------------------------------------------------------------------------

rationale

For each entity in the specification, give careful consideration to whether it could be moved to the body. The fewer the extraneous details, the more understandable the program, package, or subprogram. It is important to maintainers to know exactly what a package interface is so that they can understand the effects of changes. Interfaces to a subprogram extend beyond the parameters. Any modification of global data from within a package or subprogram is an undocumented interface to the "outside" as well.

Pushing as many as possible of the context dependencies into the body makes the reader's job easier, localizes the recompilation required when library units change, and helps prevent a ripple effect during modifications. See also Guideline 4.2.3.

Subprograms with large numbers of parameters often indicate poor design decisions (e.g., the functional boundaries of the subprogram are inappropriate, or parameters are structured poorly). Conversely, subprograms with no parameters are likely to be accessing global data.

Objects visible within package specifications can be modified by any unit that has visibility to them. The object cannot be protected or represented abstractly by its enclosing package. Objects which must persist should be declared in package bodies. Objects whose value depends on program units external to their enclosing package are probably either in the wrong package or are better accessed by a subprogram specified in the package specification.

note

The specifications of some packages, such as Ada bindings to existing subroutine libraries, cannot easily be reduced in size. In such cases, it may be beneficial to break these up into smaller packages, grouping according to category (e.g., trigonometric functions).

Language Ref Manual references: 6.1 Subprogram Declarations, 6.6 Parameter and Result Type Profile - Overloading of Subprograms, 7.1 Package Structure, 7.2 Package Specifications and Declarations, 7.3 Package Bodies, 8.3 Visibility, 10.1.1 Context Clauses - With Clauses


4.2.2 Nested Packages

guideline

example

Chapter 14 of the Ada Language Reference Manual gives an example of desirable package specification nesting. The specifications of generic packages Integer_IO, Float_IO, Fixed_IO, and Enumeration_IO are nested within the specification of package Text_IO. Each of them is a generic, grouping closely related operations and needing to use hidden details of the implementation of Text_IO.

rationale

Grouping package specifications into an encompassing package emphasizes a relationship of commonality among those packages. It also allows them to share common implementation details resulting from the relationship.

An abstraction occasionally needs to present different views to different classes of users. Building one view upon another as an additional abstraction does not always suffice, because the functionality of the operations presented by the views may be only partially disjoint. Nesting specifications groups the facilities of the various views, yet associates them with the abstraction they present. Abusive mixing of the views by another unit would be easy to detect due to the multiple use clauses or an incongruous mix of qualified names.

Language Ref Manual references: 7.2 Package Specifications and Declarations, 12.1 Generic Declarations, 14.3 Text Input-Output, 14.3.10 Specification of the Package Text_IO


4.2.3 Restricting Visibility

guideline

example

This program is a compiler. Access to the printing facilities of Text_IO is restricted to the software involved in producing the source code listing.
------------------------------------------------------------------------- 
procedure Compiler is

   ---------------------------------------------------------------------- 
   package Listing_Facilities is
   
      procedure New_Page_Of_Listing; 
      procedure New_Line_Of_Print; 
      ...
      
   end Listing_Facilities;
   
   ---------------------------------------------------------------------- 
   package body Listing_Facilities is separate;
   
begin  -- Compiler 
   ... 
end Compiler;

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

with Text_IO;

separate (Compiler) 
package body Listing_Facilities is

   ---------------------------------------------------------------------- 
   procedure New_Page_Of_Listing is 
   begin 
      ... 
   end New_Page_Of_Listing;
   
   ----------------------------------------------------------------------
   
   procedure New_Line_Of_Print is 
   begin 
      ... 
   end New_Line_Of_Print;
   
   ...
   
end Listing_Facilities; 
-------------------------------------------------------------------------

rationale

Restricting visibility of a program unit ensures that the program unit is not called from some other part of the system than that which was intended. This is done by nesting it inside of the only unit which uses it, or by hiding it inside of a package body rather than declaring it in the package specification. This avoids errors and eases the job of maintainers by guaranteeing that a local change in that unit will not have an unforeseen global effect.

Restricting visibility of a library unit, by using with clauses on subunits rather than on the entire parent unit, is useful in the same way. In the example above, it is clear that the package Text_IO is used only by the Listing_Facilities package of the compiler.

note

One way to minimize the coverage of a with clause is to use it only with subunits that really need it. Consider making those subunits separate compilation units when the need for visibility to a library unit is restricted to a subprogram or two.

Language Ref Manual references: 8.1 Declarative Region, 8.2 Scope of Declarations, 8.3 Visibility, 10.1.1 Context Clauses - With Clauses, 10.2 Subunits of Compilation Units


4.2.4 Hiding Tasks

guideline

example

------------------------------------------------------------------------- 
package Disk_Head_Scheduler is

   type Words        is ...
   
   type Track_Number is ...
   
   procedure Transmit (Track : in     Track_Number; 
                       Data  : in     Words);
                       
   ... 
end Disk_Head_Scheduler;

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

package body Disk_Head_Scheduler is

   ... 
   task Control is 
      entry Sign_In (Track : in     Track_Number);
      
      ... 
   end Control;
   
   ----------------------------------------------------------------------
   
   task Track_Manager is 
      entry Transfer(Track_Number) (Data : in     Words); 
   end Track_Manager;
   
   ---------------------------------------------------------------------- 
   ...
   
   procedure Transmit (Track : in     Track_Number; 
                       Data  : in     Words) is 
   begin
   
      Control.Sign_In(Track); 
      Track_Manager.Transfer(Track)(Data);
      
   end Transmit;
   
   ---------------------------------------------------------------------- 
   ... 
end Disk_Head_Scheduler; 
-------------------------------------------------------------------------

rationale

The decision whether to declare a task in the specification or body of an enclosing package is not a simple one. There are good arguments for both.

Hiding a task specification in a package body and exporting (via subprograms) only required entries reduces the amount of extraneous information in the package specification. It allows your subprograms to enforce any order of entry calls necessary to the proper operation of the tasks. It also allows you to impose defensive task communication practices (see Guideline 6.2.2) and proper use of conditional and timed entry calls. Finally, it allows the grouping of entries into sets for export to different classes of users (e.g., producers versus consumers), or the concealment of entries that should not be made public at all (e.g., initialization, completion, signals). Where performance is an issue and there are no ordering rules to enforce, the entries can be renamed as subprograms to avoid the overhead of an extra procedure call.

An argument which can be viewed as an advantage or disadvantage is that hiding the task specification in a package body hides the fact of a tasking implementation from the user. If the application is such that a change to or from a tasking implementation, or a reorganization of services among tasks, need not concern users of the package then this is an advantage. However, if the package user must know about the tasking implementation to reason about global tasking behavior, then it is better not to hide the task completely. Either move it to the package specification or add comments stating that there is a tasking implementation, describing when a call may block, etc. Otherwise, it is the package implementor's responsibility to ensure that users of the package do not have to concern themselves with behaviors such as deadlock, starvation, and race conditions.

Finally, keep in mind that hiding tasks behind a procedural interface prevents the usage of conditional and timed entry calls and entry families, unless you add parameters and extra code to the procedures to make it possible for callers to direct the procedures to use these capabilities.

Language Ref Manual references: 7.2 Package Specifications and Declarations, 9.7.2 Conditional Entry Calls, 9.7.3 Timed Entry Calls, 9 Tasks


Back to document index