静と動の往還としてのモデリング

DCIを参照しつつ「業務分析」について考える

はじめに

最近「アジャイル」という言葉をなるべく使わないようにしています。なぜなら、この言葉に込められた「桃源郷へのあこがれ」が、色々なものを見えなくしてしまうような気がするから*1。例を挙げましょう。アジャイルの基本は、「タイムボクシングによるインクリメンタルかつイテレーティブな開発」であると言え、それを実現するために流派によって様々なテクニックが提示されます。Scrumを見てみましょう。Scrumを実現するために絶対的に必要なのは、プロダクトオーナーによって適切に優先順位付けされたプロダクトバックログなわけですが、網羅性と整合性を保証したかたちでバックログアイテムを優先度順に並べるって、実はものすごく難しいことを言っていませんか? 確かにタイムボクシングによる軌道修正がある程度できるとは言え、「優先順位の低いところは粒度が粗くてもいいよ」で先々問題が起きないとは、私にはどうしても思えないのです。どんなにプロダクトオーナーが先を読んでいても、どれほどチームが優秀でも、プロダクトオーナーとチームとの間で最終的なアーキテクチャが共有されていない限り、コードが先々の変化に耐えられるかたちになっているとはどうしても思えない。


逆に言えば、網羅性と整合性が保証されたかたちでプロダクトバックログなり機能一覧なりが整備されていて、それを実現するためのアーキテクチャが定まっているのであれば、「それは、もう真っ当にウォーターフォールをやってもうまくいくのでは?」という気もします。しかも、継続的デリバリーのようなテクニックをウォーターフォールでは使えないかというと、それはまた別の話。


こうして見ると、アジャイルウォーターフォールかという区別は「アップフロントでの作業云々」ではなく、「何を固定するか」という話と社内事情とを照らし合わせたときに出てくる解ととらえるとすっきりします(これもよく言われることではありますが)。つまり、自社メンバだけで開発するという前提ならリソース固定(アジャイル)にならざるを得ないし、「どうしてもこの日までにこの機能を」というのがお客様のビジネスにとって必要なら、リソースのスケールアウト戦略(ウォーターフォール)を考える必要が出てくるでしょうし。


前置きが長くなりましたが、こうしたことを踏まえて、いずれにせよ必要になるであろう「アップフロントな分析・設計作業」が本エントリの主題です。

ソフトウェアをデザインする

業務を分析してソフトウェアを設計する作業は、抽象度をコントロールしながらシステムのモデルを作り上げていく作業です。どういうものを書かなければいけないかは、教科書を見ればいくらでも出てきます。業務フロー、機能一覧、ER図、画面遷移図、入力チェック仕様、データアクセス仕様、クラス図、シーケンス図 etcetc・・・。このとき重要なのは、こうした複数のモデルが、1つのシステムの別々の側面を記述しているものであること。つまり、モデル1つ1つが、明確につながっていなければならないということです。このつながりが見えているかどうかが、設計の整合性と網羅性に決定的な影響を与えます。


このつながりを考える上で、大きなヒントを与えてくれるのがDCIアーキテクチャです。

システムが「どのようなものか」/「何をするのか」

DCIの根底にある考え方を簡単におさらいします。DCIでは、人間がシステムをとらえる際には「システムがどのようなものであるか」と「システムが何をするか」の2つから考えているとします。その上で、オブジェクト指向のことを、後者を表現することには長けているが、前者をうまく表現することはできないと批判します。さらに、「人間のメンタルモデルを表現する」というオブジェクト指向の理念に従い、この「システムが何をするか」と「システムがどのようなものであるか」という異なる位相で重なり合う2つの軸を表現するために、ロールという概念が導入され、実装のためにScalaのTraitが提示されます(詳しくはこちら)。


これを実装の話としてだけとらえてしまうと、実務で使うにはちょっと怖い感覚があるのですが、システムを「どのようなものか」と「何をするか」という2つの軸でとらえるという発想は、システムをデザインする上で大きなヒントを与えてくれます。この軸は「構造とふるまい」であり、「静と動」であり、「空間と時間」でもあるのです。

静的なモデルと動的なモデル

業務分析の入り口は、ユーザーの動きです。システム以前に、ユーザーが何をするのかがまず問題になるということですね。これを表現するためのテンプレートとしては、業務フローやユースケース、あるいはユーザーストーリーなど、そのコンテキストに応じて適切なものを選択すればよいですが、表現したいのはユーザーがシステムで何をしたいのか、それを踏まえてシステムは何をしなければならないのか、という動的な側面です。


動的なモデルは時間軸を含むため、単一のモデルで全体を表現することができません。したがって、ソフトウェア全体を俯瞰するためには、つまり「システムがどのようなものか」をとらえるためには、時間軸を無視して全体を一枚に表現する静的なモデルが必要となります(Copeであれば、空間的という表現を使うでしょうか)。それは、ユースケース図かもしれないし、機能構成図かもしれないし、ER図かもしれない。どのかたちに落とすのが適切かは、先行する動的モデルの性質と精度によるでしょう。静的なモデルを引くことにより、先行する動的なモデルの検証も行うことができます(「これがあるということは、こういうこともしないといけないですよね?」)。


こうして出来上がった静的なモデルは、今度は動的なモデルによって検証されることになります。静的なモデルの構成要素が登場するインタラクションを時間軸を導入しつつより具体的なレベルで分析することで、必要な要素が抜けていないか(網羅性)、構成要素に矛盾がないか(整合性)といったことを検討できるのです(「これをやるためには、こういうものも必要ですよね?」)。


このとき重要なのは、「動的」という言葉が時間軸を含んでいるということです。たとえば、「メッセージング」のような概念はそれ自体が動的であるかのように思えますが、単純に2つの構成要素間でメッセージングが行われていることを示しているだけなら、それは静的なモデルです。コンテキストを限定し、時間の流れとともに何が起きるのかを追跡するのが動的なモデルだということです。


こうして、全体像とその動き(静と動)をクロスチェックしながら、抽象的なところから具体的なところに落としていくことで、その作業の結果が設計成果物となっていくわけです。

プログラムをデザインする

こうした静と動の関係は要件定義フェーズだけに限定されるものではなく、成果物の抽象度が詳細レベルまで落ちてきても本質的には変わりません。たとえば、ER図の妥当性は最終的にはSQLを設計する際に検証される、ということです。プログラムの設計の場合にはどうかと考えると、ここにはSteve FreemanとNat Pryceの提唱している「モックを使ったTDD」がうまく当てはまります(「モックを使ったTDD」について詳しくはこちら)。


「この入力に対してはこの出力が返される」という仕様は静的なものです。モックを使ったTDDの場合、実装時にはユニットテストコードを書きながらオブジェクトとコラボレーターとのインタラクション(動的)を規定していきます。テストコードがグリーンになったら、今度はコードを静的な構造としてとらえ直し、コラボレーターとしてのインターフェイスを実装したクラスの配置をリファクタリングします。ここではTDDのサイクルが、静と動との往還と一致しているのです。

まとめ

「システムがやること」と「システムのあり方」あるいは「静/動」という概念を軸に、設計について考えてきました。最後に、ドメイン駆動設計について少し触れておきたいと思います。『エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)』の中で登場するのは、ほとんどがエンティティ同士の関係を示す静的なモデルです。しかし、これらの静的なモデルを作り上げる過程では、シナリオの中でそうしたエンティティがどう動くのかという動的な側面が、顧客との対話の中で考察されていることが読み取れるでしょう。


事実、DDDを行うプロセスとして現在エリックが整備している「モデルを探究するうずまき*2では、動的な要素としてのシナリオと、静的な要素としてのモデルとの往還関係を繰り返しながらモデルが進化していくとされています。


Copeが「アーキテクチャ」という言葉を使うときには、システムをとらえるためのこうした大きな枠組みが含まれています*3。こうしたアーキテクチャに対する思考は、アジャイルであれ、ウォーターフォールであれ、ある程度はアップフロントに行うべきものだと思うのです。

*1:にも似たようなことは書いた気がしますが

*2:以前、こちらのエントリで少し整理しました

*3:Alexanderの思想を受け継いでいる彼の場合、実際にはそれをも超えて、人とシステムとのかかわり全体(バリューストリームと呼ばれるもの)までが視野に入っているのですが

モックによるインターフェイスの発見

設計ツールとしてのモックの使い方について考える。

導入

