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を訳してみた - 直接の依存性のみ注入せよ

2014年5月29日木曜日

Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ

直接の依存性のみ注入せよ
他のオブジェクトを取得するために注入することを避けよ。例えば、Accountを取得するためにCustomerを注入してはいけない。
public class ShowBudgets {
    private final Account account;

    @Inject
    ShowBudgets(Customer customer) {
        account = customer.getPurchasingAccount();
    }

代わりに、依存するクラスを直接注入せよ。これはテストを簡単にする。テストはテスト自身とCustomerにだけ注意すればよい。@Providesを付けたメソッドを使い、CustomerからAccountをバインドする。
public class CustomerModule extends AbstractModule {
    @Override public void configure() {
        ...
    }

    @Provides
    Account providePurchasingAccount(Customer customer) {
        return customer.getPurchasingAccount();
    }

依存性を直接注入することで、コードはシンプルになる。
public class ShowBudgets {
    private final Account account;

    @Inject
    ShowBudgets(Account account) {
        this.account = account;
    }

2014年5月26日月曜日

Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ

可変性を最小化せよ
可能であればいつでも、不変なオブジェクトを生成するようにコンストラクターインジェクションを使用すること。不変なオブジェクトはシンプルで、共有でき、再利用できる。このパターンにしたがって注入可能なクラスを定義する。
public class RealPaymentService implements PaymentService {

    private final PaymentQueue paymentQueue;
    private final Notifier notifier;

    @Inject
    RealPaymentService(
        PaymentQueye paymentQueue,
        Notifier notifier) {
        this.paymentQueue = paymentQueue;
        this.notifier = notifier;
    }

    ...

このクラスの全てのフィールドはfinalであり、@Injectが付けられたコンストラクタで初期化されている。Effective Javaでは不変性の他の利点について述べられている。

メソッドインジェクションとフィールドインジェクション
コンストラクターインジェクションにはいくつかの制限がある。

