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 こそ残ってるベキなんじゃないかと思わなくも無い。)