2014年6月30日月曜日

Google GuiceのBest Practicesを訳してみた - Moduleでは条件分岐を避けよ

Moduleでは条件分岐を避けよ
Moduleに変化する部品をもたせることと、異なった環境で異なった動作をするように定義するのは推奨されない。

public class FooModule {
  private final String fooServer;

  public FooModule() {
    this(null);
  }

  public FooModule(@Nullable String fooServer) {
    this.fooServer = fooServer;
  }

  @Override protected void configure() {
    if (fooServer != null) {
      bind(String.class).annotatedWith(named("fooServer")).toInstance(fooServer);
      bind(FooService.class).to(RemoteFooService.class);
    } else {
      bind(FooService.class).to(InMemoryFooService.class);
    }
  }
}
条件分岐自身にはそれほど問題はない。しかし、設定がテストされていない時に問題は現れる。この例では、InMemoryFooServiceは開発環境で使用され、RemoteFooServiceは本番環境で使用される。しかし、この特定の状況をテストしていないと、RemoteFooServiceが統合されたアプリケーションと動作するかは保証できない。

このことからわかるのは、アプリケーションの設定項目は最小限にすべきだということだ。開発環境と本番環境を別々のModuleへ分割すると、本番コードの全てがテストされているのを証明するのはより容易になる。この例では、FooModuleをRemoteFooModuleとInMemoryFooModuleへ分割する。これはまた、製品版のクラスがテストコードに依存しなくなる効果もある。

参照
Avoid conditional logic in modules

2014年6月23日月曜日

Google GuiceのBest Practicesを訳してみた - Providerでの入出力に注意せよ

Providerの入出力に注意せよ
Providerは便利だが、以下の機能を欠いている。
  • Providerはチェック例外を宣言しない。特定のエラーからの復帰しなければいけないコードを書いているのなら、TransactionRolledbackExceptionはcatchできない。ProvisionExceptionにより、一般的な生成エラーから復旧でき、その原因を列挙することもできる。しかし、それらの原因を指定することはできない。
  • Providerはタイムアウトをサポートしない。
  • Providerはリトライ戦略を定義しない。値が有効でないとき、何度もget()を呼ぶと、何度も失敗することになるだろう。
ThrowingProvidersは、ExceptionをthrowするProviderを実装するGuice拡張だ。それはエラーのスコープを定義でき、それにより、リクエストやセッションで一度だけエラーが発生する。

参照
Be careful about I/O in Providers 

2014年6月16日月曜日

Google GuiceのBest Practicesを訳してみた - Moduleは高速で副作用がないほうがよい

Moduleは高速で副作用がないほうがよい
Guiceのモジュールは、設定にXMLファイルではなく、Javaコードを使用する。Javaは親しみやすく、IDEの機能を活用でき、リファクタリングにも対応できる。

しかし、Javaの力はコストももたらす。つまり、Moduleでやりすぎてしまうのだ。GuiceのModuleでは、データベースへ接続し、HTTPサーバーを起動することもできる。ダメだ!Moduleで困難な仕事をすると、問題が発生する。

  • Moduleは起動するが、終了しない ― データベース接続を開いたとき、その接続を閉じるフックはない
  • Moduleはテストされるべき ― 実行時にデータベースへ接続すると、単体テストが難しくなる
  • Moduleはオーバーライドできる ― GuiceのModuleはオーバーライドをサポートしており、製品版とテスト版や軽量版と置換えができる。製品版の機能がモジュール実行の一部として実装されたとき、そのようなオーバーライドは効果がない。
Module自身で実行するよりも、適切な抽象レベルのインターフェイスを定義する。アプリケーションでは、このようなインターフェイスを使用できる。
public interface Service {
  /**
   * Starts the service. This method blocks until the service has completely started.
   */
  void start() throws Exception;

