Ada 95 Quality and Style Guide                      Chapter 3


CHAPTER 3 Readability

This chapter recommends ways of using Ada features to make reading and understanding code easier. There are many myths about comments and readability. The responsibility for true readability rests more with naming and code structure than with comments. Having as many comment lines as code lines does not imply readability; it more likely indicates the writer does not understand what is important to communicate.


Spelling conventions in source code include rules for capitalization and use of underscores, numbers, and abbreviations. If you follow these conventions consistently, the resulting code is clearer and more readable.

3.1.1 Use of Underscores




When an identifier consists of more than one word, it is much easier to read if the words are separated by underscores. Indeed, there is precedent in English in which compound words are separated by a hyphen or a space. In addition to promoting readability of the code, if underscores are used in names, a code formatter has more control over altering capitalization. See Guideline 3.1.3.

3.1.2 Numbers



type Maximum_Samples     is range          1 ..  1_000_000;
type Legal_Hex_Address   is range   16#0000# ..   16#FFFF#;
type Legal_Octal_Address is range 8#000_000# .. 8#777_777#;
Avogadro_Number : constant := 6.02216_9E+23;

To represent the number 1/3 as a constant, use:

One_Third : constant := 1.0 / 3.0;

Avoid this use:

One_Third_As_Decimal_Approximation : constant := 0.33333_33333_3333;


One_Third_Base_3 : constant := 3#0.1#;


Consistent use of uppercase or lowercase aids scanning for numbers. Underscores serve to group portions of numbers into familiar patterns. Consistency with common use in everyday contexts is a large part of readability.


If a rational fraction is represented in a base in which it has a terminating rather than a repeating representation, as 3#0.1# does in the example above, it may have increased accuracy upon conversion to the machine base.

3.1.3 Capitalization



type Second_Of_Day      is range 0 .. 86_400;
type Noon_Relative_Time is (Before_Noon, After_Noon, High_Noon);
subtype Morning   is Second_Of_Day range 0 .. 86_400 / 2 - 1;
subtype Afternoon is Second_Of_Day range Morning'Last + 2 .. 86_400;
Current_Time := Second_Of_Day(Calendar.Seconds(Calendar.Clock));
if Current_Time in Morning then
   Time_Of_Day := Before_Noon;
elsif Current_Time in Afternoon then
   Time_Of_Day := After_Noon;
   Time_Of_Day := High_Noon;
end if;
case Time_Of_Day is
   when Before_Noon =>   Get_Ready_For_Lunch;
   when High_Noon   =>   Eat_Lunch;
   when After_Noon  =>   Get_To_Work;
end case;


Visually distinguishing reserved words allows you to focus on program structure alone, if desired, and also aids scanning for particular identifiers.

The instantiation chosen here is meant to be more readable for the experienced Ada programmer, who does not need reserved words to leap off the page. Beginners to any language often find that reserved words should be emphasized to help them find the control structures more easily. Because of this, instructors in the classroom and books introducing the Ada language may want to consider an alternative instantiation. The Ada Reference Manual (1995) chose bold lowercase for all reserved words.

automation notes

Ada names are not case sensitive. Therefore, the names max_limit, MAX_LIMIT, and Max_Limit denote the same object or entity. A good code formatter should be able to automatically convert from one style to another as long as the words are delimited by underscores.

As recommended in Guideline 3.1.4, abbreviations should be project-wide. An automated tool should allow a project to specify those abbreviations and format them accordingly.

3.1.4 Abbreviations




rather than:

Recd_Time or R_Time

But in an application that commonly deals with message formats that meet military standards, DOD_STD_MSG_FMT is an acceptable abbreviation for:



Many abbreviations are ambiguous or unintelligible unless taken in context. As an example, Temp could indicate either temporary or temperature. For this reason, you should choose abbreviations carefully when you use them. The rationale in Guideline 8.1.2 provides a more thorough discussion of how context should influence the use of abbreviations.

Because very long variable names can obscure the structure of the program, especially in deeply nested (indented) control structures, it is a good idea to try to keep identifiers short and meaningful. Use short unabbreviated names whenever possible. If there is no short word that will serve as an identifier, then a well-known unambiguous abbreviation is the next best choice, especially if it comes from a list of standard abbreviations used throughout the project.

