10分で依存性注入を理解する
これは記事の日本語版です。
The English version of this article is here.
この記事で、たった10分で依存性の注入を説明してみます。
依存性の注入とは?
「依存性の注入」とは、コードの書き方のことです。あるコードの外部リソースすなわち「依存性」を固定することより、そのリソースを引数として受けることです。そう書く理由は、主にそのコードのテスト容易性を高めるためです。
典型的な例を紹介します。
fetch_data
ある日、データベースから情報と取得しろ、と上司から依頼されたとしましょう。次の関数を書きます。
def fetch_data(criteria):
database = connect_to_real_database('1.2.3.4:5555')
# 何かの特別な条件に対応してからqueryしなくては。
if criteria.has_as_special_case():
criteria.fix_the_special_case()
return database.query(criteria)
私たちは「終わったぞ!」と頭の中で宣言します。でも上司に見せたら、「ミシャ、この関数が動くかもしれないが、この特別なケースは結構複雑だな。テストも書いてくれる?」と弾かれます。
fetch_data
をテストする
今の実装では、データベースの接続が内包されているせいで、テストするのは相当難しいです。あるいは本番のデータベースだったらなおさら使えません。
関数を工夫することにします。次のように書き直します。
def fetch_data(criteria, host):
database = connect_to_real_database(host)
# 何かの特別な条件に対応してからqueryしなくては。
if criteria.has_as_special_case():
criteria.fix_the_special_case()
return database.query(criteria)
すっきりしました。これでテスト用環境を立ち上げて、そのデータベースに対してテストできるようになりました。危機が回避できました。
しかし、上司に読んでもらったら、「ミシャ、あの特別な条件なんだけど。色んな組み合わせをテストして欲しい。この関数って、数万回テストするには遅すぎない?」と指摘されてしまいました。
fetch_data
を効率的にテストする
稀ですが、今回も上司の言う通りです。データベースとやり取りするのは基本的に遅いです。テスト用のインメモリーデータベースが必要です。メモリーならとてつもなく早くなるでしょう。
とすると、次のように書き直します。
def fetch_data(criteria, database):
# 何かの特別な条件に対応してからqueryしなくては。
if criteria.has_as_special_case():
criteria.fix_the_special_case()
return database.query(criteria)
この最後の書き直しを上司に見せたら、褒めてもらいました。「よくできたね!お疲れ。」って。
まとめ
fetch_data
の最終版は、データベースはどこなのか、実際にデータベースなのか、気にしていません。テストする時、複雑なロジックのみ働かされます。そのテストは早くて簡潔です。
このようにリソースを引数として受けるコードの書き方は、「依存性の注入」と言います。上記の例と同様で、テストしやすいコードをもたらします。
注意点
確かに、データベースと接続するコードは少しテストしにくくなりました。しかし、fetch_data
のような関数と比べると、そういうプログラム構築コードはめったに変わりません。修正されないものに対して、そんな複雑なテストは不要なのではないでしょうか?この妥協は納得できます。
他にもテストしやすいコードの書き方があります。例えば、構文で副作用が管理できる言語は暗に「依存性注入」で書かれているからあまり意識する必要はありません。