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
のような関数と比べて、そういうプログラム構築コードはめったに変わりません。修正されないものに対して、そんな複雑なテストは不要なのではないでしょうか?この妥協は納得できます。
他にもテストしやすいコードの書き方があります。例えば、構文で副作用が管理できる言語は暗に「依存性注入」で書かれているからさらに意識する必要はありません。