You can establish an abbreviated format for a fully qualified name using the renames clause. This capability is useful when a very long, fully qualified name would otherwise occur many times in a localized section of code (see Guideline 5.7.2).

A list of accepted abbreviations for a project provides a standard context for using each abbreviation.


Choose names that clarify the object's or entity's intended use. Ada allows identifiers to be any length as long as the identifier fits on a line with all characters being significant (including underscores). Identifiers are the names used for variables, constants, program units, and other entities within a program.

3.2.1 Names



In a tree-walker, using the name Left instead of Left_Branch is sufficient to convey the full meaning given the context. However, use Time_Of_Day instead of TOD.

Mathematical formulas are often given using single-letter names for variables. Continue this convention for mathematical equations where they would recall the formula, for example:

   A*(X**2) + B*X + C.

With the use of child packages, a poor choice of package, subunit, and identifier names can lead to a visibility clash with subunits. See the Rationale (1995, §8.1) for a sample of the resulting, rather obscure code.


A program that follows these guidelines can be more easily comprehended. Self-documenting names require fewer explanatory comments. Empirical studies have shown that you can further improve comprehension if your variable names are not excessively long (Schneiderman 1986, 7). The context and application can help greatly. The unit of measure for numeric entities can be a source of subtype names.

You should try not to use the same name as an identifier for different declarations, such as an object and a child package. Overusing an identifier in seemingly different name spaces can, in fact, lead to visibility clashes if the enclosing program units are intended to work together.


See Guideline 8.1.2 for a discussion on how to use the application domain as a guideline for selecting abbreviations.

3.2.2 Subtype Names


type Day is
   (Monday,    Tuesday,   Wednesday, Thursday,  Friday,
    Saturday,  Sunday);
type Day_Of_Month    is range      0 ..    31;
type Month_Number    is range      1 ..    12;
type Historical_Year is range -6_000 .. 2_500;
type Date is
      Day   : Day_Of_Month;
      Month : Month_Number;
      Year  : Historical_Year;
   end record;

In particular, Day should be used in preference to Days or Day_Type.

The identifier Historical_Year might appear to be specific, but it is actually general, with the adjective historical describing the range constraint:

procedure Disk_Driver is
   -- In this procedure, a number of important disk parameters are
   -- linked.
   Number_Of_Sectors  : constant :=     4;
   Number_Of_Tracks   : constant :=   200;
   Number_Of_Surfaces : constant :=    18;
   Sector_Capacity    : constant := 4_096;
   Track_Capacity   : constant := Number_Of_Sectors  * Sector_Capacity;
   Surface_Capacity : constant := Number_Of_Tracks   * Track_Capacity;
   Disk_Capacity    : constant := Number_Of_Surfaces * Surface_Capacity;
   type Sector_Range  is range 1 .. Number_Of_Sectors;
   type Track_Range   is range 1 .. Number_Of_Tracks;
   type Surface_Range is range 1 .. Number_Of_Surfaces;
   type Track_Map   is array (Sector_Range)  of ...;
   type Surface_Map is array (Track_Range)   of Track_Map;
   type Disk_Map    is array (Surface_Range) of Surface_Map;
begin  -- Disk_Driver
end Disk_Driver;

The suffixes _Capacity, _Range, and_Map help define the purpose of the above subtypes and avoid the search for synonyms for the sector, track, and surface abstractions. Without the suffixes, you would need three different names per abstraction, one to describe each of the concepts succinctly named in the suffix. This recommendation only applies to certain visible subtypes. Private types, for example, should be given a good name that reflects the abstraction being represented.


When this style and the suggested style for object identifiers are used, program code more closely resembles English (see Guideline 3.2.3). Furthermore, this style is consistent with the names of the language's predefined identifiers. They are not named Integers, Booleans, Integer_Type, or Boolean_Type.

However, using the name of a subtype from the predefined packages is sure to confuse a programmer when that subtype appears somewhere without a package qualification.


This style guide tries to be consistent with the Ada Reference Manual (1995) in use of the terms "type" and "subtype name." In general, a "type" refers to the abstract concept, as in a type declaration, while the "subtype" refers to the name given to that abstract concept in an actual declaration. Thus, what was called a type name in Ada 83 (Ada Reference Manual 1983) is now called a subtype name.

