GxPで学んだ、ただ一つのこと

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

導入

9月末を以て、グロースエクスパートナーズ株式会社を退職しました。4年と2ヶ月の間この会社にお世話になっていたことになりますが、その中できわめて多くの貴重な経験をさせていただいたと思っています。退職を決めてから、4年前に自分が書いたエントリ(SIを仕事にするということ)は何度か読み返しました。その中でGxPを表現した次の言葉は、今読んでも正しかったと思います。

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

この4年間のふりかえりとして、この文章にある「泥臭い作業」を言葉にしてみようとしてみたのですが、相変わらず「語るまでもなく当たり前のことであったり、語れるほどには言語化されていなかったり、語るほど面白くなかったり」したので、そのかわり、GxPで学んだマインドセットのようなものについて言葉にしていくことにします。おそらくそれは、自分が求めていた「パラダイムを学ぶことと実際にデリバリーすることとのバランス」にとって、欠かせない要素だったのだと思います。

納得して向き合う

その要素とは何か...と溜めるまでもなく見出しに書いてしまいましたが、「納得感」の一言に尽きます。「そうすることになっているから」「誰がそう言ったから」そういう理由で仕事を進めることは許されませんでしたし、納得できなければ誰に対してでも何かを言える文化だったと思います。では、納得とは何か。分解するなら、一つは「理解」もう一つは「説明」でしょう。どちらも同じコインの表と裏です。プロジェクトに対してなんらかコミットしようと思えば、この納得感を一定のレベルで確保しなければなりませんし、ましてや、プロジェクトのハンドルを握ろうと思えば、それを隅々にまで行き渡らせる必要があります。少し具体的に書いてみます。


「何を」「どのように」「いつまでに」「いくらで」「誰が」作るのか、それら複合した要素をプロジェクトとして組み立てるときにはツールが必要なのですが、この点に関する「これさえやれば大丈夫」という包括的なツールは、僕が知る限りありません。というより、「これさえやれば大丈夫」という感覚がそれ自体すでに危険です。


たとえば....「何を」に関して言うならば、ユースケースなりシナリオなりで対象システムが備えるべき「機能」を何らかのかたちで表現することは誰でもやっていることでしょう。しかし、作るものの性質に応じて、適した記述方法は変わります。画面とER図があればおおよそ大丈夫なこともあれば、なんらかのルールを表形式にまとめなければいけないこともあるでしょう。「設計書とは、あるいは要件定義書とはこのフォーマットで記載するものである」という固定化した発想からは確かに安心感は得られますが、確実に何かを取りこぼします。


さらに、その「何か」を具体的に「どのように」実現すべきかは、ハードウェア構成/ソフトウェアのモジュール構成等いくつかのビュー、いわゆるアーキテクチャで表現されることになりますが、分解された構成要素は「いつまでに」「いくらで」「誰が」というスケジュール/コスト/チームの根拠になります。アーキテクチャの方針によって複雑さは凝集することも点在することもあり、そういった実態を無視して引かれたスケジュールには現実性がありません。


他にもどういうドキュメントを書くのか、そのドキュメント同士はどうつながっているのか、テストとはどう関連するのか、テストの考え方は十分か、など考えなければいけないことを挙げていけばキリがありません。ただ、大切なのは、そういった個別の構成要素およびその関連性について、まずは自分が理解する必要がある、ということです。その理解は、計画時であればプロジェクト計画という作業結果ではなく、その作業を通じて、自分あるいはチームの中で醸成されるものです。そういった「理解」があって初めて、顧客に会社にあるいはチームに「説明」できるようになります。一会社員がプロジェクトに対して果たせる責任といえば、せいぜいがそういった説明責任だけなのです。逆に言えば、誰かのせいにせずに「こういった理由により自分はこれが正しいと考えている」と説明できる立場に立つことこそが、プロジェクトのハンドルを握るということなのだと思います。


余談ですが「自分がこう考えた」ということを基点に置くことは、プロジェクトに限らず、様々な面において後悔しないための一つの方法であるように思います。もちろん、ある種の後悔はするのですが、少なくとも自分のこととして引き受けて、誰かを恨まずに先に進むことはできますからね。そういうことをひっくるめての「納得感」ですね。


***


社員がそういう姿勢で仕事に臨むことについては、どんな企業であっても少なくとも経営陣は望んでいるような気がします。それが様々な事情や経緯で現場レベルで実践できなくなっているケースも多々あるのだと思います。そういうことを求めつつ大きな裁量と共に仕事を任せていただいたこと、また、チームのメンバーが皆、主体的に考え動いてくれたこと、そして、色々なものを放りだして去って行く僕を温かく送り出してくださったこと、そういったことすべてについて、深く感謝します。ありがとうございました。