  • 注入されるコンストラクタはオプションにならない。
  • オブジェクトがGuiceによって生成されない限り、使用されない。これは一部のフレームワークとの関係を断つ。
  • サブクラスはsuper()を全ての依存するクラスとともに呼ばなければならない。これはコンストラクターインジェクションを扱いにくくする。親クラスに変更があったときは特に。
Guiceによって生成されないオブジェクトを初期化するなら、メソッドインジェクションがもっとも便利だ。AssistedInjectやMultibinderのような拡張は、バインドされたオブジェクトを初期化するために、メソッドインジェクションを使用する。

フィールドインジェクションは、コンパクトに記述できる。故に、例を示すときやスライドで頻繁に目にする。それはカプセル化もできていないし、テストもしにくい。finalなフィールドへ注入してはいけない。注入された値が全てのスレッドで参照できることを、JVMは保証していない。

2014年5月19日月曜日

Lispでバケツソート

日経ソフトウェアの2014年6月号で、バケツソートが紹介されていました。
興味深かったので、Lispで実装してみました。

ソース
(defun backet-sort (v backet-count)
  "バケツソート"
  (let ((backet (array-to-backet v backet-count)))
    (backet-to-array backet (length v))))

(defun array-to-backet (arr backet-count)
  "配列からバケツを構成する"
  (let ((backet (make-array backet-count :initial-element 0)))
    (dotimes (i (length arr) backet)
      (let ((n (aref arr i)))
        (setf (aref backet n) (1+ (aref backet n)))))))

(defun backet-to-array (backet array-length)
  "バケツからソート済みの配列を構成する"
  (let ((arr (make-array array-length)))
    (loop with arr-index = 0
          for i below (length backet)
          when (< 0 (aref backet i))
          do (loop repeat (aref backet i)
                   do (setf (aref arr arr-index) i)
                      (setq arr-index (1+ arr-index))))
    arr))


(defun main ()
  "動作確認のための関数"
  (let ((l (loop with state = (make-random-state t)
                 repeat 10
                 collect (random 10 state))))
    (print (sort (make-array (length l) :initial-contents l) #'<))
    (print (backet-sort (make-array (length l) :initial-contents l) 10))))

(main)

なんとなく汚い感じがするのは、きっと自分の腕が未熟だからでしょう。

2014年5月12日月曜日

JavaScriptでは"false"はtrueとして扱われる

タイトルだけでは「そんなバカな」と思われるかもしれませんが、要はtruthy/falsyの話です。

JavaScriptでのTruthy/Falsy
仕様は、ECMAScript Language Specification - 9.2 ToBooleanにあります。これによれば、falseとして扱われるのは次の6つです。

  1. undefined
  2. null
  3. false
  4. 0
  5. NaN
  6. "" (空文字列)
Boolean関数を使って確認してみました。


"false"がtrueとして扱われることが確認できました。

問題になるケース
問題になるケースとしては、サーバーサイドの言語からJavaScriptへ、type="hidden"のinputのvalueへfalseをセットする場合が考えられます。サーバーでセットされた値を使用して、次のようなif文を書くと問題が発生します。

// <input id="someElement" type="hidden" value="false" />のとき
if (document.getElementById('someElement').value) {
    // このブロックが実行される
    ...
}

回避するには、明示的に"true"と比較するのがいいでしょう。

// <input id="someElement" type="hidden" value="false" />のとき
if ("true" === document.getElementById('someElement').value) {
    // このブロックは実行されない
    ...
}

JavaScriptの型は独特のゆるさがあるので、ついついチェックがあまくなりがちです。しかし、今自分がどんな型を扱おうとしているのか把握していないと、思わぬところでハマってしまいます。という記事でした。

おまけ
「Boolean型に変換するAPIはないのか」と思って調べてみましたが、次のような結果に。


参考
ECMAScript Language Specification

2014年5月5日月曜日

unittest.mockの簡単な紹介

単体テストに欠かせないツールのひとつに、モックがあります。
Pythonにも3.3からモックモジュールが追加されました。
unittest.mockといいます。今回はこのモジュールの簡単な使い方を紹介します。

モックが必要になる場面として、システム日付の取得があります。
例えば、当日が閏日かどうか判定する関数を考えてみましょう。
なにも考えずに関数を実装すると、次のようになると思います。
def is_leap():
    d = datetime.date.today()
    if 2 == d.month and 29 == d.day:
        print(str(d) + ' is LEAP')
        return True
    else:
        print(str(d) + ' is NOT LEAP')
        return False

このままではOSの時計を設定しないと試験ができないので、
日付をパラメータとして渡すようにします。
def is_leap(d):
    if 2 == d.month and 29 == d.day:
        print(str(d) + ' is LEAP')
        return True
    else:
        print(str(d) + ' is NOT LEAP')
        return False

日付を注入できるようにしたことで、テストしやすくなりました。
例えば、このようにします。
assert True == is_leap(datetime.date(2012, 2, 29))
assert False == is_leap(datetime.date(2012, 3, 1))

unittest.mockを使うと、次のような感じになります。
d = unittest.mock.MagicMock()
d.month = 2
d.day = 29
assert True == is_leap(d)

モックを使わないほうが単純です。こういう場合はdateオブジェクトを使ったほうがいいでしょう。
さて、現実には全てのコードを自由に変更できるとは限りません。
たとえそんな場合でも、unittest.mock.patchを呼ぶことで、対応できる場合があります。
is_leap関数を修正せずに次のようにします。
with unittest.mock.patch('datetime.date') as dt:
    dt.today().month = 2
    dt.today().day = 29
    assert True == is_leap()

datetime.date.today()メソッドが返すオブジェクトをモックオブジェクトと置き換え、
属性を設定することでテストをパスしています。
確かにこの方法でも試験はできますが、日付をパラメータ化するほうがベターだと思います。

他にも、unittest.mockにはメソッドの呼び出しを記録する機能もあります。
詳しいことはオフィシャルのドキュメントを参照してください。

参考:

2014年4月28日月曜日

xyzzyでブックマーク機能

Emacsのブックマーク機能で紹介したEmacsのブックマーク機能が便利だったので、xyzzyでも使いたくなりました。
Lispの勉強も兼ねて、自分で実装してみました。
プログラムは記事の最後に載せます。

機能
実装済みの機能は以下のとおりです。

  • 現在表示しているバッファをブックマークとして登録する
  • ブックマークの一覧を表示する
  • ブックマークの一覧からファイルを開く
  • ブックマークを削除する
使用方法
まず、プログラムを読み込みます。専用のパッケージを定義したので、合わせてuse-packageします。

(require "bookmark")
(use-package "bookmark")

次に、キーバインドの設定をします。今のところ公開している関数は次の2つです。
  1. bookmark-add-current-buffer: 表示しているバッファをブックマークする
  2. bookmark-list-bookmarks: ブックマークの一覧を表示する
(global-set-key '(#\C-c #\a #\b) 'bookmark-add-current-buffer)
(global-set-key '(#\C-c #\l #\b) 'bookmark-list-bookmarks)
bookmark-add-current-bufferを呼ぶと、現在のバッファをブックマークします。その際、ブックマーク名を入力できます。
bookmark-list-bookmarksを呼ぶと、ブックマーク一覧を表示します。一覧からファイルの表示と、ブックマークの削除ができます。ブックマークしたファイルの表示は[f]キーを押下し、ブックマークの削除は[d]キーです。

ブックマーク一覧を実装するときは、xyzzy付属のbuf-menu.lが参考になりました。
よろしければ使ってみてください。