先日、"Mock Roles, not Objects"の日本語版「ロールをモックせよ」を公開しました。この論文は2004年に書かれたもので、著者はSteve Freeman氏、Nat Pryce氏、Tim Mackinnon氏、Joe Walnes氏という豪華メンバーです。また、Steve Freeman氏とNat Pryce氏は『Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))』(いわゆるGOOS)の著者でもあり、"Mock Roles, not Object"で語られている思想はGOOSのベースになっているとも言えます。


今回は、この"Mock Roles, not Objects"(以下、MRnO)で語られているモックの基本的な考え方について、GOOSを意識しつつ掘り下げてみたいと思います。

設計手法としてのモック

まずは"MRnO"で紹介されているサンプルを元に具体的なコードを見ていきましょう(なお、このエントリで紹介するサンプルは"MRnO"を元にjMock2に移植したものです)。実装する仕様は以下の通りです:

オブジェクトをロードするフレームワークに対してキーを元にした検索を行い、その結果をキャッシュするコンポーネントを考えてほしい。ロードされてから一定時間が経つと、そのインスタンスは使えなくなる。そこで、時々リロードをしなければならなくなる。
Mock Roles, not Objects(p.7)

最初はシンプルな正常ケースを書きます。

@Test
public void キャッシュされていないオブジェクトはロードする() throws Exception {
 
    final ObjectLoader mockLoader = context.mock(ObjectLoader.class);
 
    context.checking(new Expectations() {
        {
            oneOf(mockLoader).load("KEY1");
            will(returnValue("VALUE1"));
        }
    });
 
    TimedCache cache = new TimedCache(mockLoader);
    assertThat((String) cache.lookup("KEY1"), is("VALUE1"));
}

「cache.lookup()のタイミングで、TimedCacheのコンストラクタに渡されているmockLoaderのloadが呼び出され、その際には"VALUE1"が返されるのでlookupの戻り値も"VALUE1"になる」というからくりです。重要なことですが、この時点ですでにObjectLoaderというコラボレーター(隣接オブジェクト)が発見されています。図示するとこんな感じ:

1つ補足しておくと、ここでObjectLoaderをモック化しているのは、それが外部リソース(DBなど)にアクセスしているっぽかったり、サードパーティーライブラリを想定していたりするからではありません。テスト対象のオブジェクトが動作する上で必要なコラボレーターを、テストを書きながら発見することがモックを使ったTDDの中核です。


この後、2回目に呼ばれたときにはObjectLoaderを呼び出さずにキャッシュを使うテストも当然書かなければいけないのですが、アサート文が2つになるだけですので省略します。キャッシュを実装した段階でコードはこうなっています:

public class TimedCache {
 
    final private ObjectLoader loader;
    final private Map<String, Object> cachedValues = new HashMap<String, Object>();
 
    public TimedCache(ObjectLoader loader) {
        this.loader = loader;
    }
 
    public Object lookup(String key) {
        if (!cachedValues.containsKey(key)) {
            cachedValues.put(key, loader.load(key));
        }
        return cachedValues.get(key);
    }
}

さて、ここで時間の概念を導入しましょう。仕様書にあるこの一文ですね。「ロードされてから一定時間が経つと、そのインスタンスは使えなくなる。そこで、時々リロードをしなければならなくなる。」これに対応すると、テストはこうなります:

@Test
public void タイムアウト後のキャッシュされたオブジェクトはロードする() throws Exception {

    final Clock mockClock = context.mock(Clock.class);
    final ObjectLoader mockLoader = context.mock(ObjectLoader.class);
    final ReloadPolicy mockPolicy = context.mock(ReloadPolicy.class);

    final Timestamp loadTime = new Timestamp("2011/09/17 00:00:00.000");
    final Timestamp fetchTime = new Timestamp("2011/09/17 00:00:01.000"); // 1秒後
    final Timestamp reloadTime = new Timestamp("2011/09/17 00:00:02.000"); // 2秒後

    context.checking(new Expectations() {
        {
            exactly(3).of(mockClock).getCurrentTime();
            will(onConsecutiveCalls(returnValue(loadTime), returnValue(fetchTime), returnValue(reloadTime)));

            exactly(2).of(mockLoader).load("KEY");
            will(onConsecutiveCalls(returnValue("VALUE"), returnValue("NEW-VALUE")));

            atLeast(1).of(mockPolicy).shouldReload(loadTime, fetchTime);
            will(returnValue(true));
        }
    });

    TimedCache cache = new TimedCache(mockLoader, mockClock, mockPolicy);
    assertThat("ロードされたオブジェクト", (String) cache.lookup("KEY"), is("VALUE"));
    assertThat("キャッシュされたオブジェクト", (String) cache.lookup("KEY"), is("NEW-VALUE"));
}

図に表すとこんな感じ:

モックを使ったTDDのユニットテストが通常のTDDと大きく違う点が、実はここに現れています。通常のTDDの場合、こうしたコラボレーターが登場するのは「レッド・グリーン・リファクタリング」のうち、主に「リファクタリング」のプロセスになります。まずは動くようにした上で、より適切な責務分割を目指すわけですね。


それに対して、モックを使うとコラボレーターは、テストを書いている段階すなわちレッドの手前で登場します。実際に写経してみるとわかるのですが、登場するオブジェクトとそうしたオブジェクト間のインタラクションを相当はっきりイメージできていないと、テストを書くことができません。


この意味で、モックを使ったテスト駆動開発ではテストを書くことが設計することに直結するわけです。ただし、ここで設計されるのはあくまでオブジェクト間のインタラクションであり、クラスではありません。「では、クラスはどうやって設計するのか?」という疑問が当然出てくるわけですが、GOOSを読むと、いったんはインターフェイスの匿名クラスとして実装し、あとから名前のついた適切なクラスを抽出するというスタイルがとられていることがわかります。そうして抽出されたクラスの内部で責務があいまいだと感じた場合には、再びユニットテストを書いてコラボレーターを発見するというプロセスを繰り返すことになります。

まず受け入れテストを書く

これが、このアプローチが「外から内へ」と言われる理由です。外側(=システムへのエントリポイント)に近い場所からユニットテストを書き、インターフェイスを発見しながら内側へと入っていくわけです。すると当然、「最初のテストはどこから書き始めるのか」という疑問が生じると思いますが、それに対する答えは「まず受け入れテストを書く」ということになります。全体を図示するとこんな感じでしょうか:

実際には、受け入れテストをグリーンにする過程で必要に応じてユニットテストを書きつつインターフェイスを発見し、グリーンになった後も責務があいまいなクラスに対してまたユニットテストを書き、という具合にループが2重になっています。一見、受け入れテストの話とユニットテストの話は別々のものであるようにも見えます。しかし、モックを使ったユニットテストが、オブジェクト間のインタラクションという内側にフォーカスしている分、システムが全体として正しく動いているかという外側を明示的に意識する必要があると考えれば、両者をまとめて1つのプロセスとする考え方は筋が通っていると言えるでしょう。「受け入れテスト+モックを使ったユニットテスト」をセットで考えれば、ユニットテストを書くという行為自体が従来のリファクタリングフェーズに当たると考えることができるかもしれません。


なお、受け入れテストから始めるというこの考え方は、ふるまい駆動開発(BDD:behaviour-driven development)とも強い親和性があります。すなわち、「システムが何をすべきかをテストで表現する」ことにより、どこから始めればよいか、どうすればそのフィーチャの実装が完了したと言えるのかという問いに同時に答えることができるのです。

最後に

プログラマにとって、コーディングをしながら構造を発見していくプロセスというのはかなりエキサイティングに感じられるものですが、現実問題として本当に何もないところからこれを始めるのはいつになったら終わるのか全然わからないという怖さがあります。したがって、ある程度は事前に分析/設計を行い、大まかな構成のメドを立てておく必要があるということになります。よく批判される"Big Design Up Front"を避けつつも、事前の分析/設計を適切に行うバランス感覚が重要だということですね(それが簡単にできれば苦労はしないという話ですが)。


モックに興味を持たれた方は、ぜひ「ロールをモックせよ」を読んでみてください。ここで取り上げたこと以外にも重要な示唆が色々と書かれています。また、サンプルコードはこちらで公開しています。

SIを仕事にするということ

パラダイムを学ぶことと、実際にデリバリーすることとのバランスについて。あるいは転職報告。

導入