その後

10月からは、中間流通を軸としたエンタテイメント系総合商社の情報システム本部に勤務しています。立場が変わり、見えるものもだいぶ変わってきました。ユーザー側に移ったことにより、これまでできなかったことでできるようになったこともあると思います。ただ、根底にある「ものづくり」という本質はそう変わるものでもなく、これまでの経験を活かして引き続きがんばります。また、言葉を紡ぐ作業はこれからも続けていきたいと思いますので、どうかよろしくお願い致します。

ソフトウェアアーキテクチャの先に

最後に、少し宣伝めいたことを。10/1付で新しい翻訳書が出版されました。ちょうど転職のタイミングとなってしまいましたが、僕がGxP名義で出版する最後の翻訳書ということになります。編集の上野様、パートナーの岡澤さんにはいつものように多大なるご迷惑をおかけしました。また、10年前に書かれた本を訳させてほしいという私の勝手なお願いを引き受けてくださった翔泳社様に深くお礼申し上げます。


内容については、ゆうすけさん、今野さんのインタビューがこちらに掲載されていますので、興味のある方はどうかご覧ください。「結局のところ、アーキテクトは考えるという行為から逃れられない」という、ある種このエントリで書いてきた内容とつながるところがあるのですが、そういう、考えながら前に進もうとする人に対して、多くの気づきを与えてくれる名著だと思います。

ビヨンド ソフトウェア アーキテクチャ (Object Oriented Selection Classics)

ビヨンド ソフトウェア アーキテクチャ (Object Oriented Selection Classics)

パラダイムを学ぶことと、現実の中で活かすこと

本に書かれた内容を実際に使うことの難しさについて

導入

ここ一年ぐらい将棋にハマっているのですが、最近は棋力が上がらなくて悩んでいます。序盤で失敗して一方的に敗けるのが嫌なので、序盤から中盤の入り口について解説してある戦型の本をよく読んでいるのですが、いまいち「強くなった」という気がしません。原因は明らかで、「知らない形に出会った時に安定して力が発揮できない」のです。初めて見る局面でも自信を持って指せるようになりたい。多分私が触れたいのは、定跡とか手筋を超えたところにある「棋理」みたいなものなんでしょう。そんな話をあるプロ棋士の先生にしたときに言われたのが「お勉強しても強くなれませんよ」という言葉で、それが妙に耳に残っています。


似たようなことはシステム開発にも言えるのではないか、ということを最近よく思います。そのことについて、「ドメイン駆動設計(DDD)」や「スクラム」を例に言葉にしていきます。


DDDにせよ、スクラムにせよ、パラダイムとして優秀であることに疑う余地はありません。ただ一方で、「DDD/スクラム開発をしようとしているが、いまいちうまく行っていない」という話も時折耳にします。こういう「うまく行かなさ」は何に起因するのでしょうか。

書き留められたパラダイムと現実のコンテキスト

特定のパラダイムを教える立場にある人なら、そのパラダイムを正確に理解することは欠かせません。ただ、プロジェクトやビジネスを推進している側からすれば、「優れた手法を取り入れることがプロジェクト(ひいてはビジネス)の成功につながるとは限らない」といえます。理由の一つとして、プロジェクトやビジネスが、置かれている環境や構成要因、対象とするドメインの性質、文化といったさまざまなコンテキストに束縛されていることを挙げられます。あるパラダイムが一つの体系として書き留められる場合、そうしたコンテキストは語られないため、そもそも適用段階で具体的にどのようにしたらよいかわからないことになります。適用の方法についての議論は、こうした「どのように」が多いような印象を受けています。ただ、こうしたことについてはそれほど深刻とは思えません。適用事例が増え、ノウハウが蓄積されるにしたがって自然と解消していくものでしょう。


より深刻なのが、「何をするべきか」という問題です。「DDD/スクラム実践する」と、パラダイムが目的化してしまった場合、「何をすればDDD/スクラムをしていることになるのか」というメタな議論が生じます。こうした関心の横滑りはきわめて危険で、DDD/スクラムを正しく実践することに意識が向くと、プロジェクトの本来の目的から逸れてしまいます。DDDで言えば、モデルの正しさを追求するあまり、複雑化しすぎて誰にも理解できなくなったとしたらどうでしょう。あるいは、スクラムの実践に取り組み、「タイムボックスによる定期リリース」ができていたとして、バックログに積んであるものがバグフィックスだけだったとしたら、それはうれしいことなのでしょうか。それがうれしいとしたら、誰にとってなのでしょうか。

