Contracts

Since version 1.4, Cerny.js supports programming by contract (design by contract) in JavaScript. This methodology helps us to improve the documentation of an object or function, by making the contract of usage between the author and consumer explicit. This guide puts us in a position from where we can start programming by contract in JavaScript with the help of Cerny.js.

Preconditions

A function may have a precondition, which is a function checking that the conditions of a successful call are met. A precondition is specified with CERNY.pre. The check must be performed with the function CERNY.check. The first argument to the check must be an expression evaluating to true or false. The second argument is a message stating the nature of the violation in natural language. This message is intended for human consumption only. If the expression evaluates to false, the check is said to have failed and a CERNY.ContractViolation is thrown.

The arguments of the actual function call are passed to the precondition. It is also possible to use the keyword this. It refers to the object on which the function is called.

The following code illustrates the specification of a precondition. It defines, for the sake of example, a function for dividing two numbers.

var NewMath = {}; (function() { // Shorten some names var check = CERNY.check; var pre = CERNY.pre; var method = CERNY.method; // The new division function divide(a,b) { return a / b; } // For contracts to be enforced, it is necessary // that a function is subject to interception. Therefore // CERNY.method must be used. method(NewMath, "divide", divide); // The precondition for a division pre(divide, function(a,b) { check(b !== 0, "b may not be 0"); }); })();

Postconditions

A postcondition is specified with CERNY.post. The postcondition itself receives a clone of the object, the return value, and the arguments of the call in that order. The clone holds the state of the object before method invocation and is usually referred to as old.

(function() { // Defined a set with a remove method, which returns true on a // remove ... function remove(element) { ... } // The postcondition for a remove post(remove, function(old, result, element) { check(result === true && old.length === this.length + 1, "result may not be true when no element was removed"); check(result === false && old.length === this.length, "result may not be false, when an element was removed"); check(!this.contains(element), "element is still in the set"); }); })();

Invariants

An object may have a method called invariant, which specifies conditions that must be true for that object. The invariant is called after each method call and enforces a certain state in the object.

Let's have a look at the following example, which introduces a calendar event, and specifies that the end date may not be before the start date. Again this example only serves the purpose of demonstrating the invariant concept, and may not make a lot of sense in other aspects.

(function() { var check = CERNY.check; var method = CERNY.method; var signature = CERNY.signature; function Event(start, end) { this.start = start; this.end = end; } function setStart(start) { this.start = start; } signature(setStart, "undefined", Date); method(Event.prototype, "setStart", setStart); function setEnd(end) { this.end = end; } signature(setEnd, "undefined", Date); method(Event.prototype, "setEnd", setEnd); // This invariant guarantees a certain state in the object, // which then can be considered true. Event.prototype.invariant = function() { check(this.start.getTime() <= this.end.getTime(), "start must be before end"); } })();

Enforcing contracts

For contracts to be enforced the ContractChecker interceptor must be activated. Refer to the interception guide for further information. Contract violations are logged at FATAL level, so set your ROOT category accordingly. Furthermore they should appear as exceptions on your browser console.

For a method to take part in contract checking, it must be subject to interception. Therefore it must be attached to the object with CERNY.method, or CERNY.intercept must be called on the object.

Contracts should only be enforced during development time. A finalized product, that works correctly, should not have the ContractChecker enabled, because it is time consuming and does not contribute to the task. On the other hand, a product developed with programming by contract will execute faster, because many checks that are only needed at a development level are omitted.

Limitations

Currently there is no support for inheritance when checking invariants. The support for old is weak, because the algorithm of CERNY.clone is very naive and needs to be improved. There is no facility which removes contracts from the productive source code.

API Documentation for version