Design by Contract and JS
22 December 2011Design by contract (DbC) is an approach in application design, which originally came from Eiffel, but now widely used on different languages (particularly in Java). In real world one party (supplier) makes an offer for an agreement (business contract) and another one (client) accepts. The same way can be described relations between objects in software engineering. As a declared agreement is accepted by the client object, the last one is expected to keep its rules. So it may:
- Expect a certain condition to be guaranteed on entry point by client (preconditions);
- Expect a certain condition to be guaranteed on exit point by client (postcondition);
- Maintain a certain property on entry and on exit points (invariants).
Usually it is achieved by:
- Control of input values (validation) and types;
- Control of return value and type;
- Control of errors and exceptions;
- Side effects;
- Precondintions, postconditions and invariants.
Reflecting back to the beloved languages, PHP has interfaces and abstract classes which can be considered as the contracts guaranteeing structure of their implementers. There is type hinting to guarantee input types. JS has nothing like that in its core, but being freakishly flexible it allows many ways to implement DbC. As a remarkable example would be Cerny.js library. It provides pretty rich tools to build design by contract in JS.
However, I’m used to follow YAGNI wherever it makes sense. Cerny.js provides much more than just DbC-related tool. Besides, for my applications every time switching to JS I miss mostly only interfaces and type hinting. So, I decided to extend my library (JSA), which I use for inheritance.
The library comprises BaseAbstract object, which when inherited provides control for offspring object instantiating. Keeping in mind JS allows to wrap any function with entry/exit point additional functionality, I don't have to do much on the library to get the wanted DbC features.
Let's just make JSA through an extension to control if an object has 'contracts' property and if it does, process given instructions. Now we need an easily readable syntax for the contract object, which will be supplied to that property.
In the simplest case, contract is a static object, containing list of methods mandatory for the client object. Each method is provided by the array expected input types. Right like interface in PHP, isn’t it?
var ConcreteContract = {
methodA : ["number", "object"]
}
Though, here we can declare also entry/exit conditions and even validators per each method.
var ConcreteContract = {
methodA : {
onEntry: ["string", $.jsa.BaseAbstract],
validators: [
validatorFunc1,
validatorFunc2,
],
onExit: "boolean"
}
}
To get feeling what is that, below there is an example from the library package.
(function( $ ) {
var ContractSample1 = {
methodA : ["number", "object"]
}
var ContractSample2 = {
methodA : {
onEntry: ["string", $.jsa.BaseAbstract],
validators: [
function(arg){ return arg.length < 20; },
function(arg){ return true },
],
onExit: "boolean"
},
methodB : {
onEntry: [],
onExit: "number"
}
}
var ModuleSample1 = function() {
return $.jsa.extend({
name: 'ModuleSample1',
contracts: ContractSample1
}, $.jsa.BaseAbstract);
};
var ModuleSample2 = function() {
return $.jsa.extend({
name: 'ModuleSample2',
contracts: ContractSample1,
methodA: function(arg1, arg2) {
print(this.name + ":methodA is running");
}
}, $.jsa.BaseAbstract);
};
var ModuleSample3 = function() {
return $.jsa.extend({
name: 'ModuleSample3',
contracts: ContractSample2,
methodA: function(arg1, arg2) {
print(this.name + ":methodA is running");
return true;
},
methodB: function() {
print(this.name + ":methodB is running");
return true;
}
}, $.jsa.BaseAbstract);
};
/**
* Usage
*/
var print = function(text) { document.writeln(text + '<br />'); };
print('Sample 1. Module violates the contract');
try {
var m1 = ModuleSample1().getInstance();
} catch(e) {
print(e);
}
var m2 = ModuleSample2().getInstance(),
m3 = ModuleSample3().getInstance();
print('Sample 2. Module fails on contract preconditions');
try {
m2.methodA("a string where a number excpected", {});
} catch(e) {
print(e);
}
print('Sample 2. Module passes contract preconditions');
m2.methodA(42, {});
m3.methodA("short string", m2); // m2 is an instance of BaseAbstract
print('Sample 3. Module fails on contract validator');
try {
m3.methodA("string, which is more than 20 chars", m2);
} catch(e) {
print(e);
}
print('Sample 4. Module fails on contract postcondition');
try {
m3.methodB();
} catch(e) {
print(e);
}
// Output:
// Sample 1. Module violates the contract
// SyntaxError: One of the contracts, the object agreed on, contains abstract method 'methodA'
// and it must be implemented by the object
// Sample 2. Module fails on contract preconditions
// TypeError: ModuleSample2.methodA: argument #1 is required to be a number
// Sample 2. Module passes contract preconditions
// ModuleSample2:methodA is running
// ModuleSample3:methodA is running
// Sample 3. Module fails on contract validator
// RangeError: ModuleSample3.methodA: argument #1 is outside of its valid range
// Sample 4. Module fails on contract postcondition
// ModuleSample3:methodB is running
// TypeError: ModuleSample3.methodB: return value is required to be a number
})( jQuery );
How does it work? As I have mentioned already, BaseAbstract analyzes contracts property when any offspring object instantiated. It checks if provided by contract methods really exist in the client object. Then it wrap those methods with additional functionality to check input types, validate input values and return type. All that is easy achievable though this trick:
var wrapObject = function(context, fnName) {
var orig = context[fnName];
context[fnName] = function() {
// check argument types here
// apply validators here
var out = orig.apply(context, arguments);
// check return type
return out;
}
}
The JSA library can be downloaded here