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

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


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

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

はじめに

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

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

私はこの本に査読者として関わらせていただきました*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:経験ありませんか?ちょろっとバグを直して、テストをしないでいたら、あとですげー怒られることになったという。

翻訳に必要な3つの技術

翻訳に必要と思われる技術について整理する。

導入

私のブログの書き方を紹介したエントリの中でもすこし触れたとおり、翻訳という作業は自分の中で大きな位置を占めるようになってきています。『エリック・エヴァンスのドメイン駆動設計』が出版されて以来、新しい翻訳をやったり、他の方の翻訳をレビューに参加させて頂いたりと、翻訳がらみの仕事が増えてきましたので、この機会に自分が考えていることを整理したいと思います。


なお、この場を借りて大先輩の翻訳論をご紹介しておきます。


私の考える、翻訳に必要な3つの技術とは「英文解釈」「翻訳のテクニック」「日本語作成技術」です。結局のところ翻訳とは、「英文を正確に理解し」「日本語に置き換え」「日本語として自然に読めるかたちにする」作業であり、それぞれに一定の知識なりテクニックが必要になるということですね。


では、それぞれ順を追って説明していきます。

英文解釈

翻訳をする上で必須なのが、英文の構造を正確にとらえることです。単語は調べればわかりますが、構造は調べることができません。英文の構造をどこまで忠実に読み取れるかが、翻訳の精度を決める最初の鍵になります。「単語だけ調べて、あとは文脈からなんとなく」という読み方は、読むだけであれば大丈夫かもしれませんが、翻訳をしようと思うと苦しくなります。読むだけならパラグラフ単位、章単位で意味が理解できていればいいケースも多いですが、翻訳となると、すべての文章を日本語にしないといけないですからね。


そのために必要なのがやはり「英文法」の知識です。「とりあえず基礎からやり直したい!」という強い思いがある方におすすめなのがこの一冊(完全に受験参考書ですが)。

NEW・山口英文法講義の実況中継 (上) 改訂新版

NEW・山口英文法講義の実況中継 (上) 改訂新版

※受験時代、私はこの本のおかげで「単語をつなげて雰囲気で理解する」ことから卒業できた気がします。

前置詞の解釈

英文の意味を正確にとらえよう(訳そう、ではない)とした場合に、かなり重要になるのが前置詞に関する理解です。受験英語ですと、inなら「中に」、onなら「上に」という具合に訳し方を覚えさせられた上で、これに収まり切らないものはすべて「慣用表現」として処理させられたりしますが、これは結構大変です。これに対して、前置詞を概念として理解しておくと意外と応用が利きます。


いくつか例を挙げましょう。特定の領域を表す"in / on / at"であれば、inは「包含」、onは「接触」、atは「焦点」です。onについては、「上」でなくてよいところがポイント。壁にかかっていれば"on the wall"ですし、時間ぴったりであれば"on time"、逆に範囲に収まっている語感を持っているのが"in time"です。一方、atは特定のポイントを指し示している感じがあって、だからたとえば"at 7 o'clock"のように特定の時間を表すのにも使われます。


あるいは方向を表す"to / for"であれば、toは「到達」、forは「途中」になります。「どこかに出かける」なら"leave for"ですし、着いているか着くことが確実であれば"go to"になります。探し物をするなら見つかっていないので"look for"、何か特定のものを見ているなら焦点の"look at"ですね。

訳文に逃げない

英文解釈という観点からすると、私は「構造を理解すること」にゴールを置くようにしています。日本語にして終わりではなく、どれが主語でどれが動詞で、に始まり、形容詞のかかっている先はどれか、指示語の対象は何か、それがわかって初めて解釈としては完了だということですね。翻訳作業を行っていて意味がよくわからない文章に出会ったとき、確実にやっているのが「このitって何だ?」「このtheって何だ?」という地道な問いかけです。最初はわからなくても、可能性をひとつずつ丁寧に追っていけば、たいていの場合正解と思えるものにたどり着けています。


それでもわからないときにはじめて「慣用表現の可能性」を考えます。よく使うのがアルクGoogle検索ですね。実際に慣用表現だった場合には、かなりの高確率でアルクで見つかります。Googleの場合は似たような文章をいくつか眺めて使われ方を理解したりします。この場合も「訳す」というよりは意味を理解することを重視します。

