node.jsにおけるcircular dependency問題に対処するための3つの方法

node.js でコードを書いている時にハマった circular dependency という問題とその対処法について紹介します。

circular dependency

この問題は cyclic dependency とも呼ばれ、日本語だと 循環参照 とか 循環依存関係と呼びます。これはどういう問題かというと、あるファイルを require したときにその結果が空のオブジェクトとして返される問題 です。 この問題に直面した時はなんでこうなるのか全く理解できなかったのですが、よくよくコードを見てみると、あるファイルとあるファイルがお互いにrequireし合っていることに気づき、色々調べてみるとこういう問題があることがわかりました。

以下にこの問題を再現させるサンプルコードを示します。

これらのコードを見ると、main.jsがa.jsを、a.jsがb.jsを、b.jsがa.jsをそれぞれrequireしていますね。 main.js を実行してみると、以下のように実行エラーになります。cyclic dependency のため A というオブジェクトが空オブジェクトになっています。空なので、getId というキーでアクセスしても undefined が返っています。

この問題は、モジュールAが読み込まれる前にそのモジュール自身が読み込まれた時に発生します。

上のサンプルコードの require の流れに着目してみましょう。

main.js(2行目)var A = require(‘./a’);
→→a.js(3行目)var B = require(‘./b’);
→→→→b.js(3行目)var A = require(‘./a’);
→→→→b.js 読み込み完了
→→a.js 読み込み完了

流れを見てわかるように、a.js の読み込みが完了する前に b.js の中で a.js が読み込まれています。a.js の読み込みは完了していないので、b.js の3行目では空のオブジェクトが返っています。

ではどうすべきか

この問題はかなりデバッグしづらいです。できれば事前にみつけたいですね。この問題を事前に見つけるためには、CIで自動テストを継続的に実行させることが重要ですね。ソースコードを眺めていてもこの問題を見つけるのが難しいためです。

こういう問題が起こること自体、ある意味モジュール化の仕方に問題があるとも言えるのですが、がっつり設計し直すことも難しい状況もあると思うので、 大きく構造を変えずにこの問題を解決させる方法について紹介してみます。方法の名前は勝手に命名していますw

方法1:module.exports first

require(‘./b’) する前に module.exports を書く方法です。

オリジナルのa.jsと上のコードを比べてみると、真っ先にmodule.exportsにオブジェクトを代入しています。requireよりも先にモジュールAをexportすることで、circluar dependencyを回避しています。

この方法でコードを書き換える場合、コーディングスタイルによっては上の例のように大幅なファイル変更が必要になる場合があります。ちなみに、普段からmodule.exports firstでコードを書く癖をつけておくと、circluar dependencyの問題にハマる可能性もグッと下がるのでオススメです。

方法2:lazy require

利用したいモジュールを先頭で require するのではなく、必要なときに初めてrequireする方法です。

この方法は最も既存のコードへの影響が少ない方法です。 このファイルがrequireされるときに、init関数自体は実行されないので、cyclic dependency は回避出来ています。init関数が呼ばれて初めて、b.jsが読み込まれます。

関数呼び出しのたびに、requireされるのでオーバーヘッドが若干気になりますが、 requireされた結果はキャッシュされるので大きなパフォーマンスの低下はないはずです。

実際のソースコード

方法3:dependency injection

dependency injection(DI; 依存性の注入)は、モジュール間の結合度を下げるためのデザインパターンの一種です。

「モジュールBがモジュールAを利用する」という依存関係をソースコードにハードコードするのではなく、外部からモジュールBの引数に注入しています。こうすることで、b.jsはa.jsをrequireする必要がなくなるということです。

上の例だとモジュールのI/Fに若干の変更は入りますが、DIのテクニックを活用することで、モジュール間の結合度が下がり、モジュールのテストが書きやすくなるので、こちらもいい方法なのかなと思います。

まとめ

node.js における circular dependency という問題とその対処法について紹介しました。一度この問題にハマって解決方法を知るとそれ以降は用心深くなれるのですが、知らないとかなり苦労します。まだこの問題にハマっていない方も是非知っておいて損はないと思います!

Reference

2014年を振り返る

2014年の年越しは新潟県の赤倉温泉という場所で過ごしました。 2泊3日で12/30から1/1まで滞在していました。主にスキーをして過ごしていました。1/1は猛吹雪でほとんど滑れませんでしたが。。。

年も越したなので、2014年を振り返ってみました。ちょっと長いですが、お付き合いください。

Continue reading