ES7.decorator で契約プログラミング(design by contract)

More than 3 years have passed since last update.

D言語の契約プログラミングサポートに触発された。

Dで実際どこまでできるかはあまりわかってないが、現実的にはこんな感じだろうか。

// $ babel-node --optional es7.decorators index.js

class Counter {
constructor(initial = 0) {
this.current = initial;
}

@contract
up(i) {
this.current += i;
return this.current;
}
}

function contract(tar, name, desc) {

// 事前条件
function before(i) {
console.assert(i > 0, `arg for ${name} should be positive value`);
}

// 事後条件
function after(result) {
console.assert(result > 0, `result of ${name} should be positive value`);
}

// 不変条件
function invariant(_this) {
console.assert(_this.current > 0, 'current should be positive value');
}

let val = desc.value;
desc.value = function(i) {

before(i);
invariant(this);

let result = val.apply(this, arguments);

invariant(this);
after(result);

return result;
}
}

function main() {
let counter = new Counter(10);
console.log(counter.up(1));
}
main();

汎用化すると

function contract(rule) {

return function(target, name, descriptor) {
let rule = new Rule(target, name, descriptor);
let original = descriptor.value;
descriptor.value = function() {

if (rule.before) rule.before.apply(rule, arguments);

if (rule.invaliant) rule.invaliant(this);

let result = original.apply(this, arguments);

if (rule.invaliant) rule.invaliant(this);

if (rule.after) rule.after(result);

return result;
}
}
}

class Rule {
constructor(target, name, descriptor) {
this.target = target;
this.name = name;
this.descriptor = descriptor;
}

before(i) {
console.log('before', i);
console.assert(i > 0, `arg for ${this.name} should be positive value`);
}

after(result) {
console.log('after', result);
console.assert(result > 0, `result of ${this.name} should be positive value`);
}

invaliant(_this) {
console.log('invaliant', _this);
console.assert(_this.current > 0, '`current` should be positive value');
}
}

function main() {

class Counter {
constructor(initial = 0) {
this.current = initial;
}

@contract(Rule)
up(i) {
this.current += i;
return this.current;
}
}

let counter = new Counter(10);
console.log(counter.up(2));
}

main();

D ではコンパイル時にこの辺は消す。Java も --ea を付けないと消える。

それをしたければ babel 時に @contract のデコレータを消して、

closure compiler みたいな「不使用コード削除」な minify とかをかませば消せる。

(しかし、動的で緩い型付けの JS みたいな言語では、実行時 assertion こそ残ってるベキなんじゃないかと思わなくも無い。)