翻訳のテクニック

英文として理解できるようになったら、次に考えるべきは日本語にどう置き換えるかです。修飾関係や文の中の役割といった文法構造だけを日本語に置き換えて済むなら簡単なのですが、それでは「横のものを立てただけ」になってしまいます。


たとえば:

Her anger made him sad.

直訳すれば「彼女の怒りが彼を悲しませた」ですが、日本語として自然なのは「彼女が怒ったので、彼は悲しくなった」ですよね。これは英語が名詞中心で発想しているのに対して、日本語が動詞中心で発想していることに由来します。同様に形容詞も副詞に変換して訳した方が自然な日本語になるケースが多いです。


ほかにも英語という言語の持つ特性上、そのままでは日本語になりにくいものを処理するためのテクニックがあります。このパターンで、普段自分が自覚的に使っているものをご紹介します。

1接続詞/指示語の補完英文はそのまま訳すと、前後のつながりが見えにくくなることがある。必要に応じて接続詞、指示語を補う。
2強調/対比の明確化元の文において強調されている概念、対比されている概念は日本語化に際しても明確に訳出する。
3名詞の動詞化英語が名詞を中心に発想するのに対して、日本語は動詞を中心に発想する。その意味で「名詞の動詞化」は「名詞を動詞化する」とすべきだが、パターン名に関してはこの限りではない。これに伴い「形容詞の副詞化」も行うことが多い。
4語順の維持原文で情報が提示されている順序を崩さないようにする。代表的なものとしては、関係代名詞を後ろからかけるようにする。このとき、必要に応じて1つの文が2文に分かれることがある。ただし、意味上あるいは日本語の流れ上前からかけるべきケースも少なくない。やりすぎないこと。
5無生物主語の転換無生物主語は日本語としては不自然。態を転換して、意味上の主語を主語として扱う。
6「の」の展開文の中で「の」が使われている場合には、意味を明らかにして展開した方がよいケースが多い。特に連続する場合には必須。
7「が」と「は」の意識的な使い分け単なる主語ならば「が」。「は」は強調を表す。一文に「は」が連続すると焦点がぼやけるので注意。
8指示語への代入*1「これ・それ・あれ」が増えるとうるさいので、適宜内容は補う。「the」も既出の内容を受けている場合には訳出すべきケースが多い。

情報提示の順序

翻訳の際、「意味を訳すこと」と同じくらい意識しているのが、「情報提示の順序」です。これは単純な修飾/被修飾構造とは別の次元で文章を支配しています。この順序が意図されたものと違ってしまうと、おかしなことになってしまいます。一例を挙げましょう。

  • 王様がいつも赤いずきんを被っていたのは、王様の耳がロバの耳だったからだ。
  • 耳がロバの耳だったので、王様はいつも赤いずきんを被っていたのだ。

何か違うストーリーが混ざっているような気もしますが、言わんとしていることは伝わるでしょう。たぶん「赤ずきんの王様」みたいなタイトルのおとぎ話です。この文章を読む読者にとって、王様が赤ずきんを被っていることは「既知」の情報、その理由が「未知」の情報になります。この場合、「既知」→「未知」の順番に情報が流れてくれないと、なんとものっぺりとした文章になってしまうのです。


私自身のスタイルとして基本的には情報提示の順序を崩さないようにしているため、通常はそれほど強く意識していません。ただ、語順をひっくり返さざるを得ないときには、既知/未知の関係や文の中での強調点が崩れないように、ちょっと特別な注意を払うようにしています。逆に言えば、この点に注意していれば語順を変えた方がよいケースもすくなくありません。


日本語作成技術

「英文を理解する」、「日本語に移し替える」、その次に来るのは「日本語としての完成度を高める」です。そのテクニックを学ぶ上での必読書がkdmsnrさんのエントリにも紹介されていたこちら:

新装版 日本語の作文技術

新装版 日本語の作文技術

特に以下の3つの章は大いに参考になります(私がこの本を読んだのはDDDを翻訳した後のことでしたが)。

  • 修飾の順序
  • 句読点のうちかた
  • 助詞の使い方(特に「は」と「が」)

翻訳に限らず、日本語である程度の長さの文章を書こうと思うなら、この本は必ず参考になります。