8月1日にグロースエクスパートナーズ株式会社に入社しました。人生で2回目の転職となります。入社してまもなく一ヶ月が経とうとしていますので、本日はその報告を。ブログ翻訳プレゼンに続く舞台裏記事の第4段ですね。私とは違う物事のとらえ方をする方々も多くいらっしゃることは重々承知しておりますし、それを批判するものではないこともあらかじめご了承ください。

転職をした理由

私が7月まで勤めていたのは、いわゆる「ITゼネコン」と呼ばれる元請けSIerでした。開発の実務は協力会社さんにお任せしつつ、自分はメールと打ち合わせに埋もれる日々を送っていたわけです。要件定義から保守まで一通り経験できたという意味で学ぶこともありましたし、アーキテクチャ策定やデータモデル設計のようなことも隙を見てやっていたことは事実ですが、ソフトウェアを作るということと自分との間に、どうしても超えられない壁が立ちはだかっているような感覚が拭えませんでした。これを読む多くの方がいろいろな立場で普段職場で目にしているであろう「アレ」と言えば、大体おわかり頂けるでしょう。


それと平行して、DDDやDCIといったソフトウェアのパラダイムを学びつつ、それをアウトプットする作業もここ数年で実を結び始めていました。DDD日本語版の翻訳は自分にとっての1つの大きな成果ですし、DevLOVEをはじめとしたコミュニティでも話をする機会を頂けるようになりました。


要するに「仕事は日々の糧を得るための手段として割り切り、空いた時間で勉強をしつつ、コミュニティに対してアウトプットしていく」という日々になっていたのですが、やはり自分の中にある矛盾が少しずつ大きくなっていきました。

エリックとの約束

4月にDDDの著者であるエリックが来日し、かなりいろいろなことを話すことができました*1。エリックと会うまでは、DDDを理論書としてとらえていたのですが、本人と会って話したことでずいぶんと考えが変わりました。別れる日の夜にエリックに「これまでは翻訳者として批評家としてDDDを日本に紹介してきたけれど、これからはそれを実践していきたい」と伝えたところ、エリックの答えは、"Right thing to do, and you SHOULD do"でした。

野村さんとの会話

その後、ジュンク堂様にてフューチャーセンターファシリテータの野村さんとトークセッションを開催する機会を頂きました。事前準備のときに、「ユーザとのコミュニケーションの中で業務のとらえ方を理解し、ソフトウェアを作っていく*2」ということを話した後で、野村さんから「そういう開発をしてほしいという人がいた場合、どこにいけばいいですか?」と訊かれました。おそらくすごく素朴な質問だったと思うのですが、それに対して「僕に紹介してください」と言えなかったことが、本当に恥ずかしかったことを覚えています。


結局のところ、組織に守られながらその組織に対して文句を言い、自分という人間を認めてくれる人たちの中で夢を語るのは、ものすごく居心地がよく、楽なことでした*3。でもそれは、中学生が小遣いをもらいながら自分の母親をけなし、仲間内でギターを弾いて悦に入っているのと変わらない。「じゃあそれ、路上で、知らない人の前で胸を張って弾けるの?それで金を稼げるの?


それをやろうと思ったときに目の前に立ちはだかるのは、「生々しい現実」です。要件定義はやったことがあるし、設計も保守もやったことがある。アジャイルや設計手法の本も割と読んでいる。でも、「自分でリードしながら開発をやり切ることができるのか?」と問われたときに、胸を張ってできると言う自信はありませんでした。ユースケース記述のフォーマットを知っていることと、実際にユーザーさんとやり取りをしながら実装可能な設計に落とし込むこととの間には、「ボクシングジムに通うこととリングに立つこと」程度の開きがあります。私にはまだ、師が必要なんですね。

今の会社を選んだ理由

少し前後するのですが、転職自体はDDDを翻訳する前から考えていました。というより「DDD翻訳の仕事が入ってそれどころではなくなった」という表現が正しいですね。今のIT業界では「SIerからサービスを提供する企業へ」という人材の流れが(少なくとも私の周りでは)起きているように感じていますので、それについても少し触れておきます。


私も「実装技術」のようなものにもっと焦点を当てて力を伸ばしたいと思った時期もあり、非SIerへの転職を目指そうと思ったこともありました。しかし、私には「専門的で特殊なアルゴリズムを実装するスキル」はありません。それを育てたいとも思ったのですが、あまりそこをゴリゴリやっている自分の姿をリアルにイメージできなかったのは事実です。おそらく私には「漠然とした物事を言語化する」「物事を抽象的にとらえて、論理的に切り分ける」という作業が似合っているのでしょう。そういう経緯とDDDからの流れがシンクロして、「バリューストリームの中で自分の手を動かしつつ」「企業をお客様として業務に使うアプリケーションを作る」ためには何をしたらいいかを考えるようになりました。


一時は自分で会社を作ることも考えたのですが、尊敬する友人*4から何冊か本をプレゼントして頂き、それを読んだ結果、踏みとどまることにしました。一方で、これができる会社を探そうと思うと、それなりに大変だと思うのですが*5、私の場合は、幸運にもQCon Tokyoをきっかけとしたゆうすけさんとの出会いがあったおかげで、「探す苦労」をせずにすみました。本当にいろいろな方に助けられて今の自分があると思います。


GxPは「DDDでの開発」や「アジャイル開発」を必ずしも前面に押し出している企業ではありません。むしろ「ソフトウェアを作る」ということを、ものすごく真っ当にやっている会社です。手法としてのDDD、手法としてのアジャイルが強調されることはなくても、DDD的モデリングやチケット駆動型の開発がごく当たり前のものとして普通に行われています。これはとても健全で、正しいことだと思うのです。

入社してからの生活

入社してからの4週間でやったことを挙げると、こんな感じです。

  • 提案書を書く
  • ソフトウェアアーキテクチャを設計する
  • タスクスケジュールを引く
  • コードを修正して、テスト計画を書いて、デプロイして、テストする
  • Jenkinsを立てて、自動テストツールと戯れる
  • デプロイを簡単にするためのシェルを書く
  • スレート端末と戯れる
  • JSONプロトコルのRESTっぽいAPIをアプリに追加する
  • Groovyで移行スクリプトを書く
  • Lucene in Actionを読み返す


見て頂いてわかるとおり、上流/下流という区別なく、割と何でもやっている感じです。でも、「華々しいこと」は特にやっていません。スタンドアップミーティングもやってないし(朝会はそろそろやる)、顧客の前でドメインモデルを書いたりもしていない。壁にバーンダウンチャートが貼ってあるわけでもない。テストコードは必要に応じて書きますが、ペアプロやいわゆるTDDも特にやっていません(これは声をかければできるかも)。提案書はパワポで書いていますし、表を作るときにはExcelも使います。機能一覧とかテストケースもExcelで書いています。要するに「アジャイル」と「ウォーターフォール」という区別にこだわらず、必要なことを当たり前にやっているだけです。たぶん、「アジャイル」という言葉にこだわるあまりにウォーターフォール的なものを忌避しすぎると、不幸なことになると思うんですよね。


一番大きな変化は、仕事に関してグチや言い訳を言わなくなったことでしょうか。上司は話がわかるし、仕事ができる方々に囲まれている。ミスしていないわけではないし、見落としをリカバリーするために会社に泊まったりもしましたが、それも自分で勝手にやったことです。すべてに自分の手が届くこの状況下では、何かあれば完全に自分の責任ですよね。

まとめ

ソフトウェアをデリバリーすることには、多くの「泥臭い作業」が伴います。そしてその「泥臭い作業」は本に書かれたり、語られたりすることがあまりない。それはたぶん、語るまでもなく当たり前のことであったり、語れるほどには言語化されていなかったり、語るほど面白くなかったりするせいでしょう。だからといって、「そういうこと」をしなくていいわけではない。このあたり、パラダイムと泥臭い作業をひっくるめた「デリバリー」の全体像が見えずに苦しんでいたわけですが、他にもそういう方はいるのではないかと思います。「バリューストリームの中で自分の手を動かしつつ」「企業をお客様として業務に使うアプリケーションを作る」ことができる場所があるんだということは、多くの方に伝えたいメッセージです。


最後に1つ。コミュニティで出会った人と働くことに、少し怖さはありました。プレゼンをするときにはそれなりの準備をしてデキるコっぽく装うわけですが、仕事となれば完全に生身での勝負ですからね。「がっかりされたらどうしよう」とかは当然思うわけです。でも・・・「一番こわいのはこの痛みなの?痛いのってこわい? あんたいつまでも…大人になってもひとりじゃなんにもできない方がもっとこわいとは思わないの?」ということですよ。