実践から得られるもの

わかりやすくするために極端な例を挙げましたが、現実に特定のパラダイムを理解し、そのパラダイムの価値を自分なりに解釈し、実際の文脈の中で必要なことを行って成果に結びつけるのは、決して簡単なことではありません。私自身は大筋、DDDであれば、適切なドメインの分析とドメインの特性に応じたモデリングアーキテクチャ設計が、スクラムであれば、ゲームのルールに従うよりも組織全体のバリューストリーム/フィードバックループの構築が骨子だと考えていますが、具体的なコンテキストに置かれれば、また違った解釈が必要になることもあるでしょう。後から振り返れば教科書通りで最適だと言える判断も、その場に置かれたときに行えるというのは、まさに「お勉強」ではたどり着けない場所で、実際どうすればそういうことができるようになるのか、正直私もよくわかっていません。差し当たりは、視野を広げ、「全体」という言葉で自分に見えるものを広げつつ、一歩ずつ考えながら進んで、「何かがおかしい」という違和感や、あるべき姿に対する感覚を磨いていくしかないのではないかと思っています。

GxPでは人材を募集しています

今の会社に入って一番よかったと思うのは、会社の規模的に正しく手を伸ばせばどこまでも届く反面、プライムという立場でお客様と直接やりとりができることです。すべてがうまくいくわけではなく、苦労がないわけではありませんが、「全体」を見渡しつつシステムを通じて世の中と関わるという実感は、間違いなく得られる場所だと思っています。入社してからの取り組みについては、以前ご報告させていただきましたので、よろしければ、こちらもご覧ください(動画はこちらで公開されています)。なお、弊社はいつでも人材を募集しています


弊社に興味を持ってくださった方で、いきなりサイト登録するのがためらわれるという場合には、お気軽にお問い合わせください。メール(digitalsoul0124 at gmail.com)やfacebookなどでのご連絡もお待ちしております。

「アジャイル」と「ウォーターフォール」

アジャイルがダメだと思う7つの理由へのだいぶ遅い反応

導入

もうだいぶ前の話になってしまいましたが、アジャイルに関するブログエントリ「アジャイルがダメだと思う7つの理由」は予想以上に波紋を呼び、それに呼応していくつものエントリが公開されました。発端となったエントリに関していうと、通常であれば必要となる数々の留保事項や前提事項を書かずに切り込んでいるという点において、間違いなく「煽り」であると言えます。ただ、「アジャイル」という言葉をとりまく数々の事象をかなり的確に指摘することによって、「アジャイル」という言葉をパブリックな場所で語っている少なからぬ人たちに対して、(意識的か無意識的かは問わず)ある種のポジショニングを強いたという意味で、実にいい釣り針なのではないでしょうか。


個別の論点に関する「アジャイルでは全体スケジュールにコミットできない」「いや、アジャイルだって全体スケジュールにコミットできる」という議論は一通り終わった感がありますので、だいぶ出遅れた立場としては、少し違う視点から整理したいと思います。

ところで、「アジャイル」と「ウォーターフォール」は何が違うんでしょうね

反射的に出されるであろう主張とそれに対する反論をいくつか挙げてみます。「ウォーターフォールは出来上がるまでの時間が長い」―本当ですか?ウォーターフォールは一年かけなければならないなどという定義はどこにもありません。「ウォーターフォールではフィードバックを受けられない」―どうでしょう?重要な機能なら必要に応じてプロトタイピングをするなどということは常識です。「ウォーターフォールでは変化に適応できない」―そうでしょうか?顧客がソフトウェアを触るようになって上がってきた諸々の仕様変更を、テストの進捗を考慮しつつ、別の線を引きながら取り込んでリリースに間に合わせる、みたいなことは普通だと思います。また、アーキテクチャ策定段階で、比較的安定している機能と後々変化が予想される機能とで設計/実装方針を区別し、変化に対応しやすい(その代償としてコストのかかる)設計にしておくといったことも当然やるべきことです。