日本語での表現力

「辞書で一番上に出てきた単語を使う」という段階を超えようと思うと、どうしても日本語表現の幅が必要になってきます。たとえば「英語では一語のものが日本語だと何語かになる」「英語だと数語のものが日本語なら一語で表現できる」ということが起こりますし、「同じ意味を表す自然な言い回し」が見つけられればそれに越したことはないでしょう。このあたりを流暢にコントロールできるようになると、翻訳も職人技の域に入っていくのでしょうが、私はまだそこまでは到達できていません。ただ、悩んだときにはシソーラスが結構役に立ったりします。


どうすればこの部分を高めることができるかは模索中なのですが、最近、ほかの方が翻訳された文の査読を行うことで、スキルをかなり盗めることを知りました(ありがとうございました)。

最後に

大切なことを最後に1つ。翻訳は独自のロジックを持つ技術です。「英語の本が読めること」と「翻訳ができること」は等価ではありません。「英語の本が読めるから翻訳ができる」わけではありませんし、「翻訳ができないから英語の本が読めない」わけでもありません。「英語の本が読めなければ翻訳はできない」正しいのはこれくらいでしょう。冒頭で触れたとおり、文章レベルで正確に読めていなくても、パラグラフのレベルや章のレベルで論理が追えていれば問題ないケースも多くあります。また、理解が目的であれば、きれいな日本語に置き換える必要もありません。


もちろん、「だから翻訳には手を出すな」と言っているわけではありません。むしろ逆で、英文を読める方が「じゃあ翻訳もしようかな」と思ったときに、とりあえず理解しておくとよさそうなことをここでまとめたつもりです。確かに、本当に自然な日本語にしようと思えば、幅広い語彙と豊かな表現力が要求されるのであり、こうしたことはすぐに身につくものではありません。ただ、日本語としての練度をどこまで上げる必要があるかは、翻訳対象にもよります。技術系の文書であれば、文芸作品ほどの練度は必要ありません。ここにご紹介した本を読み、いくつかのテクニックを理解すれば、間違いなく訳文の質を上げることができます(また、翻訳作業にかかる時間も減るでしょう)。


日本語で読める技術情報を充実させていきたいと願う方々にとって、このエントリが役に立てば幸いです。



おまけ

構文が取れるようになったというところからもう一歩先に進み、英文の「意味」を解釈するとはどういうことかを考える上では、この本が圧倒的におすすめです。


議論されるものに、たとえば「次の2つの文がどういう意味の違いを持っているか」といったことがあります(p.96):

種明かしをすると、後者はキスの対象はあくまで「手」であって単なる儀礼だが、前者はキスの対象が「女王」であってなんらかの含みを思わせるそうです。受験英語で「ジムの頭を殴った」を「"hit Jim's head"ではなく、"hit Jim on his head"としろ」と言われ、慣用表現として覚えた方もいらっしゃるかもしれませんが、これと同じ理屈ですね。殴っている対象はジムであり、頭は場所でしかないということです。この本の中ではほかにも、エントリ中で触れた情報提示の順序についても言及されています。


1点補足しますと、この本、文庫本ではありますが、内容はかなりマジメな認知言語学です。翻訳のためのパターンを探すという目的にはちょっとそぐわないかもしれません。ただ、英文を深く読めるようになりたいと真剣に考えている方には間違いなくおすすめできます。




MAC-Transer 2010 プロフェッショナル

MAC-Transer 2010 プロフェッショナル

最近愛用している翻訳ソフトです。自動翻訳はもちろん使いものになりませんので、テキストエディタとしてしか使っていないという話もありますが、エディタとしての基本性能の高さと辞書機能の使いやすさから愛用しています。ただし、購入を検討されている方には1点注意事項です。

いきなり買わない。まずはテキストエディタを、次にOmega-Tを試すこと。

理由は、「イラストレーターを買う前に、紙にペンで絵を描くべき」なのと一緒です。なお、ご紹介したのはMac用で、Windows用にはPC-Transerという商品があるようです(Mac-Transerより高いですね・・・種類がいくつかありますので、調べてみてください)。

*1:このパターンは渡邉さんがDDD査読時に出して下さったものです。感謝します。

ドメイン駆動式ソフトウェアの育て方