あしたって今さ!

*1:豆蔵の羽生田さんをはじめ、この機会を与えてくださった多くの方々に感謝します

*2:当たり前のこと、というより他にどうやるのか、今となっては謎ですが

*3:誤解が怖いので念のため強調しますが、コミュニティがそういう人の集まりだと言っているわけでは断じてありません。「自分がどう向き合うか」というきわめて個人的な問題です。

*4:最近幸せそうなあの人です。おめでとうございます!

*5:「そんな会社あるんですか?」と何人もの人から聞かれました

書評:アジャイルサムライ―達人開発者への道

オーム社様と監訳者の方々より献本いただきました。厚くお礼申し上げます。ありがとうございます!


アジャイルサムライ−達人開発者への道−

アジャイルサムライ−達人開発者への道−

はじめに

まずは「読者の声」から:

最初は、軽いノリの入門書だろうと高をくくっていた。「マスター・センセイ」だし、そもそも「サムライ」だし。でも、そんなに甘くない。軽い文体とは裏腹に、アジャイルな開発のありかたが、きわめてロジカルかつ網羅的に語られている。ありそうで、なかった本。手元に置いておけば、きっといいことがあるはずだ。
> 和智右桂 『エリック・エヴァンスのドメイン駆動設計』訳者

私はこの本に査読者として関わらせていただきました*1。原文も含めてかなり丁寧に読み込んだという自負はあるのですが、「読者の声」として書かせていただいたのはそのときの率直な印象です。要約すればこの言葉に尽きるのですが、書評としましては、「アジャイルな開発のありかたが、きわめてロジカルかつ網羅的に語られている」この一文をもう少し展開することにします。

アジャイルな開発のありかた

アジャイル」という言葉はずいぶんと色々な誤解に取り巻かれているように思います。端的に言えば「ウォーターフォールに対するアンチテーゼ」という側面に目が行きすぎて、ウォーターフォールのいやなところを解消してくれるもの、というイメージを持っている人が多いのではないでしょうか。「あの大量のドキュメントから解放される」と思っているエンジニア、「いつだって要件を変えていい」と思っている顧客・・・。確かにアジャイルならそういうこともできるかもしれませんが、そのためにやらなければいけないことがあるはずです。


アジャイルな開発のありかた」という表現をしたことには理由があります。この本の中心に据えられているのが、ペアプロテストファーストといったプラクティスに留まらず、そもそものアジャイルの目的であるということを伝えたかったのです。第1章「ざっくりわかるアジャイル開発」を開くと、まず目に飛び込んでくる絵にはこう書かれています。「お客さんにとって価値ある成果を届ける・・・毎週ね!」―動くソフトウェアを顧客に届け、そこからフィードバックを受け取る。そのためにどうすればよいか、ということが語られている本なのです。

ロジカル

文体の軽さと論理的な厳格さは両立し得るものだということを、私はこの本で学びました。「ロジカル」という言葉で表現したかったのは、「構造がしっかりしている」ということです。どの話も、ベースとなっている理念から始まり、それを実現するためにはどうすればよいかが1つ1つ積み上げられて語られています。


これは言い方を変えると、この本を読むことでそれぞれのプラクティスを「なぜやらなければならないのか」がわかるようになるということであり、同時に「実際にどう使えるか」がわかるようになるということです。たとえば、本書のハイライトのひとつである「インセプションデッキ」の説明も、「プロジェクトがだめになってしまうのは、最初の認識が合っていないからだ」という話から始まっています。そして、インセプションデッキをはじめ、リリースボードやバーンダウンチャートについても、実際にどう使うのかということが丁寧に(そして面白く)語られているのです。単純な「書き方」を示したハウツー本とは一線を画していると言っていいでしょう。

網羅的

アジャイル開発が大切にしている価値とは何か」から始まり、チーム作り、認識合わせ、計画作り、要件定義、イテレーションの回し方、さらにはユニットテストリファクタリングといったかなり具体的なプラクティスまで、アジャイル開発を実践する上で必要になるであろうことが一通りこの薄さに収まっています。ではその分表層を触っただけかというと、そんなことはありません。たとえば、ユーザーストーリーとして書き留めておくべき要件の粒度やそれをどう使うかといった話にもきちんと触れられています。


逆にアジャイル開発に関する本を何冊か読んだことがある人であれば、「いまさらこの薄さの本を読む必要はないだろう」と思うかもしれません。しかし、そんなこともありません。インセプションデッキの話は本邦初公開ですし、それ以前にチームメンバーに求められるロールや、運営の手法など、必ず新しい発見があるはずです。


なぜこんなことができるのか正直不思議なのですが、おそらくは「豊富な実践によって蓄積した知識を自分の言葉で語っているだけ」ということなのでしょう。「恐ろしく頭のいい人」それが私が著者に対して抱いているイメージです。

最後に

翻訳書として見たときに、本書の素晴らしいところをあとふたつ挙げておきましょう。1つは「訳質」、もう1つは「監訳者あとがき」です。訳質については実際に手にとってご覧いただきたいのですが、翻訳であることを意識させないレベルで完璧な日本語になっています。また、訳語の選択についても練られていて、「期待マネジメント」という少し耳慣れない言葉に対しては、日本語版だけのコラムが付いていたりもします。監訳者あとがきも必読です。本書の内容について整理しつつ、アジャイルの歴史の中にしっかりと本書が位置づけられています。アジャイル開発について学びたい人のための金字塔が打ち立てられたことを、心からお祝いします。「とにかく読んでみてください」が私からのメッセージです。


アジャイル」という言葉を見て、「自分には読む必要がない」と思ってしまう人がいるかもしれませんので、すこし蛇足を加えます(そういう方がこのエントリを読んでいるかどうかは甚だ疑問ですが)。ここで語られているのは、「顧客に価値を届けるための方法」です。たとえ職場がウォーターフォールであっても、Wordで要件定義書を書くのだとしても、この本で語られている内容は必ず役に立ちます。副題にある「達人開発者」とは、「コードを書く人」ではなく、「顧客の願望をかたちにして届けることのできる人」なのですから。


※なお、冒頭にAmazonのリンクを貼りましたが、PDFが必要な方はこちらのオーム社さんのサイトからご購入することをお勧めします。
http://estore.ohmsha.co.jp/titles/978427406856P




アジャイルサムライ−達人開発者への道−

アジャイルサムライ−達人開発者への道−

*1:実は人生初の査読でした

書評:プログラミングGroovy

著者の方々より献本頂きました。厚く御礼申し上げます。ありがとうございます!


プログラミングGROOVY

プログラミングGROOVY

はじめに

まずはじめに、これはGroovyを好きな方々、Groovyのことが気になっている方々にとっての必携書になると思います。素晴らしいお仕事をなさった著者のみなさまに感謝します。


さて、本の紹介をする前に、私のGroovyに対するポジションを明らかにしておきたいと思います。過去の記事を見て頂けばおわかり頂ける通り、私は時々Groovyを触っています。たとえばDSLを書いてみたいとき、DCIアーキテクチャで何か作ってみたいとき、モダンTDDを試したいとき・・・。意図が伝わりやすいGroovyのコードと、動くアプリが一瞬で作れるGrailsは、私にとって実に頼もしいツールです。


「ちょっと待て、Scalaのエントリも書いているじゃないか」そうですね、先日のTDDBC仙台でもScala組だったように、私は時々Scalaも触っています。「どっちなんだ?」私自身はこの2つの使い分けをかなり意図的に行っています。


一言で言えば、ScalaネクスJava、Groovyは軽量Javaです*1Scalaを使うときには、Scalaの持つJavaにないパラダイム、特に関数型のパラダイムを学ぶためという明確な目的があることが多いです*2。逆に、とにかくさらっとコードを書きたいときにはGroovyを選択しています。そういう意味で、動的言語的な破壊力を隅々まで知り尽くしているわけではありません。そういう比較的中立的(?)なGroovyユーザの視点で書かれた紹介だと思って続きをお読みください。

概要

この本の対象読者について、「はじめに」に書かれている言葉を引用します。

本書の対象読者としては、Groovyのことをまだあまりよく知らないJavaプログラマを想定しています。(p.vi)

もうすこし補うと、Javaをそれなりに使い込んでいて、Javaの「面倒なところ」も含めて理解している方のツボにはまるのではないかと思います。


