Considerations when writing class invariants
Introduction
Class invariants can be an invaluable aid to developing bug-free code as discussed in Prof. Meyer's article Why not program right?, however there is a common pitfall in designing invariant conditions that should be kept in mind. It is easy to inadvertently write a precondition that gets in the way of an especially useful design pattern, that I like to call the "Conforming Instance Factory pattern". This "invariant gotcha" is mainly the fault of the compiler but in the absence of a change to the compiler can be remedied by a change to class design.
The Conforming Instance Factory Pattern
The Conforming Instance Factory Pattern is exemplified by the Eiffel-Loop class EL_OBJECT_FACTORY. This class offer a variety of ways to return a new initialised instance of a class from a given set of types conforming to a common base type. An example of it's use can be found in routine new_codec_by_id
from class EL_ZCODEC_FACTORY. A simplified source listing is as follows:
Incognizant Invariants
Now to analyse what is happening in routine new_codec_by_id
and how incognizant invariants can sometimes "get in the way" of what it is trying to achieve. What this routine essentially does is make a call to
class REFLECTOR
new_instance_of (type_id: INTEGER_32): ANY
and then cast the result to the type specified as the generic parameter G
to EL_OBJECT_FACTORY, before initializing the object by calling a make routine from the parameter type, in this case {EL_ZCODEC}.make.
But a problem arises if EL_ZCODEC contains an invariant because the invariant will be called before the make routine has had a chance to initialize the class, invariably giving rise to an invariant violation.
Compiler Fix
A smarter compiler would realise that this agent is being applied as an initialization routine and would generate code that does not call the invariant condition prematurely.
Library Fix
But in the absence of a smarter compiler we can take a queue from the Vision2 class EV_ANY which suggests a solution to this problem via the routine is_initialized. Basically we can write all invariants using the following form: