Interfaces Contribute to Software Risk
When I was at NASA, I did some research on software risk. The most common identifiable cause of software failures, apart from “functional defect” (i.e. the code was perfectly valid, but just did not implement the right thing) is “interface defect”, i.e. a failure in the interfaces between components.
Interface Problems are Worse in Large Multilanguage Systems
The reason why interface defects are particularly common is probably two-fold: compilers and other tools do not provide good support for preventing this kind of cross-component defect, components tend to correspond to the boundaries between the work of different people, and interface defects are due to lack of coordination between different people.
A large system – especially one providing a consumer web interface – is often composed from many components, written in several different languages, some of them running on a single machine and others broken into client and server parts. The system and its components may be evolving quite rapidly and different parts are usually written by different people. The checks provided by compilers are only of benefit on a component-by-component basis and so are not very helpful in this situation.
Check Types + Properties At Runtime
The kinds of type-checking available in programming languages are generally limited by the need to check types statically, which imposes a severe restriction on the notion of “type”. Many types in practice would be well represented by specifying an underlying data type plus properties which must hold of valid elements, e.g. lists (underlying type) whose elements are pairs (property), arrays of strings (underlying type) which are sorted (property). Such types are known as dependent types. Dependent types are not supported by most programming languages, because their use sacrifices automation of static type inference and checking.
Although dependent types cannot be checked statically, or used explicitly in most programming languages, a lot of the benefit of dependent types can be realized by writing functions which check whether given data have the right underlying data type and satisfy the required properties. Instead of statically checking types, which is mathematically hard or impossible, we just check data input to or output from functions to ensure they satisfy the properties, i.e. have the right (dependent) types.
Represent Interface Contracts As Types + Properties
These runtime checkable properties (dependent types) provide a good way to tackle part of the interface problem, by representing the interfaces in a machine-checkable way and building mechanisms to check the consistency of the component interfaces both at build time and at runtime. Speed is often important, so components should distinguish between a “development” environment, in which the interface checks are performed, and a “production” environment in which they are skipped.
My proposal is, therefore, to define APIs which specify the classes/functions/methods provided by the components in a system. Those APIs should specify at an appropriate level of detail the signatures, types, and properties of those classes/functions/methods. “Appropriate level of detail” here means exercising judgement about when to use heavy-weight, detailed representations, and when simpler abstractions are good enough. The functions which check the required properties of data can be used both at runtime in the development environment to check much or all of the data passing between components, and can be used at build time to check aspects of the correctness of test results. One way to view these functions is as test oracles.