3.2.3 Object Names



Non-Boolean objects:
Today           : Day;
Yesterday       : Day;
Retirement_Date : Date;

Boolean objects:

User_Is_Available : Boolean;        -- predicate clause
List_Is_Empty     : Boolean;        -- predicate clause
Empty             : Boolean;        -- adjective
Bright            : Boolean;        -- adjective


Using specific nouns for objects establishes a context for understanding the object's value, which is one of the general values described by the subtype's name (see Guideline 3.2.2). Object declarations become very English-like with this style. For example, the first declaration above is read as "Today is a Day."

General nouns, rather than specific, are used for record components because a record object's name will supply the context for understanding the component. Thus, the following component is understood as "the year of retirement":


Following conventions that relate object types and parts of speech makes code read more like text. For example, because of the names chosen, the following code segment needs no comments:

if List_Is_Empty then
   Number_Of_Elements := 0;
   Number_Of_Elements := Length_Of_List;
end if;


If it is difficult to find a specific noun that describes an object's value during the entire execution of a program, the object is probably serving multiple purposes. Multiple objects should be used in such a case.

3.2.4 Naming of Tagged Types and Associated Packages



Naming conventions spark "religious wars"; therefore, two different instantiations are presented. The first instantiation integrates the use of object-oriented features. Except for two special cases, it applies the same naming conventions to declarations, independent of whether they use an object-oriented feature:

The second instantiation highlights the use of object-oriented features through special names or suffixes:


The following two-part example from the Rationale (1995, §§4.4.4 and 4.6.2) applies the naming conventions of the first instantiation.

For the first part of this example, assume the type Set_Element was declared elsewhere:

package Abstract_Sets is
   type Set is abstract tagged private;
   -- empty set
   function Empty return Set is abstract;
   -- build set with 1 element
   function Unit (Element: Set_Element) return Set is abstract;
   -- union of two sets
   function Union (Left, Right: Set) return Set is abstract;
   -- intersection of two sets
   function Intersection (Left, Right: Set) return Set is abstract;
   -- remove an element from a set
   procedure Take (From    : in out Set;
                   Element :    out set_Element) is abstract;
   Element_Too_Large : exception;
   type Set is abstract tagged null record;
end Abstract_Sets;
with Abstract_Sets;
package Bit_Vector_Sets is   -- one implementation of set abstraction
   type Bit_Set is new Abstract_Sets.Set with private;
   Bit_Set_Size : constant := 64;
   type Bit_Vector is ...
   type Bit_Set is new Abstract_Sets.Set with
         Data : Bit_Vector;
      end record;
end Bit_Vector_Sets;
with Abstract_Sets;
package Sparse_Sets  -- alternate implementation of set abstraction
   type Sparse_Set is new Abstract_Sets.Set with private;
end Bit_Vector_Sets;

The second part of this example applies the naming convention to mixin packages that support a windowing system:

-- assume you have type Basic_Window is tagged limited private;
   type Some_Window is abstract new Basic_Window with private;
package Label_Mixin is 
   type Window_With_Label is abstract new Some_Window with private;
end Label_Mixin;
   type Some_Window is abstract new Basic_Window with private;
package Border_Mixin is 
   type Window_With_Label is abstract new Some_Window with private;
end Border_Mixin;

The following example applies the naming conventions of the second instantiation, as discussed in Rosen (1995):

package Shape is
   subtype Side_Count is range 0 .. 100;
   type Instance (Sides: Side_Count) is tagged private;
   subtype Class is Instance'Class;
   . . .
   -- operations on Shape.Instance
   . . .
end Shape;
with Shape; use Shape;
package Line is
   type Instance is new Shape.Instance with private;
   subtype Class is Instance'Class;
   . . .
   -- Overridden or new operations
   . . .
end Line;
with Shape; use Shape;
   type Origin is new Shape.Instance;
package With_Color_Facet is
   type Instance is new Origin with private;
   subtype Class is Instance'Class;
   -- operations for colored shapes
   . . .
end With_Color_Facet;
with Line; use Line;
with With_Color_Facet;
package Colored_Line is new With_Color_Facet (Line.Instance);

Sample declarations might look like:

Red_Line : Colored_Line.Instance;
procedure Draw (What : Shape.Instance);

The above scheme works whether you use full names or a use clause. As long as you use the same name for all the specific types (i.e., type Instance) and class-wide types, the unqualified names will always hide one another. Thus, the compiler will insist you use full name qualification to resolve the ambiguity introduced by the use clause (Rosen 1995).


You want to use a naming scheme that is consistent and readable and conveys the intent of the abstraction. Ideally, the naming scheme should be uniform in how it handles the different ways in which tagged types are used to create classes. If the naming convention is too rigid, however, you will write code fragments that appear stilted from a readability point of view. By using a similar naming convention for type extension through derivation and through generic mixin (see also Guideline 9.5.1), you achieve readable declarations of objects and procedures.


A naming convention for classes draws a hard line between object-oriented abstractions and other kinds of abstractions. Given that engineers have been defining abstract data types in Ada 83 (Ada Reference Manual 1983) for over 10 years, you may not want to change the naming convention just for the sake of using type extension with a type. You must consider how important it is to call out uses of inheritance in the overall use of abstractions in your program. If you prefer to emphasize abstraction, in general, over the mechanism used to implement the abstraction (i.e., inheritance, type-extension, and polymorphism), you may not want to impose such a stringent naming convention. You do not hamper quality by favoring a smoother transition in naming conventions from abstractions developed without inheritance to those developed with inheritance.

If you choose a naming convention that highlights the use of object-oriented features and later decide to change the declaration to one that does not use an object-oriented feature, the change may be expensive. You must naturally change all occurrences of the names and must be careful not to introduce errors as you update the names. If you choose a naming convention that prohibits the use of suffixes or prefixes to characterize the declaration, you lose the opportunity to convey the intended usage of the declared item.

3.2.5 Program Unit Names



The following are sample names for elements that compose an Ada program:

Sample procedure names:

procedure Get_Next_Token          -- get is a transitive verb
procedure Create                  -- create is a transitive verb

Sample function names for Boolean-valued functions:

function Is_Last_Item             -- predicate clause
function Is_Empty                 -- predicate clause

Sample function names for non-Boolean-valued functions:

function Successor                -- common noun
function Length                   -- attribute
function Top                      -- component

Sample package names:

package Terminals is               -- common noun
package Text_Routines is           -- common noun

Sample protected objects:

protected Current_Location is      -- data being protected
protected type Guardian is         -- noun implying protection

Sample task names:

task Terminal_Resource_Manager is  -- common noun that shows action

The following sample piece of code shows the clarity that results from using the parts-of-speech naming conventions:

case Current_Token is
   when Identifier =>         Process_Identifier;
   when Numeric    =>         Process_Numeric;
end case;  -- Current_Token
if Is_Empty(Current_List) then
   Number_Of_Elements := 0;
   Number_Of_Elements := Length(Current_List);
end if;

When packages and their subprograms are named together, the resulting code is very descriptive:

if Stack.Is_Empty(Current_List) then
   Current_Token := Stack.Top(Current_List);
end if;


Using these naming conventions creates understandable code that reads much like natural language. When verbs are used for actions, such as subprograms, and nouns are used for objects, such as the data that the subprogram manipulates, code is easier to read and understand. This models a medium of communication already familiar to a reader. Where the pieces of a program model a real-life situation, using these conventions reduces the number of translation steps involved in reading and understanding the program. In a sense, your choice of names reflects the level of abstraction from computer hardware toward application requirements.

See also Guideline 3.2.4 for the use of special-purpose suffixes in packages associated with tagged types.


There are some conflicting conventions in current use for task entries. Some programmers and designers advocate naming task entries with the same conventions used for subprograms to blur the fact that a task is involved. Their reasoning is that if the task is reimplemented as a package, or vice versa, the names need not change. Others prefer to make the fact of a task entry as explicit as possible to ensure that the existence of a task with its presumed overhead is recognizable. Project-specific priorities may be useful in choosing between these conventions.

3.2.6 Constants and Named Numbers


3.14159_26535_89793                                 -- literal
Max_Entries : constant Integer       := 400;        -- constant
Avogadros_Number  : constant := 6.022137 * 10**23;  -- named number
Avogadros_Number / 2                                -- static expression
Avogadros_Number                                    -- symbolic value