もう1点、この本の読み方ですが、まずは適度に写経しながら全体をさらっと通読してみるのがいいと思います。細かいAPIは表形式でまとめられていますので、必要に応じて後で調べることができます。むしろ頭の中に、「あ、こんなことができるんだ」というインデックスを作ることが重要だと思います。以下、勝手に3部に分けてみました。

準備編

「まだGroovyに触ったことがない」という方は、第1章「Groovyの世界へようこそ」をさらっと読んでみてください。「Javaを置き換えるとまでは言わないけれど、とりあえず使えるところから使ってみなよ、便利だからさ」というGroovyの声が聞こえてくると思います。


第2章「Groovyの利用方法」は開発環境整備です。これから準備される方はここを見ながらどうぞ。なお、IDEとしてIntelliJがイチオシされていますが、初めてGroovyに触るのであればエディタの方をお勧めします。IntelliJは私もぼちぼち触り始めており、使い込めばかなり良さそうだと思っているのですが、ある程度設定しないといけないことはありますし、一度に色々やると混乱しますので・・・。

写経編

準備が整ったら、ぜひ第3章、第4章を写経してみてください。第3章「プログラミング言語Groovy」ではBeanやGString、クロージャ、コレクションなどGroovyの「軽量」度合いがよく伝わると思います。たとえば、Beanであれば余計なものを一切書く必要がありません。

class MyClass {
    String msg
}

これでGetter/Setterが自動生成されます(同書, p.52)。JavaでもIDEを使えば自動生成できますが、リファクタリングでフィールドが移動する場合などを考えると結構メンテナンスが面倒であることや、そもそもの圧倒的な可読性のよさを考えると、やはりこうやって書けるのはうれしいものです。


第4章「Groovyのライブラリ」ではGroovyに準備されているさまざまなライブラリが紹介されます。ここで紹介されているものは、笑いがこみ上げるくらい小気味よいものばかりです。たとえば、こんなXMLを出力する処理を考えてみてください。

<Products>
    <Product type='regular'>
        <Name>Instant Noodle</Name>
        <Price>147</Price>
    </Product>
</Products>

Javaであれば、あの面倒なDOM/SAXプログラミングが頭をよぎるところですが、Groovyならこれだけです(同書, p.130)。

import groovy.xml.MarkupBuilder

def xml = new MarkupBuilder()

xml.Products() {
    Product(type:'regular'){
        Name('Instant Noodle')
        Price(147)
    }
}

すごいですよね。動的言語である強みを存分に活かした設計と言えるでしょう。


また、第4章はデータベース処理についても、それなりの紙面が割かれているのが大きな特徴だと思います。実際にエンタープライズで使おうと思ったときに必要なものはきちんと押さえてくれているということですね。

応用編

この後はすこし進んだ話題になります。第5章「進んだ話題」で紹介されているものは、すぐには使わないかもしれないものも多いかもしれないですが、適度に写経しながら読み進めると色々参考になります。AST変換で特に目を引くのが、@Singletonや@Immutableというアノテーションですね。こうしたアノテーションを使うことで必要なコードが自動生成されるというのは、Groovyの大きな魅力です。


第6章「Groovyのエコシステム」ではGroovyを取り巻くさまざまなプロダクトが紹介されます。1つ1つは基本的には概略ですが、いくつかのプロダクトにはそのまま動かせるコードもついていますので、手を動かしながら全体像をとらえるのに丁度よいと思います。


第7章「Groovy1.8の新機能」は最新版で追加された機能の紹介ですね。ちなみに、さらっと書いてありますが、Sqlクラスがページングに対応したそうです。これってすごいことじゃないですか?(同書, p.274)

sql.eachRow('select * from EMP', 101, 20) { row ->
    // 各行ごとの処理
}

終わりに

エントリの本文でも書いている通り、この本は電車の中で「読む」のではなく、ある程度時間をとって手を動かしてみるのがよいと思います。新しい考え方が紹介されているのではなく、「これまでやってきたことがこんなに楽にできますよ」ということが豊富なサンプルコードとあわせて説明されているからです。書くコードの量がすくなくてすむGroovyの性質上、ある意味写経のやり甲斐はないかもしれませんが、読み終わる頃にはある程度Groovyのことがわかるようになっているのではないでしょうか。Javaプログラマに対するGroovyの敷居の低さは驚異的です。


実は、Groovyにも黒魔術っぽいところはあります。私自身もそんなに知っているわけではありませんが、たとえば、以前DSLでご紹介したASTの操作は明らかにそちらの世界に一歩踏み出しているでしょう。ただ、この本では、Groovyのそういう部分に対してそれほど触れていません。Groovyの持つ普段の仕事を確実に楽にしてくれるであろう部分が、いわば「謙虚に」まとめられています。この辺りは、「Groovy便利なんだからもっと使えばいいのに」という著者の方々の思いがこもっているように感じられます。


個人的にはGroovyに対して、どう控えめに見てももっと評価されてよいと思っています。この本がきっかけで、Groovyがもっと多くの人に使ってもらえるようになることを祈っています。


プログラミングGROOVY

プログラミングGROOVY

*1:こういうまとめはどなたかがなさっているかもしれませんが、不勉強ゆえよく知らず・・・

*2:先日のTDDBCはちょっと特別ですね。

技術系プレゼンテーションの前にやっておくべきこと

プレゼンテーションの下準備を行う際に、普段心がけていることを整理する。

導入

ブログ翻訳に続く舞台裏シリーズの第3弾として、プレゼン前の下準備の際に自分が心がけていることをまとめておきたいと思います(もう舞台裏ネタはないので、これで最後です)。2010年10月のDevLOVE "Beautiful Development"が、私が個人として行う初めてのパブリックスピーキングでした*1。その時から今まで何度か登壇の機会を頂いていますが、最初のとき以来、事前に必ずやるようにしていることが3つあります。一般的に基本と言われるものがすべからくそうであるように、たいしたことではないのですが、地道にやることでそれなりの効果は得られているような気がします。もちろん、「こうしなければいけない」という話ではありません。「私はこうしています」という話です。参考にして頂ければ幸いです。


内容に入る前に、すこし本のご紹介を。プレゼンテーションのマニュアルとして私が読んだのは次の2冊です。

プレゼンテーションzen

プレゼンテーションzen

  • 作者: Garr Reynolds,ガー・レイノルズ,熊谷小百合
  • 出版社/メーカー: ピアソン桐原
  • 発売日: 2009/09/04
  • メディア: 単行本(ソフトカバー)
  • 購入: 51人 クリック: 927回
  • この商品を含むブログ (186件) を見る
パブリックスピーカーの告白 ―効果的な講演、プレゼンテーション、講義への心構えと話し方

パブリックスピーカーの告白 ―効果的な講演、プレゼンテーション、講義への心構えと話し方

プレゼン資料を見て頂いたことのある方ならおわかりのとおり、私はzen主義者です。ただ、それほど原理的ではなく「写真を使ってメッセージを伝える」ということを除いて、書いてあったことを色々忘れているような気もします。『パブリックスピーカーの告白』もやはりいい本で、これを読んでからプレゼンを技術としてとらえるようになりました。


さて、私が必ず守っている教えは、『zen』から2つ、『告白』から1つもらっています。

メッセージを明確に(プレゼンテーションzen)

プレzenの奥義は「Flickrを使いこなすこと」ではもちろんありません。大切なのは「メッセージを伝えること」です。「メッセージ」という言葉が何か格式張った印象を与えるのであれば、「エレベーターピッチ」と言い換えてもいいでしょう。要するに、聞きに来てくれた方(しかも、仕事帰りだったり、休みの日だったりする貴重な時間を割いて来てくれた方)に対して、何を持って帰ってほしいのか、一言で、ということです。


具体例のためにさらに舞台裏をさらすと、

  • ドメイン駆動設計入門」のメッセージは、「オブジェクトが表現するのは概念である」MVCのMはモデルであって、スクリプトではない」の2つでした。サブテーマとして、これを軸にDDD本を概観し、その後の佐藤さん、増田さんの発表の土台をつくる、というものがありました。
  • 戦略的設計入門」のメッセージは、「戦略的設計とは何かを具体的にイメージしてほしい」というものでした。抽象的な議論が多く、DDD本を読んでいるだけだとイメージが湧きにくいのですが、かなり重要な箇所なのです。これはドメイン駆動設計入門の続編であると同時に、DDD前夜祭陽の巻の第4部編でもありました。
  • Growing Grails Application」のメッセージは、「DDDを実践するために、欠けているものを埋めよう」でした。DDDは設計の話はしてくれますが、意外と具体的なプロセスの話をしてくれておらず、実際現場に適用するには、いくつかのプラクティスと組み合わせる必要があるのです。もちろん、抜き打ちモデリングセッションで交流を、というのも大きなテーマでした。


