愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

redflagをナビ子記法でリファクタリングする

WEB+DB press vol.59に載っていた、id:uupaaさんのナビ子記法が目からウロコだったので真似してみた。

(function(global, require) {

  var sys = require('sys'),
      fs = require('fs'),
      Script = process.binding('evals').Script;
      setups = [], events = {};


  // public interface

  global.redflag = {
    event: _event,
    setup: _setup
  };

  // implementation

  function _event(name, func) {
    events[name] = func;
  }

  function _setup(func) {
    setups.push(func);
  }

  function processFile(file) {
    var data = fs.readFileSync(file);
    Script.runInThisContext(data);
  }

  function doEvent(name, event) {
    var env = {}; 
    doSetups(env);
    if ( event(env) ) { 
      sys.print("ALERT: " + name + "\n");
    }
  }

  function eachEvent(func) {
    for ( name in events ) { 
      func(name, events[name]);
    }   
  }

  function doSetups(env) {
    var i = 0, len = setups.length;
    for (; i < len; ++i) {
      setups[i](env);
    }   
  }

  function processFiles(err, files) {
    var i = 0, fileNum = files.length;

    if ( err ) {
      console.log(err);
    }
    else {
      for (; i < fileNum; ++i) {
        if ( /events\.js$/.test(files[i]) ) {
          processFile(files[i]);
        }
      }
      eachEvent(doEvent);
    }
  }

  fs.readdir('./', processFiles);

})(global, require);


APIを変えたので、クライアントコードの側も変わる。

redflag.event('hyde is smaller than 157cm!', function(env) {
  return env.hyde < 157;
});

redflag.setup(function(env) {
  env.hyde = 156;
});


あとこれ自体はナビ子記法ではないのだけど、関数の先頭に変数宣言をまとめてしまうのはよい習慣だと思う。「forループの中で使う変数をforループの外で宣言してしまうのはバッドノウハウなのではないか」と思う人がいるかもしれないが、JavaScriptにはブロックスコープが存在しない(実際はJavaScript 1.7で導入されるletがあればいけるが、世界中の多くの人は1.7の動かないブラウザを使っている)ので、あたかもブロックスコープがあるようにブロックの内側に変数を定義してしまうより、潔く外側に出してしまった方がいい。少なくとも今は。

実際、「ブロックスコープはないんだ」ということが頭では一応分っているつもりの僕でも、たまにブロックスコープがあるかのような書き方をしてしまうことがある。人間誰しもミスをする。「この変数たちはこの関数内ではグローバルですよ」とはっきり示しておき、そういった余計なミスを減らす意味でも、変数宣言は関数の最初にまとめてしまった方がよいと思われる。