Create  Edit  Diff  FrontPage  Index  Search  Changes  History  RSS  Login

担当割当表/My Application Is All API Calls

議論

  • Skin and Wrap のとき wrapper は is-a, has-a?
    • スライドでは "In Java, final classes ca't be subclassed" にひっぱられて is-a で説明した。
    • "we make interfaces thar mirror the API as close as possible" にひっぱられると has-a になるはず。継承したら作らなくていい。

はじめに

買うなり、OSS を使うなり、あるいはプラットフォーム (J2EE, .NET とか) についてくるものだったり、だれかのライブラリを呼んでばっかりのコードを変更するときはどうするべきだろう?

まず、テストなんていらないという誘惑にかられると思う。だって、たいしたことはやっていなくて、メソッドを呼んでるだけだもの。

沢山のレガシープロジェクトはそういうところからはじまる。そのうちコードは成長していき、単純だなんて言えなくなる。API をさわっていない部分はまだわかるかもしれないけど、それらはテストできないコードのつぎはぎのなかに埋もれてしまう。レガシーシステムプログラマのジレンマはすぐだ。変更に確信が持てない。全部のコードを書いたわけではないけど、メンテはしなきゃいけない。

ライブラリ呼び出しが散らかったシステムは、多くの点で、自家製のシステムよりやりにくい。API を呼び出してばかりで構造が見えにくく、さらには API 側を改名したりといった変更ができないからだ。

メーリングリストサーバーの問題

例として、貧弱なメーリングリストサーバーを挙げよう。

  • Java Mail API (javax.mail) を使ったコード。クラスは MailingListServer? ひとつ。

コードは短いけど、明快とはいえない。どこが API をさわっていないのかわかりづらい。これをもっと良い構造に、変更しやすい構造にできるだろうか?

最初のステップは、このコードのコアを把握することだ: このコードは実際にはなにをしてくれるのか?

こんな感じに、簡単な説明を書いてみるのは良いだろう:

このコードは設定をコマンドラインから、メールアドレスのリストをファイルから読み込む。メールを定期的にチェックして、もし見つかれば、ファイルの各々のメールアドレスに転送する。

これは主に入出力についてだけど、説明するべきことはまだある。コードの中ではスレッドを立ち上げて、定期的にメールをチェックしては sleep している。さらに、来たメールをそのまま送っているわけじゃなく、それを基に新しいメールを作っている。全部のヘッダをセットして、サブジェクトをチェックしてメーリングリストから来ているのがわかるように変更しなきゃいけない。

コードの責務に基づいて分割するなら、こんな感じになるだろう:

  1. メールの受信
  2. メールの送信
  3. 受取人名簿にもとづいて、受信したメールから送信するメールを作る
  4. 定期的なメールのチェック

これらの責務のうち、Java Mail API に他よりも強く依存しているものはどれだろう? 1, 2 はかなり依存している。3 は微妙。メールのクラスは Mail API のものだけぢ、たぶんダミーのメールを作れば独立にテスト出来る。4 はメールについてはとくになにもしていない。

メーリングリストサーバーの改善

責務に基づいて分割したのが図 15.1 だ

  • 4 クラス + 2 インターフェース

ListDriver? がシステム全体を動かす。スレッドで定期的にメールをチェックしている。ListDriver? は MailReceiver? にメールのチェックを依頼する。MailReceiver? はメールを読み、送信を一通づつ MessageForwarder? にわたす。MessageFowarder? は名簿に基づきメッセージを作り、MailSender? を使って送信する。

この設計は結構良い。MessageProcessor? と MailService? はクラスをばらばらにテストするのに良いインターフェースだ。テストハーネスでは MessageFowarder? から実際にメールを送らずにすむ。MailService? インターフェースを実装した FakeMailSender? クラスを実装するのは簡単だ。

ほとんどのシステムは、API から切り離せるコアロジックをもっている。このシステム中でMessageForwarder? がメールの送受信から最も独立しているけど、まだ Java Mail API のメッセージクラスを使っている。それでも、システムを4クラスと2インターフェースに分けるのは、システムの階層化になっている。メーリングリストのプライマリロジックは MessageForwader? クラスにあり、テストできる。元のコードでは埋まっててたどり着けなかった。システムをちいさな部品にわけることで、システムのうち「上位」のものを見つけ出せる。

API 呼び出しばかりのシステムは、でかいオブジェクトとみなして、20章「This Class Is Too Big and I Don't Want It to Get Any Bigger」の責務に基づいた分割を適用してみるといいだろう。すぐさま良い設計に到達するわけではないが、責務をちゃんと把握すれば、前進するための決断を下しやすくなる。

で、良い設計とそれが可能というところがわかったら、現実に戻ろう。前進するには二つの方法がある:

  1. API をくるむ
  2. 責務に基づく括りだし

API をくるむ、というのは API と出来るだけ同じインターフェースを作り、それで API まわりにラッパーを作ることを言う。ここでのミスを減らすには「Preserve Signature (312)」が使えるだろう。API をくるむ利点は API との依存を完全に切れるところだ。本番では実際の API に、テストでは偽物の API にと委譲できる。

これを例のメーリングリストで使えるだろうか? メーリングリストサーバーが実際にメールを送るときのコードはこうだ

  • Session と Transport

Tranport クラスとの依存はラッパーで切れるが、Transport オブジェクトを作ることは Session クラスが必要なので出来ない。Session クラスのラッパーは作れない。Session クラスは final クラスだからだ。Java では final クラスのサブクラスは作れない。

このメーリングリストのコードはくるむには良い候補じゃない。API が比較的複雑だからだ。でも、リファクタリングツールなしだったら、これが一番安全な方法だろう。

幸いなことに、Java にはリファクタリングツールがあるので、責務に基づく括りだしのほうも使える。コードの責務を把握し、メソッドをくくりだしてみよう。

さっきのコードの責務は、メッセージを送ることだ。そのためにはなにが必要だろう? SMTP セッションとトランスポートだ。以下のコードでは、メッセージを送るという責務をひとつのメソッドに括り、それに新しく MailSender? クラスを与えている。

  • MailSender?

まとめ

API をくるむのと、責務に基づいた括りだし。どちらにも長所と短所がある。API をくるむのは以下のようなときに良いだろう。

  • API が比較的小さい
  • サードパーティーのライブラリと完全に切り離したい
  • テストが無く、API とではテストも書けない

API をくるめば、実際の API とその薄いラッパー以外は、すべてをテストの基に置くことができる。

責務に基づいた分割が良いのは以下のようなときだ。

  • API が複雑
  • メソッドの括りだしのためのツールがあるか、それを手でやることに自信がある

Skin and Wrap API はコード量は多いが、サードパーティのライブラリと完全に切り離したいというありがちな要求にはとても便利だ。詳しくは14章「Dependencies on Libraries Are Killing Me」を参照。Responsibility-Based Extraction ではロジックからの API 呼び出しを、抽象度の高いの名前がついたメソッドにくくりだす。これで、コードは抽象的なインターフェースに依存し、低レベルの API 呼び出しには依存しなくなる。でも、くくりだしたコードはテストはできない。

多くの組織はこれら両方を使っている。テストには薄いラッパー、アプリケーションに良いインターフェスを提供するためなら高レベルのラッパーだ。

Last modified:2008/09/14 14:56:29
Keyword(s):
References:[担当割当表]