  /**
   * Stops the service. This method blocks until the service has completely shut down.
   */
  void stop();
}
Injectorを生成した後でServiceを開始し、アプリケーションの起動を完了する。また、アプリケーションが停止したときに、リソースを解放するシャットダウンフックを追加する。
  public static void main(String[] args) throws Exception {
    Injector injector = Guice.createInjector(
        new DatabaseModule(),
        new WebserverModule(),
        ...
    );

    Service databaseConnectionPool = injector.getInstance(
        Key.get(Service.class, DatabaseService.class));
    databaseConnectionPool.start();
    addShutdownHook(databaseConnectionPool);

    Service webserver = injector.getInstance(
        Key.get(Service.class, WebserverService.class));
    webserver.start();
    addShutdownHook(webserver);
  }

参照
Modules should be fast and side-effect free

2014年6月9日月曜日

Google GuiceのBest Practicesを訳してみた - @Nullableを使用せよ

@Nullableを使用せよ
NullPointerExceptionをコードから追い出すために、null参照についてよく知らなければならない。次のシンプルなルールに従うことで、成功を収めている。

すべてのパラメータは、特に示されない限り、nullではない。

Guava: Google Core Libraries for JavaJSR-305にはnullをコントロールするためのシンプルなAPIがある。Preconditions.checkNotNullはnull参照を発見したら直ちに終了するために使用でき、@Nullableはパラメータがnullを許可することを示すために使用できる。
import static com.google.common.base.Preconditions.checkNotNull;
import static javax.annotation.Nullable;

public class Person {
    ...

    public Person(String firstName, String lastName, @Nullable Phone phone) {
        this.firstName = checkNotNull(firstName, "firstName");
        this.lastName = checkNotNull(lastName, "lastName");
        this.phone = phone;
    }

Guiceはデフォルトでnullを拒絶する。nullの注入を拒否し、代わりにProvisionExceptionをスローして失敗する。もしnullを許可したいのであれば、フィールドやパラメータへ@Nullableを付けられる。Guiceはedu.umd.cs.findbugs.annotations.Nullableのようなあらゆる@Nullableに対応している。

参照
Use @Nullable

2014年6月6日金曜日

Google GuiceのBest Practicesを訳してみた - 静的状態を避けよ

静的状態を避けよ
静的状態とテスタビリティは敵対している。テストは高速で、副作用がないほうがよい。しかし、定数以外が静的フィールドに保持されると、管理に苦痛をもたらす。テストによってモックが作られた、静的なシングルトンを安全に処分するのは難しい。さらに、他のテストにも悪影響をおよぼす。
requestStaticInjection()は松葉杖である。Guiceは、静的に構成されたアプリケーションをDIに対応したスタイルに簡単に移行できるように、このAPIを追加した。新しく開発されるアプリケーションは、このメソッドを使用するべきではない。
静的状態は悪いが、静的であること自体には問題はない。静的クラスはOKであり(むしろ好まれる)、純粋な関数(ソートや数学)にとっては、静的であることが相応しい。

参照
Avoid static state

関連
Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ
Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ
Google GuiceのBest Practicesを訳してみた - 循環する依存関係を解決する

2014年6月2日月曜日

Google GuiceのBest Practicesを訳してみた - 循環する依存関係を解決する

循環する依存関係を解決する
Store、Boss、Clerkを含むいくつかのクラスがあるアプリケーションについて考える。
public class Store {
        private final Boss boss;
        //...

        @Inject public Store(Boss boss) {
                this.boss = boss;
                //...
        }

        public void incomingCustomer(Customer customer) {...}
        public Customer getNextCustomer() {...}
}

public class Boss {
        private final Clerk Clerk;
        @Inject public Boss(Clerk Clerk) {
                this.Clerk = Clerk;
        }
}

public class Clerk {
        // Nothing interesting here
}

今のところ、依存関係は全く問題ない。Bossを使用してStoreをインスタンス化し、Clerkを使用してBossをインスタンス化する。ところが、販売のためにClerkにCustomerを取得させようとすると、Storeへの参照が必要になる。
public class Store {
        private final Boss boss;
        //...