レッツゴーデベロッパー2011での発表原稿とスライド

導入

2011年05月28日「レッツゴーデベロッパー2011@仙台」が開催されました。このイベントのテーマは「共有と交流」。"「共有」には、最新技術、知識、復興への想い、それぞれの決意を共有することを、「交流」には、東北と東北圏外のデベロッパーやコミュニティ同士の交流を深めることを込めて。" このイベントにてDDDセッションに登壇させて頂きましたので、そのときの発表原稿とスライドを公開致します。なお、当日はワークとして参加者の方にペアモデリングを行って頂きましたが、このドラフトではその部分を割愛しています。


さて今年4/9にDDD日本語版が出版されました。それから2ヶ月弱、翔泳社様から、はやくも増刷のお知らせを頂きました。多くの方々とおかげと深く感謝しています。さて、この増刷が意味しているのは今や千人単位の方の手元にDDD日本語版があるということです。出版と時期を同じくして著者のエリックも来日しました。読書会も同時多発的に開催され始めています。日本におけるDDDは、間違いなく次のステージへと進んでいます。いわば、「読むことが目的だった時代の終焉」。読み、理解したら、その次に来るのは実践でしょう。DDDの考え方に従ってアプリケーションを作ったらどうなるのか、ということを考えることが今回のテーマです。

ドメイン駆動設計とは?

ドメイン駆動設計の考え方

先日、とある方とお話していたときに、非常に鋭い言葉を聞かせて頂きました。「ユーザさんには、自分が「ユーザ」であるという意識はない。彼らは自分の仕事をやっているだけなんだ」と。これは非常に的確でありながら、開発者としてはなかなか気づくことのできない視点だと思いました。DDDの根本的な関心もやはり同じようなところにあります。「システムを作る上ではまず顧客がどのようなビジネスを行っているかが重要である」、言葉にしてしまうと当たり前すぎるこのメッセージがドメイン駆動設計の出発点になります。逆に言えばこれまで我々は技術などにとらわれて、顧客の仕事を理解するという本当に重要なことを忘れていた、ということでもあるでしょう。


ドメイン駆動設計ではさらに、顧客のビジネスを顧客自身の言葉で理解することが求められます。単純にとらえれば、「同じ言葉で会話をする」、つまり「顧客が使っている言葉を自分も使う」ということです。ただ同時に、顧客が描いているモデルを共有するということでもあります。「こういう場合は特殊ケースなのでフラグを立てて・・・」ということを、勝手にやってはいけないということですね。勝手なフラグは言葉として相手が理解できないだけでなく、それを成立させる別のモデルを導入してしまっていることも示唆しています。会話やドキュメントなど、あらゆる場所で顧客の言葉を使うこと、それがユビキタス言語というパターンです。


「モデル」という言葉が出てきました*1。ここで言うモデルとは、「顧客の目から見た業務の姿」だと考えてください。モデルは現実そのものではありません。人間が現実をとらえたときの姿です。その際には、適切な抽象化が行われ、不要な詳細は捨て去られます。「何が重要で、何が重要ではないか」、その取捨選択の中にモデルの本質があります。開発者には、顧客の持つそういうモデルを共有することが求められます。先ほどのフラグの例で言えば、一見「特殊ケース」に見えるものが、業務の全貌を理解している人からすれば正常パターンの1つでしかない、ということはあり得ることです。


さて、せっかくモデルを共有しても、実装時に失われてしまっては元も子もありません。共有したモデルをソフトウェアの中に持ち込むこと、これがモデル駆動設計と呼ばれる重要パターンです。モデルと実装を結びつける上ではなんらかのパラダイムが必要になりますが、それにあたってDDDが準拠するのがオブジェクト指向です。概念をクラスとして表現することで、顧客の描くモデルをソフトウェアの中に反映するのです。

アーキテクチャ

そうした場合ソフトウェアはどのようなアーキテクチャになるのでしょうか。それについては第2部で語られることになります。中でも概要を理解する上でポイントとなるのがレイヤ化アーキテクチャという考え方です。レイヤ化アーキテクチャでは次の4つのレイヤが説明されています。

この中で最も重要なのがドメイン層です。これはまさに、モデルが存在するための空間となります。