また、ウォーターフォールの基本的な性格とされる「要件定義」→「設計」→「実装」→「テスト」という工程は、フィードバックサイクルの長短に関わらず必要なことであり、これを以てアジャイルウォーターフォールの違いを語ることは難しいでしょう。「基本設計書がすべて出そろってレビューが完了するまでは実装に着手してはならない」といった教条も時折は耳にしますが、それがウォーターフォールのあるべき姿かどうかという疑問と共に、現実としてそこまで厳格な運用が行われているケースはごく稀ではないかと思います。実際には、サブシステム(あるいは大機能など、呼び方は色々あると思いますが)といった、一定の粒度でシステムを分割し、各工程を並行させると共に、実装も着手できるところから着手しているでしょう(そのリスクを発注側と受注側のどちらが持つかは別として)。


つまり、現実の開発においては、「変化の方向性を先読みしつつ、フィードバックを適切に受け取ること」が重要なのは間違いなく、そのための施策は色々とあるわけです。ただ、そういった諸々の施策を「アジャイル」と呼び、そうではないものを「ウォーターフォール」と呼んで思考停止するのは、色々と不幸なことを引き起こすと思うのです。一方で、アジャイルの主張する「変化への適応」に気を取られすぎて、「事前に考えること」を否定することも逆方向の思考停止です。「それは後で考えれば良い」という台詞は、後で発生するであろう揺らぎを吸収できるスケジュールとアーキテクチャに支えられてはじめて説得力を持ちます。事前に計画を立てること、全体像を描くこと、全体の中で個別に戦略を考えること...総じて先のことを予め考え、読んでおくことは常に必要です。そういった「考えること」の放棄に「アジャイル」という名前を冠することに対しては、同様に強い危機感を覚えます。


結局のところ、「考えてわかることは、考える。考えてもわからないことは、どうすればわかるかを考えたうえで、そこまで手を進めてみる」という基本がすべてで、それ以上でもそれ以下でもないということです。


とはいうものの、世の中に目を向けてみると、ここまで「アジャイル」という言葉が盛り上がる背景には、"これまでの"開発がうまくいっていないという感覚あるいはある種の閉塞感が存在するのだろうとは思います。つまり、批判されるべき「何か」は現実に存在し、それへの批判として別の「何か」が存在するのは事実で、それぞれがウォーターフォールアジャイルという名前をまとっているだけではないか、と感じるのです。それが一体何であるのか、普段受託開発を行っている自分の経験をふりかえりながら言葉にしていきたいと思います。

チームと文化

批判されるべき「何か」は、プロセスや成果物ではなかろう、ということが出発点です。プロセスにしても成果物にしても、コンテキストに応じて適切なものを選んでいけばよいものだからです。むしろ「コンテキストに応じて適切なものを選ぶ」ことができない環境にこそ問題視すべきだと考えます。

単能工とコマンドコントロール

基本設計書を書くだけの"SE"。設計書の内容を実装するだけの"PG"。よく話題にあがりますね。「それではダメだ」という批判と、「実際にそういう人たちが集まってシステム開発をしているんだ」という現実路線の両方が存在すると思います。僕自身の経験に照らせば、一定以上の規模であれば、実装がある程度わかる人が設計書を書き、設計の矛盾に気づける人が実装し、双方のコミュニケーションが円滑に進んでいる状態が現実的で健全な開発であるという気がします。それでも工程ごとに担当者が分かれれば、そのコミュニケーションを繋ぐための中間成果物が必要になるのであり、そういう中間成果物が「お客さんと要件を詰め、過不足のないドキュメントを描き、柔軟かつ統一のとれたアーキテクチャでそれを実装して、鉄壁のテストをやってくれるエンジニア」から見て無駄なオーバーヘッドに見えてしまうのは仕方が無いことだと思います。そういうエンジニア2,3人で開発できる規模なら、また違ったやり方もできるでしょう。


工程ごとに担当者が分かれていても、工程間がスキル的にもコミュニケーションパス的にもゆるやかにつながってフィードバックが回っていれば、それほど問題にはならないと僕は考えています。問題なのは「あらゆる指示が上から降ってきて、ただ決められた期日までにその指示をこなす」ことが求められる環境です。これを生み出すのは、同一工程内の階層化、具体的に言えば、少数の「タスクを分解し指示する人」と多数の「指示に従ってタスクを遂行する人」との分断です。工程ごとに作業者が分かれていて、かつその作業が上から降ってくるという構造であれば、メンタリティとして成果物の提出先が「仕事を割り当てる人」になってしまい、それを受け取る次工程の人が忘れ去られてしまうのもわからなくはないですが、当然いい結果は生まないでしょう。


