Design by Contract and JS

Refactoring

Related articles: Prototypal Inheritance in JavaScript for Modules and Scalable JavaScript Application Design

Design 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 is based on object factory, which accepts hooks. In the package you can find jsa.dbc.js hooking into factory to implement DbC. How does it do the trick? JS allows to wrap any function with entry/exit point cross-cutting functionality, I don’t have to do much on the library to get the required features.

Let’s just make JSA through the hook to check if the instantiating object has contract public 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( jsa, undefined ) {
    var ContractSample1 = {
        methodA : [ "number", "string" ]
    },
    ContractSample2 = {
        methodA : {
            onEntry:  [ "string", jsa.BaseAbstract ],
            validators: [
                function( arg ){ return arg.length < 20; },
                function( arg ){ return true },
            ],
            onExit: "boolean"
        },
        methodB : {
            onEntry:  [],
            onExit: "number"
        }
    },
    ModuleSample1 = function() {
        return {
            __contract__: ContractSample1
        };
    },
    ModuleSample2 = function() {
        return {
            extend: jsa.BaseAbstract,
            __contract__: ContractSample1,
            methodA: function() {
                print( "ModuleSample2: methodA is running" );
            }
       };
    }, ModuleSample3 = function() {
        return {
            __contract__: ContractSample2,
            methodA: function() {
                print( "ModuleSample3: methodA is running" );
                return true;
            },
            methodB: function() {
                print( "ModuleSample3: methodB is running" );
                return true;
            }
       };
    },

    /**
    * Usage
    */
    print = function( text ) {
        document.writeln(text + '<br />');
    };

    print( 'Sample 1. Module violates the contract' );
    try {
        ModuleSample1.createInstance();
    } catch ( e ) {
        print( e );
    }

    var m2 = ModuleSample2.createInstance(),
        m3 = ModuleSample3.createInstance();
    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, "string" );
    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: Contract requires 'methodA' method
// Sample 2. Module fails on contract preconditions
// TypeError: Argument #1 of method 'methodA' 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: Argument #1 of method 'methodA' is outside of its valid range
// Sample 4. Module fails on contract postcondition
// ModuleSample3: methodB is running
// TypeError: Method 'methodB' return value is required to be a number

})( jsa );

The JSA library can be downloaded here