ソフトウェアの中にドメイン層を確保する上で重要な役割を果たすのが、リポジトリです。通常のトランザクションスクリプトの場合、SQLを発行した結果得られるリザルトセットを直接使うケースがほとんどだと思いますが、DDDの提唱するアーキテクチャでは常にモデルオブジェクトを操作します。そのためリポジトリがクライアントからの要求に応じて、必要なオブジェクトをクライアントに渡すことになります。こうすることでクライアントは、オブジェクトがメモリ上にあるかのように操作することができます。

プロセス

「顧客はドメインをとらえるモデルを持っている」と言っても、そのモデルをアップフロントにすべて理解し実装することはできません。第一にドメインが複雑であれば、開発者側がすべてを一度に理解することはできませんし、顧客自身も実装可能なレベルで完璧なモデルを事前に持っているわけではないからです。したがって、顧客とドメインモデルを共有するプロセスは当然イテレーティブなものになります。


これについては、現在Ericが整備を進めているプロセスがあります。それが「モデルを探究するうずまき」です。このうずまきはまず「シナリオ」から始まります。顧客からシナリオを聞き出しつつ、それをモデル化していきます。モデルができたらすぐに新しいシナリオを聞いて、モデルに揺さぶりをかけます。この内側のループにかける時間は1日2日程度だそうです。ある程度モデルができあがったら、「コードプローブ」に入ります。シナリオをテストとして実装し、モデルが実際に使えるかどうかを確認します。結果はシナリオにフィードバックし、また新しいループが始まります。

イベント参加受け付けをモデリングする

2011年4月9日のDDD前夜祭では、@t_wadaさんによる"DDD Boot Camp"が開催されました*2。そのときのお題は「DDD前夜祭の申し込みをモデリングする」でした。今回は「レッツゴーデベロッパー2011の申し込みをモデリングする」と置き換えて考えてみたいと思います。


ポイントは先ほどの「モデルを探究するうずまき」の内側のループ、シナリオとモデルの往復を試すことにあります。その際、主要な概念とその関連性が見えてくれば成功だと言えるのですが、実際にやってみると意外と行き詰まります。シナリオを考える際に、「申し込みの締め切りをどうするか」という重要かつやや複雑な処理にいきなり飛び込んでしまうと「上限がどう決まるか?」「当日キャンセルは見込むか?」といった疑問がわいてしまって手が止まってしまうのです。できれば、わかりやすくシンプルなところから始めて、すこしずつ複雑なものに取り組んでいくという段階を踏みたいところです。シナリオの例を示しましょう。

  • イベント情報を表示する
  • 申し込みを受け付ける
  • 参加者一覧を表示する
  • 申し込みを締め切る

ソフトウェアを「育てる」

このようにすこしずつソフトウェアとモデルを膨らませていく上で大きなヒントを与えてくれるのが、「Growing Object-Oriented Software, Guided by Tests (Addison-Wesley Signature Series (Beck))」です。この本ではソフトウェアを「育てる」ために、受け入れテストとユニットテスト(場合によってはインテグレーションテスト)というレベルの違うテストで2重のループを構成することが提唱されています*3


具体的には、まず「Walking Skeleton」と呼ばれる、動くための最低限の機能を作ります。その上で、エンドツーエンドの受け入れテストを書きながら、1機能ずつ作っていくのです。これを実際に試してみるにあたり、今回は技術スタックとして以下に示すものを使います*4


ケルトンでは、シナリオ「イベント画面の初期表示」を実装します。

def "イベント画面初期表示"() {
    when:
        go "/PlanTheEvent/event/show"
    then:
        $("h1").text() == "イベント情報"
        $("td#detail").text() == "レッツゴーデベロッパー"
}

これだけでも、やることは意外とあります。画面とモデルオブジェクト、データベースのテーブルを作る必要がありますし*5アーキテクチャも定めなければなりません。また、フレームワークに慣れていなければ、使い方も一通り調べる必要があります。


こうしたことを行ってはじめてテストが通るのですが、その後ある程度リファクタリングも行わなければなりません。たとえば、前述したテストコードからはHTMLに依存した記述を取り除く必要があります。

def "イベント画面初期表示"() {
    when:
        go "/PlanTheEvent/event/show"
    then:
        $("#pageTitle").text() == "イベント情報"
        $("#detail").text() == "レッツゴーデベロッパー"
}

この段階でモデルオブジェクトが1つできあがっています。


次は、申し込み受け付け画面への画面遷移を経て、登録処理のテストを書きます。

def "参加者一件登録"() {
    when:
        go "/PlanTheEvent/participant/apply"
        $("form").twitterId = "@digitalsoul0124"
        $("form").message = "よろしくお願いします"
        $("#register").click()
    then:
        $("#pageTitle").text() == "イベント情報"
        $("#detail").text() == "レッツゴーデベロッパー"
        $("#participantsCount").text() == "1"
}

このテストがグリーンになるころにはモデルに参加者オブジェクトが追加され、申し込み受け付けの基本モデルが完成します。


先ほど話題にあげた要件「申し込みの締め切り」はこのモデルをベースに考えることになります*6。申し込みの上限をどう決めるかは、その業務を実際に行っている人に聞くしかありません。仮に「イベントを行う部屋の広さに加えて、ある程度のキャンセルを見込む」という答えが返ってきたとしましょう。ここに「部屋」という概念が新しく追加されます。


10%のキャンセルを見込むと考えて、次のようなコードを書けば、とりあえずテストは通るようになります。

// 満席判定
boolean fullToCapacity() {
    participantsCount() >= (roomsCapacity() * 1.1)
}

しかし、ここには概念が1つ暗黙的に潜んでいます。


「キャンセルを見込んで、多めに予約を取る」という考え方は通常「オーバーブッキング」と呼ばれます。このオーバーブッキングのポリシーをモデルで表現します。

具体的にどのようなメソッドを持たせるべきかは、コードを書いて確認します。

def "満席/11人で満席"() {
    when:
        def overbookingPolicy = new OverbookingPolicy()
        def room = new Room(capacity:10)
        def LIMIT = overbookingPolicy.limitFor(room)
        def event = new Event(room:room)
        for(i in 1..LIMIT){
            event.addParticipant(new Participant())
        }
    then:
        overbookingPolicy.fullToCapacity(event)
}

ここまでで、前述したシナリオを実現するモデルがいったん完成します。

※ここまでのコードはgithubにて公開しています*7

まとめ

今回は「ドメイン駆動設計を実践する」という観点から、シンプルなところから始めて、すこしずつ育てていくという手法を実際に試してみました。シナリオと紐づけながらモデルを作っていくことについて、ある程度のイメージを持って頂けたのではないでしょうか。


このような開発はユーザにとっても開発者にとっても理想だと思うのですが、今の日本ではなかなかできないのが実情でしょう。しかし、「できない」と嘆くだけではなく、「実際に自分はできるのだろうか?」と自問し、チャンスがあったときに確実に実践できるようにしておくことが求められる時代になってきているのではないかと思うのです。




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

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

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

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

*1:この点については、ドメイン駆動設計入門 - Digital Romanticismで詳しく説明しています

*2:http://www.slideshare.net/t_wada/devlove-dddbc

*3:この本の中ではモックを使ったユニットテストがきわめて重要なものと位置づけられていますが、今回の発表ではその点には触れていません。

*4:SpockとGebの選定にあたっては、@bikisukeさんにご支援頂きました。ありがとうございました。

*5:今回、永続化は行わずオンメモリで実装しています。

*6:参加者一覧の表示はこのモデルを使って実装できますので、ここでは割愛します。

*7:デモ時にあった警告は@nobeansさんにより修正して頂いています。また、@kiy0takaさんが受け入れテストをPageクラスを使って書き換えてくださっています。お2人ともありがとうございます!!

関数型Scala(7):ラムダとその他のショートカット - Mario Gleichmann

この記事はMario Gleichmann氏による、「Functional Scala」シリーズの第7回「Functional Scala: Lambdas and other shortcuts | brain driven development」を、氏の許可を得て翻訳したものです。(原文公開日:2010年12月5日)




前回は、関数型プログラミングの世界における最も強力な概念のうちの1つ、すなわち高階関数を見てきました。本質的に、ある関数が高階と呼ばれるのは、他の関数を引数として受け取る場合か、結果として関数を出力する場合でした。この考え方により、抽象化の新しいやり方がもたらされるだけでなく、ある種の状態やいわゆるコンビネータ(関数ビルダと呼んでも構いません)を捕捉したり受け渡したりするといった、かなり便利なこともできるようになるのです。なお、コンビネータとは入力された関数もしくはその他の入力を元に新しい関数を構築するもののことです。


