Design by contract is a technique for specifying the requirements and deliverables between clients and suppliers in an OO system. Contracts have been implemented as part of the Eiffel language. This paper examines the possible ways that contracts can be added to Java, and explores in detail one particular mechanism based on reflection. The advantages and disadvantages of this mechanism are discussed.
Design by contract is a technique introduced by Meyer and implemented in Eiffel [Meyer]. It represents a way of specifying the requirements and deliverables between clients and suppliers of services in an OO system. A contract allows a method to specify what conditions need to be true before the method can function correctly, and in turn it specifies what will be true after it has executed. By ``publishing'' its contract, a method can then avoid often torturous validation code.
This promotes a programming style that is the opposite of the extremely defensive style seen in much systems programming. For example, instead of the C style of code such
if ((fp = fopen("file", "r")) == NULL) {
fprintf(stderr, "cant open file\n");
exit(1);
}
...
one might see (in informal notation)
require: "file" can be opened in mode "r"
fp = fopen("file", "r");
...
The runtime system is expected to check assertions, and if they fail then it will take appropriate action. This action may be to throw an exception (in languages that support exceptions) that may be caught by programmer code or left to the default runtime action.
Contracts may form documentation only or may be part of the runtime environment. Typical Eiffel systems allow this to be controlled by a compile-time switch, whereby code can be generated to enforce contract checking or to ignore it. Eiffel is unfortunately atypical among programming languages, in that it includes the option of runtime type checking. Most languages can only leave it to the documentation level, and even then, only in an informal manner.
Java has arisen as a major OO language, after a long gestation in private and a short period of visibility. Despite being developed over a period of years, when it was made public there were several unfinished features that were simply discarded. Amongst these, according to Gosling [Gosling], were assertions and a suitable contract mechanism. Now that Java is a standardised language with a formal mechanism for making changes, it may prove difficult or time consuming to incorporate these features into the language.
This paper discusses some of the possibilities that may be explored in adding contracts to Java, and reports on an experimental implementation using the ``reflection'' techniques available in Java. Reflection is a Java API that allows a program to examine classes and methods at runtime, and allows methods to be constructed and executed. It is shown that using the Reflection API is a feasible way of implementing contracts, but slow using current Java implementations. It also throws up several language design issues that cause difficulty in using the reflection technique, but which were probably unavoidable given the development environment and requirements.
It is not hard to devise syntax changes to Java with a
corresponding semantic meaning. For example, a
pop()
method for a Stack
could
have its contract specified as
public Object pop()
require(! empty())
ensure(result == old getTOS() &&
getSize() == old getSize() - 1)
{
...
}
The semantics would be as in Eiffel - there is little need to rework this.
This would require changes to both the syntax and semantics
of the Java language, and these are under the control of
Sun as a standards organisation.
For example, it would require introduction of keywords such
as require
, ensure
and
old
, and a change in syntax to allow clauses to
be specified for methods.
This may occur in time,
but if there is a desire to use contracts now, it may be
too long to wait.
A mechanism that would work with no changes to language or involving any other techniques simply uses a style that each programmer conforms to. That is, each programmer includes the require clause on entry to a method, and calls the ensure code on exit.
Performing the require checking is easy. For example,
the pop
method would just make a call to the
precondition code:
public Object pop() {
if (empty())
throw new RequireException();
...
}
Performing ensure checking is a little harder.
Firstly, there is no single return
point from
a Java method. Fortunately, each method can have a
finally
section which is called as the method
returns. Postcondition code can be placed in the
finally
section. Secondly, it is often necessary
to refer back to values as they were on entry to the method.
This can only be done by manual coding, saving the old values
on entry to the method. Finally, the return
value of a method may be needed in this checking. Whereas
Eiffel returns values in the variable Result
,
there is no such variable in Java. This, or a variable
playing a similar role, would need to be added as a
convention.
This would lead to the pop
method looking like
public Object pop() {
// require
if (empty())
throw new RequireException();
// capture values
Object oldTOS = getTOS();
int oldSize = getSize();
Object result = null;
try {
// implementation
result = s[--size];
return result;
} finally {
// ensure
if (! (result == oldTOS &&
getSize() == oldSize - 1))
throw new EnsureException();
}
}
This has both advantages and disadvantages. The advantage is that it is an enforceable programming style that adds contract checking. Simple tools could be built to extract the contract from the code for documentation purposes.
The disadvantages are firstly that it is a little cumbersome, and coding standards may be observed in the breach unless rigorously maintained. Secondly, it is not possible to turn contract checking ``on'' and ``off' like it is with Eiffel at compile time. It is always on if present, off if absent.
A more serious problem is that it does not address inheritance. The semantics of contracts is that they are also inherited. The preconditions are or-ed together, and the postconditions are and-ed together. To perform this by hand in a large system with a lot of inheritance would be extremely difficult and time consuming.
A Java program exists in two static formats and is subject to a small number of operations. The two formats are source code and byte code as class files. The operations that can be performed include interpretation, transformation, compilation and reflection. (While other formats such as native code format are possible via native code compilers, they violate the ``write once, run anywhere'' paradigm and are not yet common except in transient form by some ``Just In Time'' compilers.) Contract checking may be introduced during any of the operations:
A method of using reflection is considered in this paper, and contrasted against these other possibilities.
The iContract system by Kramer [Kramer] uses a preprocessor to generate Java code similar to that in section three which supplies contracts. The contract is given using extra comment keywords, similar in format to those used by the documentation tool JavaDoc:
/**
* @pre !empty()
* @post result == old getTOS() && \
getSize() == old getSize() - 1)
*/
Object pop()
{ ... }
If the preprocessor is not run, contracts are not inserted into the code, and it runs like a standard Java program. If the preprocessor is run, it generates new Java code with the additional checks in place.
iContract is a fully functional mechanism to implement assertions, but it relies on a preprocessor external to any current Java compilation and runtime environment. It certainly provides a way of implementing contracts. Adding contracts using this system requires access to the source code of the classes. It cannot be added post-hoc to class files.
There is little else reported in the Java literature on
assertions and contracts. The JDC Technical Tips column
for February 1998 [JDCTips] carries a short article on
an Assert
class that can be used in programs.
It does not address the full complexities of contracts,
though.
Payne et al [Payne] discuss the problem of contracts in Java at some length. Their final proposal only covers the most basic of contract systems. It does not deal with ``old'' values, nor does it handle inheritance.
This paper considers a further mechanism for handling contracts. In this, each method call is passed through a suitable interpreter at runtime. This interpreter can perform precondition checking before calling the method, and can perform postcondition checking on completion. This is discussed in detail in the following sections.
Java does support a measure of interpretive execution, but this needs to be called specifically by the programmer rather than being accessible in some ``magic'' way. Java has a ``reflection'' mechanism [ReflectionAPI] that allows a program to examine classes and objects and find out information about them. For example, one can query a class about all of its methods and find out the method names and parameters. In addition, one can also construct a method call for an object and then invoke that method.
Let us develop how this can be used to supply an interpretive layer to method calls. Instead of say, doing
Stack s = new Stack();
s.push(obj);
we want to write something like
Stack s = new Stack();
execute(s.push(obj));
Now execute()
will not be a method of any of the
application classes: it may need to be called for any
method of any class.
So we need a class, called say Contract
with
a static
method execute()
.
This is the only public method
needed for this class. It may throw exceptions, say
a RequireException
and an
EnsureException
.
The parameters to execute()
are a little messy.
We cannot simply write s.push(obj)
because this
will simply evaluate the method before we get a chance to
interpret it. We need to pass the object as one parameter
and the name of the method as another. This will allow
the introspection/reflection mechanism to find the method
and execute it. The parameters to the method call,
on the other hand, must
be evaluated at the time of the call. We need a list of
method parameters, evaluated immediately.
The execute()
method will then
have three parameters:
the object with the method, the method as a string, and an
array of evaluated parameters of the method.
The call will look like
Object parameters[] = new Object [] {obj};
Contract.execute(s, "push", parameters);
This constructs a parameter array with one element,
obj
, and then calls the method
execute()
with the object, the string name of the
method and the parameter array.
This syntax is not appealing, but unavoidable using this
mechanism.
The method execute()
must perform require
and ensure checking, as well as executing the
method. In structured English, the algorithm is
execute(obj, method, args)
find the class of obj
find the requires clause, the ensures clause
and the formal parameter list of the method
for this class
substitute the actual parameters for the formal
parameters in the requires clause
evaluate the requires clause, and throw an exception
if false
for each "old" component of the ensures clause, evaluate
its initial value (after substitution of parameters)
find the method for this class
execute the method after substitution of parameters
and save the result
evaluate the ensures clause after substitution of
parameters and result, and throw an exception
if false
return the result
The reflection API is used to retrieve the method for the
class, and to evaluate it with the actual parameters
substituted. There is a nuisance implementation detail here.
The Reflection API has two relevant methods:
Class.getDeclaredMethod()
and
Class.getDeclaredMethods()
.
The first of these requires
the classes of actual parameters to exactly match
those of the formal parameters. So an actual parameter
of type Integer
will fail to find a method
with formal parameter of type Object
, although
it is perfectly valid to make such a call.
The second, getDeclaredMethods()
returns a
list of all methods (including inherited methods),
and a slower search must be made through all methods using
this,
trying each class and its superclasses to find a suitable
method. This is slower than the technique actually used
by the JVM interpreter.
In order to check a contract for a method call of a class,
it must be located. This location will need to be separate
from the class file, since we are making no changes to
the Java source files, and hence no changes to the compiled
files. It could be stored in some central registry, in
jar
files or in directories. The particular
method is not particularly important.
It is important to note that this method of checking contracts does not require access to source code, unlike transformation methods. In particular, contracts can be added to library classes if desired.
Java has a set of primitive types such as int
and boolean
. These types are not classes.
It is easy to see why not: Java shares many features with
C, and a simple and efficient system for these common types
would be preferable to a heavier class system. If a class
is required, there are the classes Integer
,
Boolean
, etc which act as ``wrappers''
around the primitive types. This would not be a problem
in other ``purer'' languages such as SmallTalk or Eiffel.
Methods can have a mix of primitive types and classes for parameters. However, if one wants to form a list of the parameters, such as an array of parameters, this causes a problem: an array can be an array of objects, or of one particular primitive type, or of another primitive type, and so on. This can only be resolved by wrapping the primitive types in an object of their corresponding class and using this object.
This can potentially lead to ambiguities.
For example, suppose a method
can take an int
parameter or an
Integer
parameter:
void f(int n);
void f(Integer N);
A list of parameter values would be a one-element list of
Integer
objects in both cases.
The Reflection API handles this in a number of ways:
For example, the list of argument classes for the first
method, with an int
parameter,
contains the class object
java.lang.Integer.TYPE
, whereas for the
second method with an Integer
parameter contains
Integer
. Thus by looking at the class types,
the Reflection API can determine whether or not to unwrap
an object.
This wrapping and unwrapping, and the introduction of special class objects, is clearly cumbersome and inelegant. It is too late to change to change this fundamental language decision now though - it is part of Java's inheritance that is not the best for some purposes.
Interpretation usually carries penalties of execution time overheads. Typically an interpreted system is an order of magnitude slower than a compiled system. The mechanism used here is not full interpretation, but only interpretation of method calls. Thus one may hope that the speed penalty is not so severe. It turns out not to be the case.
A Stack class was implemented with the following contracts:
/**
* ensure: obj == getTOS() && getSize() == old getSize() + 1
*/
public void push(Integer obj);
/**
* require: ! empty()
* ensure: result == old getTOS() && \
getSize() == old getSize() - 1
*/
public Integer pop();
Pushing a number of elements onto the stack and then popping them all off was performed. This was done on 150Mhz Pentium running Linux, using the port of Sun's JDK 1.1.3. The computer had 32M RAM and 64M of swap-space. For each number of elements, three tests were performed:
push()
and pop()
but no contract checking
push()
and pop()
with the contracts also
interpreted as a boolean expression (involving
interpretive calls to empty()
,
getTOS()
and getSize()
)
The results are shown in the table (times are in seconds):
Number of elements, n | no interpretation | method call interpretation | contract interpretation |
---|---|---|---|
1 | 0.98 | 1.00 | 1.38 |
10 | 0.98 | 1.00 | |
100 | 1.15 | 1.13 | 1.76 |
1000 | 0.99 | 2.39 | 8.06 |
2000 | 1.00 | 3.83 | 15.70 |
3000 | 1.01 | 5.14 | 22.56 |
4000 | 1.02 | 6.96 | 30.27 |
5000 | 1.02 | 8.27 | 37.32 |
6000 | 1.04 | 9.77 | 45.96 |
7000 | 1.42 | 11.07 | 52.44 |
8000 | 1.06 | 12.68 | 60.26 |
9000 | 1.07 | 14.20 | 69.17 |
10000 | 1.07 | 15.45 | 76.45 |
20000 | 1.18 | 36.31 | 176.00 |
30000 | 1.28 | 59.27 | 310.72 |
40000 | 1.48 | 98.77 | 512.21 |
50000 | 2.56 | 126.70 | 668.29 |
60000 | 4.64 | 153.83 | 748.67 |
70000 | 6.20 | 183.66 | 976.09 |
80000 | 8.64 | 214.05 | 1130.75 |
90000 | 10.89 | 244.37 | 1273.59 |
100000 | 13.32 | 270.52 | 1429.75 |
There is a fixed startup time of about one second. Changing ratios are probably caused by the garbage collector being invoked for the interpretive method at smaller stack sizes than for the compiled version, and even earlier for the contract checking version. Allowing for this, interpretation using the Reflection API is about 20-30 times slower than compiled code for the Linux JVM runtime system. Adding contract checking multiplies this by a further factor of five.
The programs were also run under the JBuilder development environment on Windows95 on the same computer. Only two tests were run, shown in the following table:
Number of elements, n | no interpretation | method call interpretation | contract interpretation |
---|---|---|---|
10000 | 0.22 | 14.29 | 73.33 |
100000 | 43.78 | 226.74 | 1187.20 |
These figures are sufficiently similar to those of the previous table to leave the conclusions unaltered by use of a different JVM interpreter.
Measurements were also made of the search on parameter
types caused by use of getDeclaredMethods()
instead of getDeclaredMethod()
(which assumes
as exact match of parameter types). By jigging the code to
match up types
in call and declaration, the getDeclaredMethod()
could be used in special cases.
This effectively places the search for
matching method in the JVM interpreter, which is written
in C, rather than producing a list which has to be searched
by Java. This did speed things up, but only by a factor
of two. Of course, here the inheritance hierarchy is short
and the number of methods is small, so this could be more
of a factor for more complex classes.
An attempt was made to run the iContract system on the Stack to compare the interpretive versus the transformation method. Unfortunately, iContract threw an exception and died during transformation. The transformation was however performed by hand: when tested it showed little difference from the non-interpreted version, showing that the transformation method had far less overhead than interpreted methods.
The interpretive system discussed here has been built as ``proof of concept''. It lacks a full expression parser, but one is being built using the Java CC compiler-compiler [JavaCC]. Issues such as location and retrieval of contracts have not been finalised. Inheritance has not been dealt with, although it should not pose any problems. Once these issues are resolved it will be possible to determine if execution speed is a problem for larger scale applications.
Future work will continue on this project if appropriately funded. This may be conditional on factors such as a timeline for introduction of contracts as core components of Java.
A range of alternative mechanisms for the introduction of contracts into Java has been discussed. A method using the Java Reflection API has been implemented to ``proof of concept'' level, and initial timings produced.
This showed that this technique is feasible, but slow. To achieve an acceptable speed may require internal work on the Reflection implementation within the Java Virtual Machine interpreter.
On the other hand, this technique does not require access to source code in order to add contracts to methods, and the contracts may be stored in a variety of ways.