他にも、「旅行手配ドメイン」とか「Geb+Spock」とか、スパイスっぽいものはあるのですが、メインとなるメッセージをぼかしてしまうような話はかなり削っています。メッセージに焦点を合わせ続けられるようにし、余計なものを削る上では次のプラクティスが助けになります。

発表原稿を作成する(プレゼンテーションzen)

プレzenのいいところは、スライドを眺めているだけでそれなりに楽しいところ。悪いところは、話者がいないと何の話かさっぱりわからないところです。それを補うためにプレzenには「ハンドアウトを配ればいいじゃないか」という素朴なソリューションが提示されます。


とはいえ、「ハンドアウトをプリントアウトするのもそれなりに手間がかかるし、どうしたものか」と考えた結果が、「発表前に書いて、後でブログで公開」でした。やはり原稿を書くとストーリーを前もって組み立てられるので、論理に飛躍があるところとか、脱線しているところに気づくことができます。ただ、読み上げはしていません。ライブ感が失われるような気がするので。じゃあ、原稿も読まずにどうやっているのか、暗記でもしているのか、という話が次のプラクティスにつながります。

リハーサルをする(パブリックスピーカーの告白)

これは目からウロコが落ちました。曰く「うまくなりたいなら、練習しろよ」と。発表原稿を書くのとは前後するケースがありますが、私は最低2回リハーサルをやっています。PCを操作しながら、立って実際に声を出してやっているので、傍から見たら相当奇妙に見えるでしょう。


2回というのは、別に勤勉なわけではありません。1回目は時間が大きくズレたり、話がうまくつながらなかったり、脱線したり、言いよどんだり・・・要するにうまくいかないんですよ。それを発表原稿にフィードバックして、スライドを増減させた上でもう一回やっています。これでうまくいくこともありますが、うまくいかないこともあります。そういう場合は、もう一度。大体3回やれば感じがつかめます。


このときに心がけているのが、「セリフを覚える」のではなく、「論点を整理する」ことです。「このスライドではこれとこれとこれを伝えよう」―こうしておけば、わりと普通に話すことができます。たまにセリフがとんでいることもありますが、本当に伝えるべきメッセージが1つか2つに絞られているので、多少細かいことを言いそびれても、全体のメッセージを大きく外すことはありません。時間も、すこし早めに終わるように事前に調整していれば、手元のタイマーを見ながら話して大体ぴったりに収められます。


ちなみに、タイマーとレーザーポインタのついたリモートコントローラを @t_wadaさんに紹介してもらいました。お値段はちょっとするのですが、それに十分見合う便利なアイテムです。

LOGICOOL プロフェッショナルプレゼンター タイマー機能・LCD搭載 R800

LOGICOOL プロフェッショナルプレゼンター タイマー機能・LCD搭載 R800

最後に

一言で言えば、私のプレゼンは「上演」です。準備してきたものを「演じる」わけですね。だから、準備がしっかりできていれば、当日はそれなりに心に余裕が持てます。実際にプレゼンをしているときに意識しているのは、なるべく聞いてくださっている方の反応を見ること。やっぱり、うなずいてくださったり、笑ってくださったりすると、うれしいじゃないですか。スライド作りとか、リハとか、それなりに準備に時間はかかるんですけど、でも喜んでくださる方の顔を見ると、やってよかったなー、と思います。


一番大事なことですが、プレゼンは一人ではできません。場を準備して登壇させて下さる方がいて、聞いてくださる方がいて、初めて成り立つことなんですよね。とても感謝しています。いつも、どうもありがとうございます!

*1:学会発表は以前にもやったことはありますが、あれはちょっと別ですので。また、2010年のデブサミで、JavaEE勉強会のメンバーとパネルディスカッションをやらせて頂いています。あれは勉強になりました。

TDDの実践 〜TDDBC仙台レポート〜

2011年7月2日に開催されたTDDBC仙台のレポート。

導入

「TDD Boot Camp」通称TDDBCにはずっと参加したいと思っていたわけですが、今回仙台で機会を得ることができました。最初はJavaでと思っていたのですが、Scala組に入れて頂きまして、山中(@ymnk)さん、武田(@takedasoft)さんと3人でチームを組んでペアプロという貴重な体験をさせて頂きました(どうもありがとうございました!)。最終的には仕様変更2が何となくかたちができたところで時間切れとなりました*1


プログラムが組み上がっていく過程や、興味深いリファクタリング、うっかりテストを書かずにコードを修正してしまったことによるバグの埋め込み、モックを使ったタイマー処理の分離など、非常に興味深い体験を数多くさせて頂きましたので、ここにご報告させて頂きます。なお、作業中のコードは記憶を頼りに書いていますので、もしかすると動かない(コンパイルすらできない)可能性もありますが、その点はどうかご容赦ください(バージョン管理しておけばよかったと激しく後悔)。なお最終的なコードはこちらで公開しています。


なお、ネタバレを気にして和田さんに確認をとったのですが、「問題なし」とのこと。プロの仕事に頭が下がります。

前半

お題
LRU方式で、最も使われていないキーに紐づくデータを追い出すようなマップを実装する。

  • 特定のキーに対して値を設定/取得された場合に使われたとみなす

まずはテストから:

@Test
def マップに1つ値を設定するとそれが取得できる() = {
    val lru = new Lru()
    lru.put("KEY1", "VALUE1")
    assertEquals("VALUE1", lru.get("KEY1"))
}

コンパイルすら通りませんが、外側=使う側から設計していくのがTDDです。たいしたことのない実装に見えますが、このコードを書いた瞬間に、かなりの設計判断をしています。

  • クラスの名前はLruにする。
  • Mapと同じように、putで値を設定し、getで取得する。


まずはコンパイルを通るようにして(つまり、クラスとメソッドを空で作って)テストを実行すると、結果はレッドになります(getでnullが返りますので)。これを通すためのコードも簡単です。

class Lru {
    
    var key:String = _
    var value:String = _
    
    def put(key: String, value: String) = {
        this.key = key
        this.value = value
    }
    
    def get(key: String):String = {
        value
    }

おなじみの仮実装ですね(よくよく見ると、keyフィールドも要らなかったですね)。しかし、テストを実行させてグリーンになることを確認するのは大切です。自分たちがすくなくとも、テストの書き方と実行のさせ方をわかっていることが確認できるわけですから。こうして、これからの「黄金の回転」のための基礎ができあがります(この段階ではリファクタリングをやりませんでしたが、今思えば、「クラス名ってLruでいいんだっけ?」という議論をしてもよかったかもしれません)。


次に、この実装に対してレッドになるテストも簡単に書けます。

@Test
def マップに2つ値を設定するとそれが取得できる() = {
    val lru = new Lru()
    lru.put("KEY1", "VALUE1")
    lru.put("KEY2", "VALUE2")
    assertEquals("VALUE1", lru.get("KEY1"))
    assertEquals("VALUE2", lru.get("KEY2"))
}

もちろん、仮実装で粘ることもできるのですが、ここでフィールドをマップに変えました。

val map = new HashMap[String,String]

個人的には、TDDで一番"難しい"ポイントは「どのタイミングで最終的な実装に切り込むか」にある思っています。テストに通すだけなら、永遠に仮実装で凌ぎ続けることもできるわけです。ただ、もちろんそれでは実際の使用に耐えられません。重要なのは次の2点だと考えています。

  1. テストは無限な現実の1ケースであり、実装は抽象的に考える必要がある。
  2. テストをグリーンにするコードは、最終形の途中にある不完全なかたちになる。

この不完全なかたちを作る作業は、頭の中では先読みしつつちょっと手前でとめる作業になります。次にどうすればレッドにできるかわかっている状態でコードを書くことがあるということですね。また逆に言えば、テストをレッドにするという行為は、目指す最終形に対して抽象化/一般化できていない部分を突き崩すものであるべきなのです。


3つ目のテストでいよいよ、核心部に切り込むことになります。

@Test
def マップに3つ値を設定するとそれが最初がnull() = {
    val lru = new Lru()
    lru.put("KEY1", "VALUE1")
    lru.put("KEY2", "VALUE2")
    lru.put("KEY3", "VALUE3")
    assertEquals(null, lru.get("KEY1"))
  }

保持しておくキーの数に特に指定はなかったのですが、とりあえず「2つ持つ」という仕様で作業を始めました。これはある意味現実を抽象化し切れていない部分ですので後で修正することにして、先にコアの実装をやっつけます。


完成形に至る不完全なかたちとしては、「とりあえず受けとった順にキーをリストに詰めていって、3つ以上あったら先頭から消していく」という実装にしました。

class Lru {
    
