Hatena::ブログ(Diary)

kazuhoのメモ置き場

2012-04-20

node.js におけるエラー処理のコーディングパターン (もしくは非同期 JavaScript における例外処理)

node.js を代表とする JavaScript を用いた非同期プログラミング環境においては、コーディングパターンのベストプラクティスが共有されておらず、結果として品質の低いコードが多くなるという問題があるように思います。そこで、特にエラー処理をどう書くべきか、既存のライブラリを使う方法を紹介してみることにしました。

いきなりですが、ファイルの文字数を返す関数を作ることを考えてみます。Java だと以下のような感じになるでしょうか。countChars メソッドに注目すると、エラーを例外として扱っていて、モジュラーかつ簡潔になっていることがわかります。

class FileCounter {
  static long countChars(String filename) throws IOException {
    FileInputStream is = new FileInputStream(filename);
    try {
      long count = 0;
      while (is.read() != -1) {
        count += 1;
      }
      return count;
    } finally {
      is.close();
    }
  }
  static void main(String args[]) {
    try {
      System.out.println(countChars(argv[0]));
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
  }
}

では、同様のものを node.js で書くとなると、どうしたらいいでしょう。解決すべき問題は2点あります。

第1の点は、コールバックの第1引数に渡ってくるエラー値をどうやって自動的に処理するかです(node.js では、エラーは各コールバックの第1引数として通知されます)。第2は、エラーが発生した場合、そのエラーをどうやって上位(呼び出し元)に伝播 (propagate) させていくか、という点です。

処理をウォーターフォールで記述可能な async.jsラッパーを使い *1、なおかつ、自分の定義するコールバック全ての第1引数を error 値の受け取りに使うことで、これら2つの問題をきれいに解決することができます。

function countChars(filename, callback) {
  async.waterfall([
    function (next) {
      fs.readFile(filename, 'utf-8', next);
    },
    function (data, next) {
      next(null, data.length);
    }
  ], callback);
}

function main(args) {
  async.waterfall([
    function (next) {
      countChars(args[0], next);
    },
    function (length, next) {
      console.log(length);
    }
  ], function (err) {
    console.err(err);
  });
}

この コードを見ていただければ、countChars 関数にはエラー処理が全く含まれていないことがわかると思います。エラーは全て main 関数に伝播されて、そこで処理が行われています。もう少し定式化すると、

async.waterfall([

// この中に書かれるのが try 文に相当する処理

], function (err, ...) {

// ここは catch 文に相当する処理

// catch しないのなら、関数を書かずに、上位の callback を渡す

});

という構造になっているのがわかります。

このように、「全ての」コールバックの第1引数をエラー値の通知に使うものとし、その処理を async.js のようなライブラリを用いて隠蔽することで、エラー処理を必要なところでまとめて書くという、同期的なプログラミング言語例外処理と同様のモデルを非同期の JavaScript プログラムでも実現することができます。

繰り返しますが、「全ての」コールバックの第1引数を error にすることが重要です。

プログラミングにおいて、エラー処理は見過ごされがちですがとても重要な要素です。以上のようなコーディングパターンを用いれば、エラー処理を簡潔かつ確実に書くことができ、品質の高い JavaScript アプリケーションを実現することができるでしょう。

*1:同様の枠組みがあれば他のラッパーでもいいです

craftgearcraftgear 2012/04/20 13:43 こんにちは、asyncの代わりにsynchronizeを使うとtry-catchで書けますよ

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/kazuhooku/20120420/1334891656