個人的には、この「分解されたタスクの指示書」がWBSの姿をしていることこそが、この構造が「ウォーターフォール」と呼ばれてしまう原因ではないかと思っています。ただ、もはや開発モデルとしてのウォーターフォールは関係ないですよね。ここにあるのは、「多重階層によるコマンドコントロール」と「成果物を壁の向こうに投げつけるサイロ構造」です。フィードバックが届かない多重階層構造によって人が病み、サイロ構造によって成果物の品質も下がるのはよくわかります。

多能工と自己組織化

「お客さんと要件を詰め、過不足のないドキュメントを描き、柔軟かつ統一のとれたアーキテクチャでそれを実装して、鉄壁のテストをやってくれるエンジニア」に話を戻します。そんなスーパーマンがいたとして、その人も始めからそうだったわけではないでしょう。要件定義、設計、アーキテクチャ策定、実装、テストといった各工程を個別に経験する中で知識と技術を蓄積した結果にすぎません。つまり、多能工になるためには、工程を横断して様々な経験をする必要があるわけですが、前述の文化ではその経験を積むことができません。


「多重階層によるコマンドコントロール」では多能工が育たず、多能工が育たないから「多重階層によるコマンドコントロール」が必要になるのです。この負の連鎖を断ち切るために、単能工とコマンドコントロール、多重階層の関係をもう少し整理しておきます。作業をする人が見ることのできる範囲が狭い場合、その狭い範囲に応じたチャンクにまで仕事を細分する必要があります。そして、その負荷は、指示をする人に集中することになります。一人で見ることのできるノードの数には限りがありますので、作業者が食べられるサイズになるまでに階層ができあがるのはよくわかります。


つまり、「指示を出す人/従う人」という構造に無理があるのです。では、違う道はどこにあるのでしょうか。「正解」ということではないのですが、受託開発のリーダーという立場で自分のやっていることは、「指示出し」ではありませんので、少しふりかえってみたいと思います。


...さて、僕は何をやってるんでしょうね。感覚を言葉にすると、対チームとしては「大きな方向づけと情報の流れのコントロール」、対顧客としては「窓口」ですかね。考えてみると、作業の細分化はほとんどメンバーに任せてます。得意分野を中心に緩やかに周辺をカバーできるメンバーに囲まれているので、渡せる仕事のチャンクが大きく、作業分割と統合にコストがかからないんですね。コストがかからない、というよりそこに自分が入ると、自分がボトルネックになって効率が落ちてしまう、という表現が正しいです。また、大きな方向づけと書きましたが、進め方も自分だけで決めているわけではなく、あまり時間を気にせずに、よく議論している気がします。とはいえ、和智「どうしたらいいと思います?」○○「こうですかね?」和智「じゃあそれで」○○「」というパターンが頻出している気もして、エントリを書いていて、改めてメンバーに恵まれていると思いました。要は自分がボトルネックにならなければ順調に開発が進むわけで、ボトルネックにならないコツは、情報の流れをプッシュ型からプル型に変換することだと思っています。「大きく渡して、必要に応じて聞いてもらう」というやり方ですね。

終わりに

ソフトウェア開発を生業としていれば、さまざまな業務に関わることになります。やることが大きく、作るべきものが最初から分かっている業務であれば、コストを抑える代償に変化を想定しないアーキテクチャで進めることができるでしょうし、そもそも何をするかもわからないうちから実験的にモノづくりを始める状況であれば、変化に柔軟に対応できるアーキテクチャをインクリメンタルに膨らませていくべきでしょう。


重要なのは常に、「何のために何を作るのか」という目的であって「どう作るか」は手段でしかあり得ません。それに対してエンジニアとしてできるのは、専門性を高めることと守備範囲を広げることを通じて、どういう要求にも対応できるようになっていくことかありません。顧客が満足するものを提供しつつ、プロジェクトを通じてメンバーが成長できるのであれば、開発手法は別に何でもいいと思うのです。

テスト駆動開発の進化

デブサミ関西2012での講演内容まとめ

はじめに

今月、GOOS日本語版が発売されました。

実践テスト駆動開発 (Object Oriented SELECTION)

実践テスト駆動開発 (Object Oriented SELECTION)

継続的デリバリーに続き、高木さんと一緒にお仕事をするのはこれで二冊目です。今回も多くの人に助けられて、目標としていたデブサミ関西での出版にこぎつけることができました。関係者の皆さま、どうもありがとうございました。