    val map = new HashMap[String,String] 
    var keystack = new ListBuffer()
    
    def put(key: String, value: String) = {
        keystack += key
        if (keystack.size > 2) {
            val removeKey = keystack.remove(0)
            map.remove(removeKey)
        }
        map += key -> value
    }

これでテストはグリーンになりますが、バグがあることはすでにわかっています。同じキーを連続していれたら他のものを追い出してしまうんですね。そこを突いてレッドにするテストを書きます。

@Test
def マップに2つの値を繰り返しいれて取得できる() = {
    val lru = new Lru()
    lru.put("KEY1", "VALUE1")
    lru.put("KEY2", "VALUE2")
    lru.put("KEY1", "VALUE1")
    lru.put("KEY1", "VALUE1")
    assertEquals("VALUE2", lru.peek("KEY2"))
    assertEquals("VALUE1", lru.peek("KEY1"))
}

前の実装だと、keystackの中身が["KEY1","KEY1"]になってしまうので、"KEY2"が追い出されてレッドになります。そこでコードを修正します。「keystackの中にもうすでにキーが入っていたら、keystackをいじるのをやめよう!」if文で囲むだけですね。

class Lru {
    
    val map = new HashMap[String,String] 
    var keystack = new ListBuffer()
    
    def put(key: String, value: String) = {
        if(!keystack.contains(key)) {
            keystack += key
            if (keystack.size > 2) {
                val removeKey = keystack.remove(0)
                map.remove(0)
            }
        }
        map += key -> value
    }

たしかこのあたりで、「Scalaなんだし、nullを返すのはやめようよ」というリファクタリングが入りました。さて実装自体は一見良さそうに見えたのですが、まだレッドにできます。

@Test
def 値を入れ直した場合に順序が更新される() = {
    lru.put("KEY1", "VALUE1")
    lru.put("KEY2", "VALUE2")
    lru.put("KEY1", "VALUE1") // ★
    lru.put("KEY3", "VALUE3")
      
    assertEquals(Some("VALUE1"), lru.peek("KEY1"))
    assertEquals(None, lru.get("KEY2"))
    assertEquals(Some("VALUE3"), lru.peek("KEY3"))
}

"KEY1"に対応する値がNoneであるということで、レッドになってしまいます。理由は「★」の場所でkeystackの中身が["KEY1","KEY2"]のまま変わらないからですね。["KEY2","KEY1"]にしないといけません。「keystackの中から消して、後ろにつければいいじゃないか」ここでScalaっぽさが炸裂します。

def put(key: String, value: String) = {
    keystack = stack.filter( _ != key ) // ★
    keystack += key // ★
    if (keystack.size > 2) {
        val removeKey = keystack.remove(0)
        map.remove(0)
    }
    map += key -> value
}

これでグリーンになります。よくよく見ると、keystackに対する2つの操作(★)は1行にできますね(実際にこのリファクタリングを行ったのはもうすこし後のことでしたが)。

keystack = stack.filter( _ != key ) + key

一瞬完成したかと思ったのですが、ここで1つ忘れていたことを思いだしました。「getの時も使ったって考えるんだよね?」

@Test
def 値を取り出した場合に順序が更新される() = {
    lru.put("KEY1", "VALUE1")
    lru.put("KEY2", "VALUE2")
    lru.get("KEY1") // ★
    lru.put("KEY3", "VALUE3")
      
    assertEquals(Some("VALUE1"), lru.get("KEY1"))
    assertEquals(None, lru.get("KEY2"))
    assertEquals(Some("VALUE3"), lru.get("KEY3"))
}

もちろん★の部分でkeystackの中身が更新されないので、"KEY1"が取得できず、テストはレッドになります。実装はそれほど難しくありません。putの先頭でkeystackをいじっているところをprivateメソッドに切り出し、getの先頭でも呼ぶようにしました。

def put(key: String, value: String) = {
    touch(key)
    map += key -> value
}
    
def get(key: String): Option[String] = {
    touch(key)
    map.get(key)
}

これで完成だろうとドヤ顔でテストを実行したところで、和田さんの準備した落し穴に顔面から突き刺さりました。「いままで通っていたテストが通らなくなった!なんだこれは?」こういうとき、原因に一瞬で気づく人がいるのが強力なチームのよいところ。「Assertでgetするときにも順番が変わっちゃうね」


状態を変更しないで値が取れるメソッドpeekを準備して、テストをすべて書き換えました。

def peek(key: String): Option[String] = map.get(key)

「これで要件を満たしただろう」と考えて、ちょっとしたリファクタリングを行いました。keystackを触る処理をメソッドではなくプライベートクラスとして抽出したのです。

class Lru {
    
    val map = new HashMap[String,String] 
    var keystack = new KeyStack()

    def put(key: String, value: String) = {
        keystack.touch(key)
        map += key -> value
    }

    [...]

    class KeyStack {
        
