Developing Systems using Perfect Developer
Version 3.0, December 2004
This document is intended as an introduction to software development using Perfect Developer and the Perfect language.
An introduction to using the Perfect language can be found in the document Getting Started With Perfect. A detailed description of Perfect and the terms used in this document can be found in the Language Reference Manual. An introductory tutorial can be found on the Escher Technologies web site
We recommend that software development using Perfect Developer is performed stepwise as follows:
Requirements capture may be performed using any standard method, or informally for small systems. Where possible, formulate requirements in ways that can be translated directly into data descriptions or properties.
The Perfect language does not currently include an adequate notion of time for describing real-time interactions between the software and the outside world. Therefore, reactive systems cannot be fully described in Perfect. To get round this problem, we suggest describing the software system as follows:
In many cases it will be possible to define just one event (a "new input available" event) together with a single Perfect method that interrogates the input and performs the required action.
Where the software system is to control or update external devices, such devices should be modeled in Perfect as external classes, so that properties of the complete system can be verified.
The software specification should be developed in an object-oriented manner. It may be desirable to use a modeling language with tool support (e.g. UML).
The initial model should be as simple as possible. In particular, the following rules should be obeyed:
Along with the abstract data, the methods should be described. The following guidelines are suggested:
Requirements and expected behaviors should be represented in the specification as properties (for example, properties of invoking methods in a specified order). In this manner, the system may be proven to meet the requirements (provided that the requirements were correctly captured and translated to properties).
A valid specification must be consistent and satisfiable. Perfect Developer may be used to verify that the specification is consistent. It does this by generating proof obligations for the specification and then attempting to discharge (i.e. prove) these.
In some cases, Perfect Developer may report that an obligation has been refuted. In this case, the specification is known to be in error and should be corrected.
Where Perfect Developer reports that it was unable to discharge a proof obligation, this may be due to any of several reasons:
When a proof attempt fails, the tool will report the simplest expression that it was unable to prove. If failure was due to an incomplete specification, it may be that this expression represents an additional precondition or class invariant that should have been included.
Satisfiability is in general hard to prove, and in any case, knowing that a specification is satisfiable is of limited value without knowing how to satisfy it. Perfect Developer does not attempt to prove satisfiability directly, however the specification is known to be satisfiable if the tool is able to generate code to implement it, or if the user is able to provide an implementation which the tool is able to verify.
Perfect Developer can generate code in either C++ or Java. Later versions may also generate Ada.
Where the system is unable to generate code directly from the specification, this may be due to one of the following:
The specification should be checked to ensure it has been written as intended. It may then either be rewritten in a more soluble form (provided this can be done without loss of clarity), or a solution may be added in the form of an implementation.
Reactive systems respond to specified external events by taking specified actions.
The actions to be taken can be specified as Perfect schemas, but it is currently not possible to specify external events in Perfect. The code that hooks the event and connects it with a Perfect schema must therefore be developed in another way.
It is also possible to develop this code in Perfect by writing a schema having an incompletely-specified postcondition and an implementation. The implementation comprises a sequence of statements similar to the statements that would be written in a programming language and may include calls to an external library to wait for an event or query the nature of an event. The postcondition merely identifies the variables that the implementation may change.
This method of code development should be used sparingly, since in the absence of a full specification, verification of the implementation is limited to checking that all relevant preconditions and assertions are satisfied.
For interactive applications, it will be necessary to develop a user interface. Given a suitable library, it is perfectly feasible to describe a user interface in Perfect; however it may be more productive to use an interactive screen layout tool that generates a skeleton program directly in the target language. Calls to invoke the required Perfect schemas can then be embedded in the skeleton.
The prototype may be refined to improve performance. The essence of refinement is to improve data representation and algorithms employed while leaving the external specification of the classes (i.e. the client view) unchanged.
To avoid wasting effort in unprofitable refinements, it is important to identify areas which need to be refined. Prime candidates for refinement will be operations for which the time taken grows rapidly with increasing volumes of data; however, even in these cases, a check should be made on the time taken for the maximum data size, to see if refinement is worthwhile.
Both data structures and code may be refined. Because refinement of data structures typically requires new implementation code as well, data refinements should be investigated and implemented before code refinements.
If the original design is sound, refinements should not involve changes in specification.
Data refinement consists of adding internal variables to abstract classes. This is why any data structure that it likely to be refined should be specified as an abstract class in the first place.
Every internal variable will serve one of two purposes.
Internal variables may be used to change the representation of some or all of the abstract data. The abstract data is then redefined in terms of the internal variables.
Typically, this form of representation change is used where the abstract data is specified as a simple Perfect collection type (possibly with a constraint or class invariant) and the internal representation allows for the most frequently used operations to be performed more quickly.
Abstract variables redefined in this way may not also be redeclared as confined or interface selectors. If they are redeclared as confined or interface functions, such functions are designated ghost functions, therefore they may only be referred to in specifications and such specifications cannot be automatically implemented.
Naturally, it would be possible to avoid using an internal section by replacing the original abstract variables with new ones corresponding to the new representation. This is normally bad practice because it has the following effects:
Having redefined an abstract variable in terms of internal data, it will be necessary to re-implement all class members (other than ghost members) that refer to that abstract variable.
When abstract data is redefined in terms of internal data, the developer must choose whether to re-implement the equality operator. Because equality between instances of a class is defined as equality of the corresponding abstract variables, and abstract variables that are redefined in terms of internal variables become ghost functions, equality must either be declared as ghost itself, or re-implemented. Equality having ghost status may be unimportant, depending on the purpose of the class. Alternatively, if there is a one-to-one mapping between values of internal variables and values of the redeclared abstract variables, the developer may very easily re-implement equality as equality of the internal variables.
We previously advised that an abstract class specification should not include redundant data, and that for each value that is frequently needed and can be calculated from class data, a function should be specified.
Where the evaluation of such a function is expensive, the value can be stored as a redundant internal variable. The function is then re-implemented by statements to retrieve the value of this variable. Either the value can always be kept (in which case it must be initialized by the constructor and updated in any modifying member schema), or a Boolean flag can be maintained to indicate whether the value is up to date (in which case the declaration of the redundant variable should be enclosed in a when construct, guarded by the flag).
Code refinement consists of attaching implementations to postconditions and to values following the defined-as symbol, or improving the existing implementations.
It is not necessary to refine to the most basic code possible. For example, an implementation might make use of complex predicate statements, which the system can be left to implement. At a later date, some of those predicate statements could themselves be given implementations.
Many code refinements will be performed in conjunction with a corresponding data refinement. Code refinements that may be independent of data refinement include:
Perfect Developer should be used to verify the refinements. Where the system fails to verify that an implementation satisfies the specification, the following may help:
The amount of testing carried out will depend on the degree to which the system has been verified. Some users of formal methods choose to omit unit testing altogether.
For a fully verified system, the main purpose of testing is to identify the following types of error:
Debugging should not be necessary in a fully verified system. Where complete verification was not achieved, Perfect Developer provides the following support for debugging:
© 2001-2004 Escher Technologies Limited. All rights reserved.