Ada95 Lessons Learned

To produce "good" Ada95 code using Object Oriented methodology is certainly not obvious. Despite the comprehensive documentation available, there are many pitfalls along the way. This document is the fruit of the experience gained through wanderings, tries and failures.

                   There is one problem with people who know everything:
                           They don't learn anything !

Table of Contents:

  1. How to declare a class.
  2. Naming convention.
  3. Object Unity.
  4. Portability issues.
  5. Generic for Parameterized Classes.
  6. Beware of the inheritance.
  7. Beware of Controlled Objects.
  8. Weakness of the Ada 95 language.

  1. How to declare a class.
  2. Almost everybody (but not everybody) agrees that a class in Ada 95 corresponds to a package containing a main type. In most of the cases the main type is declared private or with a private extension. Other types may also be declared within a package specification, we will refer to them as regular types (not class), they are used for interfacing purposes (when public) or to define internal data structures (when private).
    with Angle;
    package Coordinates is
       type Object is ...   -- Class type (either private or derived with private extension).
       type Geographic is record  --  Regular type (public)
          Latitude : Angle.Radian;
          Longitude : Angle.Radian;
       end record;
    end Coordinates;
    That is the basics, but still, the programmer has many choices: Tagged ? Controlled ? ... Let's discuss the details further:

  3. Naming convention.
  4. This note intends to be complementary to the Ada 95 Quality and Style Guide (Chapter 3.2). It provides naming rules proper to Object Oriented Development using Ada 95.

  5. Object Unity.
  6. Often, objects belonging to the same class have unique identifier (or key) attributes. If these identifiers are specified at the initialization of the object, how can we insure that identifier duplication is avoided ?
    First Solution:
    The class itself keeps track of the objects initialized (internal collection). The initialization of an object is done through an Initialize routine (see: The myth of object creation.), therefore Initialize can check if the identifier is already in use by looking to an internal table containing the identifiers used. The problem with this solution is that it does its job too well. Indeed, it prevents transitory duplication of the objects and independent duplication for backup or simulation purposes.
    More flexible solution:
    The unity requirement is allocated to an external collection. Instead of allocating the unity requirement to the class itself, it is always possible to allocate this requirement to a collection containing the instances of the class. The check for object duplication is done when the object is inserted into the collection. In a lot of cases this external collection makes sense, therefore, there is no extra work associated with the duplication check.
    Each instance of the class Car contains a plate number that is supposed to be unique. A possible implementation might be:
    package Car is
       type Plate_Number is ...
       -- The Initialize routine doesn't check if the plate number
       -- is duplicated.
       procedure Initialize(
          The_Car : in out Car.Object'Class;
          With_Plate : in Car.Plate_Number);
          ... );
    end Car;
    -- A collection is defined.
    package Car_Table is new Table(
       Item => Car.Reference,
       Id => Car.Plate_Number,
    procedure Main is
       My_Car : Car.Reference := new Car.Object;
       Car.Initialize(The_Car => My_Car.all,
                      With_Plate => My_Plate,
          The_Item => My_Car,
          Into => The_Car_Table,
          Using_Policy => Raise_Exception); -- An exception is raised if 
                                            -- the plate number is  
                                            -- duplicated.

  7. Portability issues.

  8. Generic for Parameterized Classes.
  9. In Ada, a parameterized class maps directly to generic package. Some notation allows parameterized class to add new methods (Booch), with others (UML) parameterized classes have to be derived first in order to add (or overload) class methods. Ada generics correspond to that last case. If you have to modify or to add any specific behavior to your parameterized class, an intermediate package is used for derivation purpose. It's a good idea to adopt a consistent naming convention for these packages (ie. <Class_Name>_Instance). Also, all the exceptions and regular types of the intermediate package are renamed in order to make the extension transparent to the client packages.
    At first, methods defined within List are satisfactory for Employee_List:
    with List, Employee;
    package Employee_List is new List(Item => Employee.Object);
    Later (incremental process), the method Search is added to the class Employee_List.
    with List, Employee;
    package Employee_List_Instance is new List(Item => Employee.Object);
    with Employee_List_Instance, Personne, Employee;
    package Employee_List is
       type Object is new Employee_List_Instance.Object with private;
       type Reference is access all Object'Class;
       type View is access constant Object'Class;
       Nil : constant Object;
       -- Rename all regular types and exceptions of Employee_List_Instance.
       First_Of_Empty_List_Error : exception 
          renames Employee_List_Instance.First_Of_Empty_List_Error;
       subtype Length is Employee_List_Instance.Length;
       -- Add the new method.
       Search_Not_Found_Error : exception;
       procedure Search(The_Employee : out Employee.View;
                        With_Name : in Personne.Name;
                        Within : in Employee_List.Object);
    end Employee_List;

  10. Beware of the inheritance.
  11. When a class is derived, all the methods primitive to its ancestors are inherited. But, sometimes, some of the inherited methods will not work for the derived class, (ie. They don't take into account new attributes), it is necessary to overload them. Forgetting to overload a method that should have been, is one of the most common errors, it cannot be found for sure during unit testing, only a thorough inspection of all the inherited methods in the context of the derived class may detect this oversight. In Ada 95, controlled objects are first citizen candidates for such neglect, any controlled attribute added to a derived class will force to overload the inherited Adjust and Finalize routines (at the minimum, they will have to call the adjustment and finalization for the new attribute).
    Here, the class Position is derived from Coordinates, but the method Distance_Between has to be overloaded to take into account the new altitude component of the position vector.
    package Coordinates is
       type Object is tagged private;
       function Distance_Between(Left : in Coordinates.Object;
                                 Right: in Coordinates.Object) 
          return Distance.Object;
    end Coordinates;
    package Position is
       type Object is new Coordinates.Object with private;
       function Distance_Between(Left : in Position.Object;  -- Overload of
                                 Right : in Position.Object) -- Coordinates.Distance_Between
          return Distance.Object;
    end Position;

  12. Beware of Controlled Objects.
  13. The implementation of Finalize and Adjust for the controlled objects is very delicate and there are numerous pitfalls along the way. Also, the debugging is delicate because any exception raised within these procedures during an assignment generates a cryptic Program_Error. Here is a list of hints which should help in implementing bug-free Adjust and Finalize routines.

  14. Weakness of the Ada 95 language.

Comments To: Jean-Marie Dautelle
Revision: January 22, 1998
Copyright © 2000 The Dautelle Family.