講演では触れませんでしたが、ここで「実践テスト駆動開発」というタイトルの由来について少し書いておきます。原書のタイトルはご存じの通り、"Growing Object-Oriented Software, Guided by Tests"で、前半の頭文字をとってGOOSと呼ばれています。"grow"も"guide"も本書にとって外すことのできないキーワードでしたので*1、タイトルから外すことは考えられませんでした。ただ、これをうまく収めるタイトルがどうしても浮かばなかったため、直訳を副題としました。主題に今の名前を付けたのは、ケント・ベックの著作(Test Driven Development: By Example (Addison-Wesley Signature Series (Beck)))以来、TDDにとっては二冊目のバイブルという位置づけを大切にしようと考えたのです。


講演のタイトルを「テスト駆動開発の進化」としたのは、2002年のケント・ベックの著作と比べてGOOS本はどういう所が変わっているのか、という点にフォーカスしているためです。


さて、今回の講演のアジェンダです。

  • テスト駆動開発の進化とは?
  • GOOSのポイント
    • 進化という観点だけではとらえきれないGOOSのポイントについてご紹介します。
  • 開発の現場で活かすには?
    • GOOSで紹介されるテスト駆動開発を実際のソフトウェア開発のライフサイクルに組み込むにはどうすればいいかを考えます。


それでは内容に入っていきましょう*2。当日使ったスライドはこちらです。

テスト駆動開発の進化