        @Inject public Store(Boss boss) {
                this.boss = boss;
                //...
        }
        public void incomingCustomer(Customer customer) {...}
        public Customer getNextCustomer() {...}
}

public class Boss {
        private final Clerk clerk;
        @Inject public Boss(Clerk clerk) {
                this.clerk = clerk;
        }
}

public class Clerk {
        private final Store shop;
        @Inject Clerk(Store shop) {
                this.shop = shop;
        }

        void doSale() {
                Customer sucker = shop.getNextCustomer();
                //...
        }
}

これはClerk -> Store -> Boss -> Clerkという循環をもたらす。Clerkをインスタンス化しようとするとStoreがインスタンス化され、StoreはBossのインスタンスが必要であり、BossはClerkのインスタンスが必要なのだ。
この循環を解決するための方法がいくつかある。

循環を断ち切る(推奨)
しばしば循環は不十分な責務の分割を反映している。そのような循環を断ち切るには、分割したクラスへ依存を抽出する。
この例では、来店する顧客を管理する機能をCustomerLineというクラスに抽出でき、ClerkとStoreへ注入できる。
public class Store {
        private final Boss boss;
        private final CustomerLine line;
        //...

        @Inject public Store(Boss boss, CustomerLine line) {
                this.boss = boss; 
                this.line = line;
                //...
        }

        public void incomingCustomer(Customer customer) { line.add(customer); } 
}

public class Clerk {
        private final CustomerLine line;

        @Inject Clerk(CustomerLine line) {
                this.line = line;
        }

        void doSale() {
                Customer sucker = line.getNextCustomer();
                //...
        }
}

StoreとClerkはどちらもCustomerLineへ依存しているが、循環は存在しない(StoreとClerkの両方が同じインスタンスを参照するときも)。このことはまた、テントで特売セールをするとき、店員が自動車を販売できることも意味する。ただ単に他のCustomerLineを注入すれば良い。

Providerを使用して循環を解決する
Providerを注入することで、依存関係へ継ぎ目を追加できる。ClerkはまだStoreに依存しているが、必要になるまでShopを参照することはない。
public class Clerk {
        private final Provider shopProvider;
        @Inject Clerk(Provider shopProvider) {
                this.shopProvider = shopProvider;
        }

        void doSale() {
                Customer sucker = shopProvider.get().getNextCustomer();
                //...
        }
}

StoreがSingletonや他の再利用するスコープを指定しない時、shopProvider.get()は新たなShopをインスタンス化し、Bossをインスタンス化し、Clerkをインスタンス化することに注意が必要だ。

ファクトリメソッドを使用して2つのオブジェクトを結びつける
依存するクラス同士がより密接に結びついているときは、上記のメソッドでは解決できない。View/Presenterパターンを使用していると、このような状況に出くわす。
public class FooPresenter {
        @Inject public FooPresenter(FooView view) {
                //...
        }

        public void doSomething() {
                view.doSomethingCool();
        }
}

public class FooView {
        @Inject public FooView(FooPresenter presenter) {
                //...
        }

        public void userDidSomething() {
                presenter.theyDidSomething();
        }
        //...
}

どちらのオブジェクトも相手のオブジェクトを必要としている。このような状況とうまくやるために、AssistedInjectが使える。
public class FooPresenter {
        privat final FooView view;
        @Inject public FooPresenter(FooView.Factory viewMaker) {
                view = viewMaker.create(this);
        }

        public void doSomething() {
        //...
                view.doSomethingCool();
        }
}

public class FooView {
        @Inject public FooView(@Assisted FooPresenter presenter) {...}

        public void userDidSomething() {
                presenter.theyDidSomething();
        }

        public static interface Factory {
                FooView create(FooPresenter presenter)
        }
}

そのような状況は、Guiceを使用してビジネスモデルを表現しようとするときに出くわす。そのモデルには、異なる関係を反映した循環があるかもしれない。そのような状況にも、AssistedInjectは効果的だ。

参照
Resolving Cyclic Dependencies

関連
Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ
Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