LoginSignup
19
18

More than 5 years have passed since last update.

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

Posted at

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

19
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
18