特に、新しい抽象化の形式に関して言えば、ユースケースに特化した変更されるロジックを関数から抜き出し、後には純粋な機能だけを残す方法について見てきました。特殊なロジックは独自の関数で定義され、引数として渡されます。このようにして、フィルタリング用の単一の関数と、それぞれでリストがどのようにフィルタリングされるかを決定する多くの述語関数が作られました。この種の抽象化が抽象的であるように思えるなら、もう一度フィルタ関数を掲載しましょう。

val filter = ( predicate :Int => Boolean, xs :List[Int] ) => { 
    for( x <- xs; if predicate( x ) ) yield x 
} 
…
val even = ( x :Int ) => x % 2 == 0 // ★1
val odd = ( x :Int ) => x % 2 == 1  // ★2val candidates = List( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 )
val evenValues = filter( even, candidates ) 
val oddValues = filter( odd, candidates ) 

これはもう知っています!1行目から始まる高階関数と、5行目(★1)6行目(★2)の述語関数が2つですね。これらの関数を背景として、リストと任意の述語関数をフィルタ関数に適用し、フィルタリングされたリストを受け取ることができます。そして、フィルタリングによって素数のリストを作りたいと思えば、適切な述語関数をもう1つ定義するだけです。さて、ここで疑問なのですが、関数フィルタに渡すためには、常に予め述語関数を定義しなければならないのでしょうか?何のための質問でしょうね?もちろん、フィルタに渡すためには関数が必要ですし、関数が天から降って来るわけではありません!もちろん、もちろんです。しかし、覚えていらっしゃるでしょうか。関数を扱った第2話で純粋な関数リテラルについて見ています:

( x :Int, y :Int ) => x + y

あー、思い出しました?ここに書いたのは関数の中核です。つまり、引数のリストがあって、その後に関数矢印( => )と関数の本体が続きます。これは完全な関数で、ただ名前が与えられていないだけです。このようなやり方で関数を定義したら、後でその関数を参照する方法はなく、したがって後で呼び出すことはできません。名前のない、無名関数だからです。そしてここに、いわゆるラムダ式が登場します!これはまさに、純粋で無名の関数定義です。


これは私たちの疑問に対して、どう応えてくれるのでしょう?そうですね、無名の関数定義であっても、値を表します。これは他のあらゆる型のあらゆる値を定義し、それを名前と関連づけない場合と変わりません(後で参照するために、その値に別名をつけることをやらない、ということです):

val almostPi = 3.14159265                     // Double 型の値で、後に名前 almostPi を使って参照できる
2.71828182                                    // Double 型の別の値。今度は「無名」
val mult =  ( x :Int, y :Int  )  =>  x * y    // ( Int, Int ) => Int 型の値で、名前 multを使って参照できる
(  x :Int, y :Int )  =>  x + y                //  ( Int, Int ) => Int 型の別の値。今度は「無名」

Double 型の値を参照している別名を用いて関数を呼び出すことができるのと同様に( double( almostPi ) のように)、Double 型のリテラルを使ってその関数を呼び出すことも当然できます( double( 2.71828182 ) のように )。ふぁーあ。いつもの通りですね。その通りです。関数型プログラミングにおいて、ラムダ式を直接高階関数に渡すのは完全に普通のことなのです!そしてこれが、私たちのいくぶんわざとらしい質問に対する答えとなります。つまり、高階関数の呼び出しに使う前に、述語関数を名前と関連づけて導入する必要は必ずしもないということです。その代わり、関数を呼び出す際に都度定義することができるのです:

val evenValues = filter( ( x :Int ) => x % 2 == 0, candidates ) 
...
val positiveValues = filter( ( x :Int ) => x > 0, candidates ) 

OK、素晴らしい。しかし、一度しか使わない関数を前もって定義しなくてよくなるということ以外に、どういうメリットがあるのでしょうか?そうですね。儀式的なコードの量をさらに減らすことができるということがわかります。Scala型推論カニズム万歳!関数フィルタの型を細かく見ると、( Int => Boolean, List[Int] ) => List[Int] という型であることがわかります。そして、その型がわかるのはあなただけではありません。Scalaコンパイラも、関数の型を見抜くことができるのです!そして、コンパイラが述語関数の型を知っているので、引数の方を関数リテラルを使って明示的に註釈をつけなくてもよいのです(したがって、引数の型は、高階関数の呼び出しで関数定義をする際に、その都度決定されます)。