そもそもテスト駆動開発とは、プロダクションコードに先立ってテストコードを書く(テストファースト)ことで、コードを動かしながら完成に近づけていくという手法です。まず失敗するテストを書き(レッド)、そのテストを通るようにし(グリーン)、先に進む前にリファクタリングする、というリズムは、黄金の回転と呼ばれたりもします(詳しくは(こちら)。


具体的にFizzBuzz*3を使って例を示します。TDDでは、プロダクションコードを書く前に、まずはテストコードを書きます*4

   @Test
    public void when1then1() throws Exception {
        FizzBuzz ex = new FizzBuzz();
        String result = ex.result(1);
        assertEquals("1", result);
    }

シンプルなテストですが、これだけでも多くの判断をしています。

  • クラス名はFizzBuzzにしよう
  • メソッド名はresultにしよう(これでいいの?)
  • 戻り値は文字列にしよう("fizz"と"buzz"があるので)

この状態では実コードがありませんのでコンパイルも通りません。コンパイルを通しながら、最もシンプルな仮実装をするとこうなります。

public class FizzBuzz {
    public String result(int i) {
        return "1";
    }   
}

かならずしもreturn "1";である必要ななく、return Integer.toString(i);でも構いません。どこまで先に進んでいいかということについてTDDでは特に何も定めていないのです。重要なのは、「何をテストすべきか」という自分の理解を深めながら先に進むことです。個人的には、まずはできる限りシンプルなテストと実装(この場合であれば「1を渡したら"1"が戻る」)でインターフェイスを定め、その後で仕様に関するテストを計画的に書いていくのが良いのではないかと思っています。


さて、テストがグリーンになったところでリファクタリングです。resultというメソッド名がなんとなく気になりますが、今は先に進みましょうか。またテストを書いてレッドにします。インターフェイスが定まったので、次は仕様のテストですね。今回は、数字のテストを置いておいて先に"fizz"をやりました。

    @Test
    public void 引数が3だったらFizz() throws Exception {
        FizzBuzz fizzBuzz = new FizzBuzz();
        assertEquals("fizz",  fizzBuzz.result(3));
    }

これを通すためにコードはこうなります。

    public String result(int i) {
        if(i % 3 == 0) {
            return "fizz";
        }
        return "1";
    }

これでふたつめもグリーンです。中身もとりあえず良さそうですね。こうして、5の倍数だったら、公倍数だったら、数字だったら、とテストを書いていくと、動かしながら完成品が出来上がります。テストがあるのでリファクタリングを安心してできるところも、TDDのいいところですね。


ここまで読んで頂いておわかりの通り、TDDは何かひとつのクラスを実装するには非常に優れた手法です。しかし、普段我々が携わっているようなアプリケーション(私の場合はWebアプリケーションになります)では、どこから手を付けて良いのかわからなくなってしまいます。こうした疑問に対して、スティーブとナットはやはりテスト駆動開発の黄金律を使って立ち向かいます。その黄金律とはすなわち、まずは失敗するテストを書くというものです。ただし、そこで彼らが最初に書くテストは、「受け入れテスト」です。受け入れテストでテストするのは、システムが実装すべきフィーチャ、顧客に対して提供すべき価値ということになります。そしてこの受け入れテストはエンドツーエンドに実施しなければいけません。エンドツーエンドとは、システムの「端から端まで」、たとえば、ユーザーインターフェイスからデータベースまでということです。


この受け入れテストから書き始めるTDDは次に示すような二重のループを描くことになります。


外側のテストは実装すべき機能がどこまでできているかを示す進捗の指標となり、内側のテストによってコードの質が高く保たれます。この外側のループと内側のループを繰り返し辿ることで、ソフトウェアが作られていきます。本講演のタイトルにしたテスト駆動開発の進化とはすなわち、コードを書くことから、ソフトウェアを作ることへということなのです。

GOOSのポイント

さて、前述した二重のループに加え、GOOSにはいくつか特筆すべきポイントがあります。ここではそれを二つとりあげます。それが、モックウォーキングスケルトです。

モックについて

ケント・ベックJUnitの作者であるように、スティーブとナットはjMockの作者です。jMockは二人のオブジェクト指向観をベースに作られています。彼らはオブジェクト指向システムを、「協力しあうオブジェクトの網の目」ととらえます。クラスの継承関係よりも、オブジェクトがどのようにコミュニケーションし合うかが大切だと考える彼らにとっては、クラスよりもインターフェイスが重要です。


さて、そのような発想に従い、網の目としてオブジェクトが構成されていると、テストをするのが難しくなってしまいます。あるオブジェクトをテストしようと思うと、隣り合う他のオブジェクトも動いてしまうことになるからです。


そこで登場するのがモックです。モックを使うと、隣り合う他のオブジェクトをニセモノと差し替え、テストの際に思い通りの動きをさせることができます。この話は具体的な方がわかりやすいので、具体例として「モックによるインターフェイスの発見 - Digital Romanticism」をご紹介しました。興味のある方はご一読くださいませ。なお、前のブログの方には書いていませんので、テストコードの表現がシーケンス図と同じになるという点をここで強調しておきたいと思います*5


キーに対して値をロードして返すシーケンス図がこちら:


実装がこちらです

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


例に示した仕様ではオブジェクトローダーが事前に存在することが想定されていましたが、実際にテストを設計しながらインターフェイスが発見されるということも十分に起こります。受け入れテストからはじめて、外側から内側へ、インターフェイスを発見しながら開発を進めていく点がモックを使ったTDDの特徴になります。モックを使ったテストコードを書くことは設計の活動ですので、テストファーストとはいえコードを書くことにこだわりすぎず、色々な図を使いながら考えて進めていくのが良いのではないかと思います。


そのようにインターフェイス(あるいはオブジェクト)を発見するためのパターンがGOOSには三つ出てきます。

分解(Breaking out)
あるオブジェクトが責務を持ちすぎている場合、ふるまいの凝集した単位を分割するというものです。
発芽(Budding off)
新しい概念が登場したときにプレースホルダー型をラップする(値)もしくは、サービスを定義する(オブジェクト)というものです。分解は既にあるものを分けるのに対して、発芽は新しい概念が導入される点が特徴です。
包含(Bundling up)
関連するオブジェクトの集団をひとつのオブジェクトにまとめるというものです*6。上記二つは分けるパターンだったのに対して、これはまとめるパターンです。

これらのうち、分解と包含は基本的な考え方だと思いますが、発芽はきれいなコードが書けるようになるためのいいテクニックだと思います。


なお、もう一つ重要な点として、ビジネスロジックの中核は、ビジネスドメインの言語を使うべきだ、というものがあります。これについて詳しく知りたい方は、ぜひ以下の本をご覧ください。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

ウォーキングスケルトンについて

GOOS流のTDDでは、フィーチャの開発とエンドツーエンドテスト基盤の開発を並行して進めなければなりません。しかしこれには、テストが失敗したときの原因がどちらにあるかわからないという点を始めとした難しさがあります。ウォーキングスケルトン(Walking Skeleton)とは、これを解決するための方法です*7。まずは、できる限り薄いフィーチャのスライスを取り出し、それをビルド/デプロイ/テストするための基盤を作ります。こうした基盤が一度できれば、あとは少しずつテストを増やし、プロダクションコードを育てていくことができるようになります。詳しくは以下の本をご覧ください。

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

中間のまとめ

これまでの内容をいったん整理します。まず第一に、GOOS流TDDの核心は受け入れテストとユニットテストにより構成される二重のループにあります。それを円滑にまわすために、まずは薄いスライス(ウォーキングスケルトン)を使ってテスト基盤を作り、フィーチャを増やしていくという形をとることになります。第二に、テスト駆動開発という名前がついてはいますが、ここで言われているテストはシステムのふるまいです。つまり、テストを考えるということは、システムを外側から見たときのふるまいを考えるということに他なりません。こうした考え方については、「BDDの導入 - Dan North - Digital Romanticism」をご参照ください。

開発の現場に活かすために

さて、ここまでGOOSに書かれたTDDの特徴を見てきました。自社システムの開発を数人で行っているチームであれば、ここまでのノウハウで十分にうまくいくと思います(もちろん、スクラムのようなある程度の開発プロセスに関する方法論は必要でしょうが)。しかし、プログラマ30名〜40名で行う受託開発を考えると、なかなかこの形に持ち込むのが難しそうです。最後に、こういった中規模〜大規模の開発現場でGOOSを活かすにはどうすればいいかを考えていきたいと思います。


二重のループをどう開始すればよいか、という問いは、二重のループの外側を見ることでしか解決できません。二重のループに入る前の段階として、GOOSには以下のステップが説明されています。

  1. 問題を理解する
  2. アーキテクチャを定める
  3. ビルド/デプロイ/テストを自動化する


ここで重要なのは1.と2.、特に2.のアーキテクチャです。GOOSが想定しているアーキテクチャは、タイトルにある通り、「オブジェクト指向ソフトウェア」です。しかし、ここで想定しているような中規模〜大規模の開発では、すべてを「オブジェクト指向」で作ることはあまり現実的ではありません。まず重要なのは、業務分析を行った上でドメイン(ここでは「機能の大分類」と読み替えて頂いても構いません)を抽出し、それぞれのドメインごとに特性を分析することです。


ここで分析すべき特性とはすなわち、業務の複雑さをどこで受け止めるべきか、ということです。ここでは二つ分類を出してみます。

  1. エンティティ主体のドメイン
  2. ロール主体のドメイン

エンティティ主体のドメインは、普段多くの方が目にしているものだと思います。つまり、業務の複雑さがデータモデルに吸収され、E-R図とSQLビジネスロジックの中核になるようなドメインです。このようなドメインであれば、ムリにオブジェクト指向的に作る必要はなく、SQLを気持ちよく書くためのSQLテンプレートエンジンを準備した上で、入力チェック、DBアクセス、編集処理といった手続きをトランザクションスクリプトとして実装することが正解となります。このようなドメインに対してO-Rマッパーを当てはめた結果、コード内に大量のJPQLが埋め込まれ結果として可読性が落ちてしまうケースを見たことがある方も多いのではないでしょうか。また、こうしたドメインの場合、テストとして設計するべきは、シナリオではなく、データのパターンになります。ユーザの操作だけで見ると「詳細画面を表示する」という一シナリオで済んでしまうところ、「このフラグがこうなっている場合はこの項目は表示しない」、あるいは「ここがこういう状態の場合にはリレーションのこっちから値を持ってくる」といったパターンが出てくることになります。


一方で、処理が複雑で、ロール間のインタラクションとしてモデル化した方が適切だと思われるドメインも中には存在します。例としては、決済処理や権限といったよくある共通処理、あるいは外貨金利の日またぎ計算といった特殊な業務ロジックが挙げられます。このようなドメインに対してトランザクションスクリプトで立ち向かってしまうと、処理が重複したり、メソッドが長くなりすぎたり、という問題が発生します。またテストの設計も、「こういう条件でこういう入力があったらこのインターフェイスを呼び出す」という形のシナリオになるでしょう。


ここでは二つの分類を出しましたがこれが二つである必然性はありません。各ドメインを記述するのに適切なパラダイムは他にもあり得ると思います。重要なのは、作るものの特性を見極め、それに合わせたアーキテクチャを策定しようということです。GOOSではソースコードは「育てる」ものだとされていますが、そのソースコードが育つ方向性を事前に規定するのがアーキテクチャなのです。

*1:詳しくは本書の序文を読んでみてください

*2:内容については、一部後から追記しています

*3:wikipediaFizzBuzzを参照

*4:このコードは行きの新幹線の中で @yusuke_arclamp とペアプロをしました。ご協力ありがとうございました。

*5:これは行きの新幹線の中で、上司の@yusuke_arclampから指摘されて気付きました。静的な構造を表現するクラス図より、動的な動きを表現するシーケンス図の方が、モックとの相性が良いのです

*6:日本語版p.64で包*括*となっていますが、ミスによる表記揺れです。申し訳ありません。

*7:直訳すれば歩くスケルトンで、骨格だけで筋肉のないガイコツが動いている、というイメージです。継続的デリバリーおよびGOOSでは、意味を取って動くスケルトン、としています

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

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:「そんな会社あるんですか?」と何人もの人から聞かれました