        var stack = ListBuffer[String]]()

その上で、先ほどのペンディング事項「キャッシュサイズは変えられるようにしないと」を実装しました。

@Test
def キャッシュサイズを3に変更して3つの値を出し入れできる() = {
    val lru = new Lru(3)
    [...]

Scalaのデフォルト引数機能を使うことで、これまでのテストは触らなくてすみます。

class Lru(var cacheSize:Int = 2) {

ここで前半が終了しました。


なお、どこかのタイミングでテストのリファクタリングを行い、毎回のnew Lru()をテストフィクスチャに追い出しています。

var lru: Lru = _
 
@Before
def setup = {
    lru = new Lru();
}

コードレビュー

コードレビューの最中、Ruby組の方から驚きのバグが報告されました。「入っていない値をgetしたときにスタックの順序が狂ってしまいますね」自分たちのコードのバグとしての気づきでしたが、我々のコードにも同様の問題がありました。和田さんの準備した落し穴に、気づかぬうちに落ちていたわけです。


ここでまたしても重要だと思うのが、テストは現実のさまざまなケースをパターン化し、具体的にしたものです。「本当にこれまでのテストですべてのパターンを網羅できているか?」「気づいていない操作パターンはないか?」そういうことを考えることが非常に重要だということですね。「レッドにする」ことは時として非常に高度な技術を要します。

後半:仕様変更

ここで和田さんから(うれしそうに)仕様変更のお知らせが入りました。

仕様変更

  1. キャッシュのサイズを後から変更できるようにしたい。
  2. 時間が経ったら消えるようにしたい。
  3. スレッドセーフにしてほしい。


後半のセッションの開始時、「存在しないものに対するget」のバグが悔しかった僕は、ついうっかりコードを先に書いてしまいました(和田さんが隣にいたらものすごく怒られたところです。危ない危ない)。

if(!map.contains(key)) None

書いた後で気がつきます。「ごめんね、和田さん」反省して、テストコードを書きました。

@Test 
def 値が入っていないものをGetしても状態は変わらない() {
    lru.put("KEY1", "VALUE1")
    lru.put("KEY2", "VALUE2")
    lru.get("KEY3")
      
    assertEquals(None, lru.peek("KEY3"))
    assertEquals(Some("VALUE1"), lru.peek("KEY1"))
    assertEquals(Some("VALUE2"), lru.peek("KEY2"))
}

しかし、レッド。getで相変わらず状態が変わってしまっています。しばらく眺めて気がつきました。メソッドの途中ではreturnが省略できないんですよね。

if(!map.contains(key)) return None

テストは先に書かなければいけないし、すくなくとも実装したものはテストしないといけないということですね*2


仕様変更1でちょっと難しいところは、キャッシュサイズを減らした場合にkeystackを削らなければいけない点にあります。この「削り」に関して、またしてもScalaのかっこいいAPIが炸裂します(Scala勉強会@東北のページを紹介して頂きました)。

    if (stack.size > cacheSize) {
        for(i <- stack.dropRight(cacheSize)){
            map.remove(i._1)           
        }
        stack = stack.takeRight(cacheSize)          
    }

あふれている分を取得(dropRight)してマップから削除し、stackから必要なところだけ取り出して(takeRight)再代入します。


これで、テストはグリーンになります。ここで、すこし大きめのリファクタリングを行いました。仕様変更2で結構大きな変更が想定されたので、それに耐えられるようにこのタイミングできれいにしておきたかったのです。


今書いたコードと、今まで使っていたこのコード:

if (keystack.size > cacheSize) {
    val removeKey = keystack.remove(0)
    map.remove(0)
}

「やっていることはすごく似ていないだろうか?」すこし考えて、「どちらの場合もcacheSizeに合わせて、keystackとmapを削っているだけだ」ということに気づきました。今まで使っていたコード(すぐ上)は、新しく書いたコード(2つ上)の特殊ケースにすぎないのです。それをまとめた結果、コードはかなりすっきりしました。

def touch(key: String) = {
    stack = stack.filter( _ != key ) + key
    shrinkIfNeeded()
}

def changeSize(size: Int) = {
    cacheSize = size
    shrinkIfNeeded()
}

private def shrinkIfNeeded() = {
    if (stack.size > cacheSize) {
        for(i <- stack.dropRight(cacheSize)){
            map.remove(i)           
        }
        stack = stack.takeRight(cacheSize)          
    }
}

実は、午前中に行った「KeyStackクラスの作成」というリファクタリングの恩恵に、ここであずかることができています。この変更はKeyStackクラスの中に閉じており、外側のLru本体には影響を与えていません。


さて、仕様変更2です。

関心事の分離

「古いものを消す」という仕様を入れるためには、時間の概念を導入しなければなりません。スレッドを新しく走らせる必要もあるでしょう。これをいままでのクラスに入れたくはなかったですし、テストコードも書きにくくなってしまいます。そこで、テストコードを書く前に図を書いて、設計の検討を行いました。

TimerとLruとの間にはRemovable(これも名前の検討は必要ですが)というインターフェイスを立てます。Timerのテスト(=時間の概念が入る)は、Removableをモック化して定期的にメッセージが送られていることを確認します。Lruのテストは直接Removableに定義されたメソッドを呼び出し、正しくふるまっていることを確認します。


Timerのテスト時にモックが必要になります。本当はjMockやMockitoのようなモックフレームワークがあるとよかったのですが、限られた時間の中で(しかもScala)で動かす自信がなかったので、プライベートクラスとしてごりっと実装してしまいました。

@Test
def 定期的に削除メッセージを送信する() {
    val mock = new MockRemovable()
    val timer = new LruTimer(mock)
        
    timer.start();
        
    Thread.sleep(15000)
        
    assertTrue("呼び出されている", mock.called)
}
    
private class MockRemovable extends Removable {
    var called = false;
    def remove(time:Date) {
        called = true;
    }
}

ここに重要な設計が1つあって、RemovableのメソッドがDateオブジェクトを受けとるものであることを定めています。本体はこうなりました。

class LruTimer(removable: Removable) extends Thread{
  override def run() = {
      while(true) {
          Thread.sleep(10000)
          removable.remove(new Date())
      }
  }
}

タイマーができたところで、Lru本体に取りかかります。もちろん、テストコードから。

@Test
def ある時点の前のキャッシュを削除する() {
    lru = new Lru(3) // 今見るといらないですね
    lru.put("KEY1", "VALUE1")
    lru.remove( new Date(System.currentTimeMillis() + 10000) )
    assertEquals(None, lru.peek("KEY1"))
}

「removeに渡されたDateよりも前に使われたものは消そう」と決めました。まずは、keystackに時間の概念を導入します(このとき、Lruクラスには影響がなく、変更はすべてプライベートクラスであるKeystackクラスに閉じています)。

var stack = ListBuffer[Tuple2[String,Date]]()

def touch(key: String) = {
    stack = stack.filter( _._1 != key ) + ( key -> new Date )
    shrinkIfNeeded()
}

ご覧のとおり、stackにタッチするときに、タイムスタンプを設定しています(new Dateはアレですが、それはリファクタリングできれいになるでしょう)。そして、removeが呼ばれたときにそのタイムスタンプを見て、古いものを削除することになります。

def removeBefore(time:Date) = {
    for( i <- stack.filter( ! _._2.after(time) ) ){
        map.remove(i._1)
    }
    stack = stack.filter( _._2.after(time) )
}

このあたりでタイムアップとなりました。


こうしたインターフェイスとモックを使ったTDDのやり方については、「Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))」で詳しく説明されています。講演の中で和田さんがモダンTDDのバイブルとして位置づけていた本ですね。

補足

TimerとLruを分割して個別のテストから書き始めましたが、2つを組み合わせて使おうと思うと、もう一工夫必要です。今のままで素直にファクトリメソッドを書こうと思うと:

// 試しに書いてみると・・・
def createLru():Lru = {
    val lru = new Lru()
    val timer = new LruTimer(lru)
    timer.start() // で、このタイマーのインスタンスは誰が管理するの?
    lru
}

という感じになってしまいます。「外側から内側へ」という発想を常に忘れてはいけないという教訓を示す好例です。しかも、Removableインターフェイスの仕様を事前に明確に定めていなかったため、現段階ではちょっとしたバグがあります。一定時間ごとにその瞬間より前のキャッシュが消えてしまうんですね。実際には、「○○分/秒前の値を消す」という考慮が必要でしょう。


もちろん、どちらのケースに対してもTDDのサイクルを使って切り込んでいくことができます。まずはレッドになるコード(もしかしたら、コンパイルもできないコード)を書くところから。

まとめ

TDDBC仙台で窓際にいたScala組の歩みをそれなりに正確に再現できたのではないかと思います。もちろん、すべてがこんなにスムーズに行ったわけではなく、僕が「あれ、この場合for文ってどう書くんでしたっけ?ちょっとお願いします!」とかいってキーボードを渡したシーンもありましたし、「あれ?なんで通らないんだ?」とデバッグコードを埋め込んでみたシーンもありました。逆に、想定外の落ち方をしたときにその原因にすぐ気づいてもらえたり、タイムスタンプの実装で「keystackの中身をタプルにすればいいよね」と一瞬でコードを書いてもらえたりと、強力な方々とペアプロをすることの生み出す豊かさを随所で感じることができました。非常に贅沢な時間をすごさせていただいたことを、チームを組ませて頂いた山中さん、武田さんを始め、講師の和田さん、そして開催に尽力してくださった方々に深く感謝します。まだ体験したことがないという方は、ぜひ参加されるといいと思いますよ!


最後にもう一度、TDDの黄金の回転について整理したいと思います。

レッド
「失敗するテストを書く」これは新しいパターンのテストケースを書く作業であると同時に、インターフェイスを設計する作業でもあります。いかに適切な粒度で最終形との距離を縮めていくか。多分ここに、TDDのエッセンスが凝縮されているんじゃないかと思います。
グリーン
グリーンにすること自体は、おそらく最も純粋な「プログラミング」の作業です。これが適切に行うためには、言語やアルゴリズムに対する知識が必要になりますし、それはそれで身につけるべきものです。1つつけ加えると、コードを書いていると「どのみちこれではこういう場合に落ちるな」ということに気づくことがあります。それが次のレッドの種になるわけですから、本当は手元に紙を置いておいて、書き留めてみるといいのかもしれません。
リファクタリング
リファクタリング」というと、コードを修正しないといけないような気になりますが、多分そんなことはありません。グリーンになったところでコードを見直し、「これで大丈夫かな?」と次のレッドの種を探したり、あらためて構造を頭の中で整理してみたり、言葉に出して確認してみたり、という作業が非常に有効だということに気づきました。コードを修正するかどうかは、結果にすぎないということですね。


ひどく頭を使ったせいか、終わった後は予想以上に消耗していました。XPには「40Hours / a Week」ルールがありますが(残業は週40時間まで、じゃないですよ)。このレベルで日々の仕事をやっていたら、残業なんてとてもじゃないけど身体がもちません。



Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))

Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))

プログラミングScala

プログラミングScala

*1:時間内ではテストが1つ通らなかったのですが、家に帰って見てみたら、変数の再代入が原因のなんてことないバグでした。公開しているコードでは修正しています。

*2:経験ありませんか?ちょろっとバグを直して、テストをしないでいたら、あとですげー怒られることになったという。