val evenValues = filter( x => x % 2 == 0, candidates ) 
... 
val positiveValues = filter( x => x > 0, candidates ) 

さらに、お望みであればもっと簡潔に書くこともできます。すべての引数を関数本体内で一度しか参照しないのであれば、関数リストの宣言を省略することができるのです!うーん、えっと、それでは、関数本体の中でどうやって引数を参照するのでしょうか?ここで、不思議なアンダースコア( _ )がはじめて機能するようになるのです。この記号は(後の回でも見るように)何でも屋です。今回は、与えられた引数リストを順番に参照するためのショートカットになります。関数本体で登場するアンダースコアは、与えられた引数の値と置き換えることができます。つまり、最初のアンダースコアは最初の引数の値を参照し、次のアンダースコアは2番目の引数の値を参照する、といった具合です。

val evenValues = filter( _ % 2 == 0, candidates ) 
...
val positiveValues = filter( _ > 0, candidates ) 

人によっては、この形式は簡潔すぎると感じるようです。趣味の問題だと思いますけどね。しかし、可読性の高いコードという観点からすると、この書き方はあまりに多くの情報を隠しているかもしれません(少なくとも、私のように静的型付け言語から来た人間からすると)。そこで、簡単なアドバイスですが、アンダースコアの記法を適用するのはきわめて「経済的な」場合だけとし、裏にある型がコンテキストから容易に把握できる状況に限定した方がよいでしょう。


実際にはアンダースコア記法は、正統的な関数定義において、コンパイラが与えられた引数の型を推論できる限りは使うことができます。コンパイルエラーが起きる例を示しましょう。

val mult = _ * _ // compile error : missing parameter type ...

次に示す、若干変更したバージョンはスムーズにコンパイルされます。関数本体においてアンダースコアで示されている各引数の型を明示的に記述できているからです:

val mult = ( _ :Int ) * ( _ :Int ) 

この場合、コンパイラには、関数の値を導き出すのに必要なものがすべて与えられています:アンダースコアは2回登場しますが、どちらもInt 型として註釈がつけられています。したがって、この関数の引数リストは2つの引数からできていて、どちらも Int 型となります。関数をこのかたちで定義するのがわかりやすいかどうかの判断は、あなたにお任せします。しかしながら、(少なくとも私には)もう少し読みやすいと思える妥協点があります。関数リテラルを別名と関連づける際に、式全体の型を明示的に宣言してもよいのです:

val mult : (Int, Int) => Int = _ * _ 

いいでしょう。ここでは、2つのアンダースコア両方のコンテキストは、関数の型註釈内で見事に記述されています。引数リストを見さえすれば、2つのアンダースコアの並びが混乱を招くことはありません。関数の本体が、例で示したように短い場合には特にそうです。

まとめ

今回は、関数リテラルの、ちょっとかっこいい新しい用語を学びました。それが、ラムダ式です。ここまで、その都度定義されて使い終わったら捨てられることになる関数を使うのが適切な状況をとりあげ、ラムダ式が役に立つところを見てきました。これにより、特殊な形式の関数につけられた奇妙な名前がレパートリーに加わったことになります。それに加え、関数の引数に対するプレースホルダとしてアンダースコアを用いることにより、儀式的なコードを減らす方法にも触れました。おわかりの通り、使い方は好みの問題かもしれません。こうした過程で型情報が失われてしまうかもしれないからです。しかしながら、コンパイラが型情報を見失うことは決してない、ということは本質的です。Scalaは静的型付け言語ですから(型情報を省略できる時もあるので、Scalaが動的型付け言語であるように見えるかもしれませんが)。そのような場合には、失われた型情報を私たちが提供しなければなりません。やり方は、関数本体でアンダースコアを書くたびに型情報を添えても、関数式全体に型を明示的に書いても構いません。いずれにしても、こうしたショートカットを活用する場合には、特に可読性という観点から見た結果について、明確に意識しなければなりません。