Declaring Pi as a named number (assuming a with clause for the predefined package Ada.Numerics in the Ada Reference Manual [1995, §A.5] allows it to be referenced symbolically in the assignment statement below:

Area :=       Pi * Radius**2;       -- if radius is known.

instead of:

Area := 3.14159 * Radius**2;        -- Needs explanatory comment.

Also, Ada.Characters.Latin_1.Bel is more expressive than Character'Val(8#007#).

Clarity of constant and named number declarations can be improved by using other constant and named numbers. For example:

Bytes_Per_Page   : constant := 512;
Pages_Per_Buffer : constant := 10;
Buffer_Size      : constant := Pages_Per_Buffer * Bytes_Per_Page;

is more self-explanatory and easier to maintain than:

Buffer_Size : constant := 5_120;   -- ten pages

The following literals should be constants:

if New_Character  = '$' then  -- "constant" that may change
if Current_Column = 7 then    -- "constant" that may change


Using identifiers instead of literals makes the purpose of expressions clear, reducing the need for comments. Constant declarations consisting of expressions of numeric literals are safer because they do not need to be computed by hand. They are also more enlightening than a single numeric literal because there is more opportunity for embedding explanatory names. Clarity of constant declarations can be improved further by using other related constants in static expressions defining new constants. This is not less efficient because static expressions of named numbers are computed at compile time.

A constant has a type. A named number can only be a universal type: universal_integer or universal_real. Strong typing is enforced for constants but not for named numbers or literals. Named numbers allow compilers to generate more efficient code than for constants and to perform more complete error checking at compile time. If the literal contains a large number of digits (as Pi in the example above), the use of an identifier reduces keystroke errors. If keystroke errors occur, they are easier to locate either by inspection or at compile time.

Independence of literals means that the few literals that are used do not depend on one another and that any relationship between constant or named values is shown in the static expressions. Linear independence of literal values gives the property that if one literal value changes, all of the named numbers of values dependent on that literal are automatically changed.

See Guideline 4.1.4 for additional guidelines on choosing a parameterless function versus a constant.


There are some gray areas where the literal is actually more self-documenting than a name. These are application-specific and generally occur with universally familiar, unchangeable values such as the following relationship:
Fahrenheit := 32.0 + (9.0 * Celsius) / 5.0;

3.2.7 Exceptions


Invalid_Name: exception;
Stack_Overflow: exception;


Naming exceptions according to the kind of problem they are detecting enhances the readability of the code. You should name your exceptions as precisely as you can so that the maintainer of the code understands why the exception might be raised. A well-named exception should be meaningful to the clients of the package declaring the exception.

3.2.8 Constructors



function Make_Square (Center : Cartesian_Coordinates; 
                      Side   : Positive)
  return Square;


Including a word like New, Make, or Create in a constructor name makes its purpose clear. You may want to restrict the use of the prefix New to constructors that return an access value because the prefix suggests the internal use of an allocator.

Putting all constructors in a child package, even when they return access values, is a useful organizational principle.

For information regarding the use of Ada constructors, refer to Guideline 9.3.3.


Comments in source text are a controversial issue. There are arguments both for and against the view that comments enhance readability. In practice, the biggest problem with comments is that people often fail to update them when the associated source text is changed, thereby making the commentary misleading. Commentary should be reserved for expressing needed information that cannot be expressed in code and highlighting cases where there are overriding reasons to violate one of the guidelines. If possible, source text should use
self-explanatory names for objects and program units, and it should use simple, understandable program structures so that little additional commentary is needed. The extra effort in selecting (and entering) appropriate names and the extra thought needed to design clean and understandable program structures are fully justified.

Use comments to state the intent of the code. Comments that provide an overview of the code help the maintenance programmer see the forest for the trees. The code itself is the detailed "how" and should not be paraphrased in the comments.

Comments should be minimized. They should provide needed information that cannot be expressed in the Ada language, emphasize the structure of code, and draw attention to deliberate and necessary violations of the guidelines. Comments are present either to draw attention to the real issue being exemplified or to compensate for incompleteness in the sample program.

Maintenance programmers need to know the causal interaction of noncontiguous pieces of code to get a global, more or less complete sense of the program. They typically acquire this kind of information from mental simulation of parts of the code. Comments should be sufficient enough to support this process (Soloway et al. 1986).

This section presents general guidelines about how to write good comments. It then defines several different classes of comments with guidelines for the use of each. The classes are file headers, program unit specification headers, program unit body headers, data comments, statement comments, and marker comments.

3.3.1 General Comments



The structure and function of well-written code is clear without comments. Obscured or badly structured code is hard to understand, maintain, or reuse regardless of comments. Bad code should be improved, not explained. Reading the code itself is the only way to be absolutely positive about what the code does; therefore, the code should be made as readable as possible.

Using comments to duplicate information in the code is a bad idea for several reasons. First, it is unnecessary work that decreases productivity. Second, it is very difficult to correctly maintain the duplication as the code is modified. When changes are made to existing code, it is compiled and tested to make sure that it is once again correct. However, there is no automatic mechanism to make sure that the comments are correctly updated to reflect the changes. Very often, the duplicate information in a comment becomes obsolete at the first code change and remains so through the life of the software. Third, when comments about an entire system are written from the limited point of view of the author of a single subsystem, the comments are often incorrect from the start.

Comments are necessary to reveal information difficult or impossible to obtain from the code. Subsequent chapters of this book contain examples of such comments. Completely and concisely present the required information.

The purpose of comments is to help readers understand the code. Misspelled, ungrammatical, ambiguous, or incomplete comments defeat this purpose. If a comment is worth adding, it is worth adding correctly in order to increase its usefulness.

Making comments visually distinct from the code by indenting them, grouping them together into headers, or highlighting them with dashed lines is useful because it makes the code easier to read. Subsequent chapters of this book elaborate on this point.

automation notes

The guideline about storing redundant information in comments applies only to manually generated comments. There are tools that automatically maintain information about the code (e.g., calling units, called units, cross-reference information, revision histories, etc.), storing it in comments in the same file as the code. Other tools read comments but do not update them, using the information from the comments to automatically generate detailed design documents and other reports.

The use of such tools is encouraged and may require that you structure your header comments so they can be automatically extracted and/or updated. Beware that tools that modify the comments in a file are only useful if they are executed frequently enough. Automatically generated obsolete information is even more dangerous than manually generated obsolete information because it is more trusted by the reader.

Revision histories are maintained much more accurately and completely by configuration management tools. With no tool support, it is very common for an engineer to make a change and forget to update the revision history. If your configuration management tool is capable of maintaining revision histories as comments in the source file, then take advantage of that capability, regardless of any compromise you might have to make about the format or location of the revision history. It is better to have a complete revision history appended to the end of the file than to have a partial one formatted nicely and embedded in the file header.

3.3.2 File Headers



--      Copyright (c) 1991, Software Productivity Consortium, Inc.
--      All rights reserved.
-- Author: J. Smith
-- Department:System Software Department
-- Revision History:
--   7/9/91 J. Smith
--     - Added function Size_Of to support queries of node sizes.
--     - Fixed bug in Set_Size which caused overlap of large nodes.
--   7/1/91 M. Jones
--     - Optimized clipping algorithm for speed.
--   6/25/91 J. Smith
--     - Original version.


Ownership information should be present in each file if you want to be sure to protect your rights to the software. Furthermore, for high visibility, it should be the first thing in the file.

Responsibility and revision history information should be present in each file for the sake of future maintainers; this is the header information most trusted by maintainers because it accumulates. It does not evolve. There is no need to ever go back and modify the author's name or the revision history of a file. As the code evolves, the revision history should be updated to reflect each change. At worst, it will be incomplete; it should rarely be wrong. Also, the number and frequency of changes and the number of different people who made the changes over the history of a unit can be good indicators of the integrity of the implementation with respect to the design.

Information about how to find the original author should be included in the file header, in addition to the author's name, to make it easier for maintainers to find the author in case questions arise. However, detailed information like phone numbers, mail stops, office numbers, and computer account user names are too volatile to be very useful. It is better to record the department for which the author was working when the code was written. This information is still useful if the author moves offices, changes departments, or even leaves the company because the department is likely to retain responsibility for the original version of the code.


With modern configuration management systems, explicitly capturing version history as header comments may be superfluous. The configuration management tool maintains a more reliable and consistent (from a content point of view) change history. Some systems can re-create earlier versions of a unit.

Chapter 3 continued