Agile2010 Day Four

終わった! 午後参加した二つの発表はレガシーコードとどう付き合うか、という毎年ある話。 だけど、年々より具体的かつある程度体系化された手法が確立されつつある。 今日のは納得いく発表だった。

って、報告はもう疲れたので帰りの飛行機か、帰宅してからにします。

この日は午後の二つのセッションに参加。いずれもレガシーコードに関わる話題だ。

一件目のThe Worst of Legacy Code: Forensic Developmentは「本当に」レガシーなコードをどうやってリファクタリングしていくか、の提案で対象は「オブジェクト指向だろうが手続き型だろうが問わない」ということだった。(ただし手続き型の場合は、CやCOBOLでテスト書くのと同じくらいの煩雑さは増えると思われる) ここでいう「本当に」レガシーなコードとはテスト自動化が足りてないあるいは全く達成されていない、と思ってよい。何か触ればどこかが壊れるのだが、それが把握できない状態、ということである。この定義だと、我々は今この時点でもレガシーコードを書くことができる、とも言える。

二件目のLarge-scale refactorings using the Mikado Methodは、比較的注意深く開発を進め、テストコードも整備してきたが、やがて全体像が見えにくくなり、いつの間にか複雑な依存関係が生み出されて保守効率が落ちた状態をどうやって解きほぐしてリファクタリングするか、という課題に答えるものである。

いずれも決して簡単ではないし楽しい作業ではないが、実戦に使えそうな手段であった。

The Worst of Legacy Code: Forensic Development

by Jason Kerney and Llewellyn Falco

まず配布された衝撃的な資料。
The Worst of Legacy Code: Forensic Development
このロシア語Javaを保守しろといわれたらどうする? ちなみにテストコードはない。参照するライブラリのソースは渡されていない。 どうする?

ロシア語というのは極端な例だが、引数名がarg1だったりメソッド名がmethod1だったりしても同じようなものだろう。要は「可読性が低く、テストコードはなく、依存しているライブラリのソースはわからない」状態でどうやって保守しますか? という問いかけ。

この問いかけに対し、参加者が手を挙げて色々と意見を述べていく。
The Worst of Legacy Code: Forensic Development
The Worst of Legacy Code: Forensic Development
「消しちゃえ」「テストしろ」「モック使え」「デカップリングだ」「Mikado使え(次のセッションの連中)」「Delegateしろ(俺)」「リファクタリングだ」「書き直し!」「デバッガだ」「ソースに印つけていくだ!」等など。

これらの提案をJason達は二つに分類している。要は「読んで分析する」か「動かして挙動を見るか」しかない、と。写真で黒丸がついてるのが「読む」、赤丸がついてるのが「動かす」。

そして「読む」に対する問いかけ。
The Worst of Legacy Code: Forensic Development
→このコードにバグがあるかどうか読んだだけでわかりますか?

public class FrameRate {
	public void countdown(HttpContext web, VideoMaker maker) {
		String type = (String)web.getAttributes().get("TvFormat");
		Integer frame = 0;
		
		if ("ntsc" == type) {
			frame = TvType.NTSC.FrameRate;
		}
		do {
			maker.createFrame("" + frame);
			frame--;
		} while (frame > 0);
	}
}

class TvType {
	public static TvType NTSC = new TvType();
	private static Integer NTSC_FRAME_RATE = 30;
	public Integer FrameRate = NTSC_FRAME_RATE;
}

HTTP Getで渡される変数typeの中身がntscなら、なんかの処理をTvType.NTSC.FrameRate回つまり30回だけ実行する、ということらしいが、それで正しいですか? という問いかけ。Javaに慣れている人はすぐわかるだろうけど、TvTypeのコンストラクタはstaticメンバーについてはNTSC, NTSC_FRAME_RATEの順で初期化していくから、TvType.NTSCのFrameRateに30は入らずゼロのままである。従って、countdownのループ文は一度実行された時点で終了する。*1

こうした変数初期化の順序に依存するようなバグは、コードを読むだけでは中々捕まえられないよね、ということ。じゃあ、どうするか? 走らせるわけだけど、上のコードはぱっと見ただけでもHttpContext渡してくるコンテナかなんかの中で動くし、VideoMakerというよくわからんクラスもいるよね。

だけど、上のコードで試したいのは「typeが"ntsc"」の時に「frame回数分(=30回)」ループが回るか、ということ。なのでテストコード側でHttpContextとVideoMakerのmockを渡してやれば話は解決する。
The Worst of Legacy Code: Forensic Development

Jason達はこのようなテスト方法を「Peel(皮むき)」と「Slice(薄切り)」という比喩で表現していた。

  • PeelはExtractを使って、テスト実行にじゃまっけな部分を対象の外にくくりだしてしまうこと。
  • SliceはMockやStabを使って、テスト対象が依存しているクラスを置き換えちゃうこと。

上の例で言えばcountdownはweb.getAttributes().get("TvFormat")だけを知りたいのだから、引数をwebにせず、String型のtypeにする。そして呼び側は"ntsc"を渡してやればループ開始の条件は満たせる。これがPeel。

一方のよくわからないVideoMaker型に関してはmockを用意する。createFrame()が何をしようがcountdownは知ったことではないのでmock先では何も実行しなくてよい。これがSlice。(例えばDBを呼ぶとか、ネット通信するとかいうのであれば、その戻り値をmockからもらえばよい)

この作業をコツコツと繰り返していけば、とりあえず変数名やメソッド名がなんだかわからなくても、依存しているクラスのソースがわからなくても、まずは分岐・繰り返しのテストケースだけは整備していける。もちろん、Peelはインターフェースを変えてしまうので、全体のビルドをして影響範囲を把握することも大事。影響範囲が広がるのであれば、直ちにはコミットしないか、ブランチにコミットさせていくことになろう。

重要: レガシーコードはバグを見つけてもただちにつぶしてはいけない。

発表で強調されていたのは、このPeel/Sliceを使ってテストを整備していくのは「大規模なリファクタリングを可能にする準備」のためであり、直ちにバグをつぶすためではない、ということ。これは「あるバグに依存している正常コードがどれだけあるかわからない」というレガシーコードの宿命であり、この不安を取り除くためにもまずはテストコードを準備するんだよ、ということ。

気の長い話だけど、避けては通れない話。発表後、J. B. Rainsberger氏が感想を述べていたが、技としては決して古くはない。ただしボトムアップ的にテストコードを整備していって、そこからレガシーコードのリファクタリングにつなげようという部分は価値があるようだ。

Large-scale refactorings using the Mikado Method

Ola Ellnestam , Daniel Brolund

水曜夜、一階のバーで飲んでいたら「カラオケいこーぜー」と妙に盛り上がっている北欧系の巨人たちがいたわけだが、そのうちの二人がこのOlaとDanielだった orz

そのOlaとDanielが提唱する「Mikado Method」とは依存性が複雑になってしまったクラス群を大規模にリファクタリングしていくための手法らしい。なぜMikadoか? Mikado Gameという遊びがあるらしい。会場で実物を見せてもらったが、床にばら撒いたたくさんの焼き鳥串を順番に拾い上げて、青い肉片がついていたら勝ちらしい(やや意訳

そのMikadoゲームがなぜ大規模リファクタリングか?

まずはOla/Danielによる漫才コーディングから始まった。*2 「Applicationに依存するUI」と「UIに依存するApplication」に手を入れようとして、ドツボにはまるという序章である。
Large-scale refactorings using the Mikado Method
もちろん、実プロジェクトでは直接参照しあうクラスが相互依存するということはないだろう。時間が限られた発表だったのでこういう極端な例となっていたが、実際には徐々に開発が進行していくことで、いつの間にか相互依存ループが出来上がっていた、などということは充分あり得る。

  • せめて受け入れテストを書いておこう!(小さな目標)
    • 新しいプロジェクトを作る(そのゴール)
    • 受け入れテストを書く(そのゴール)
  • UIを別プロジェクトに動かしてみよう!(小さな目標)
    • UIを別プロジェクトに動かす→依存性があって失敗する→ダメ
  • 依存性を切ろう!(小さな目標)
    • ApplicationInterfaceをくくりだそう。(そのゴール)
    • UIにApplicationへの依存性を注入しよう。(そのゴール)

こんな具合にちょっとの改善目標を設定し、それを試し、ダメならあきらめ、よさそうなら残しておく。そして、その過程を下の写真のようなグラフに記録していく。
Large-scale refactorings using the Mikado Method
このグラフが「Mikadoゲーム」に似ているらしいのだがMikadoゲームを知らない私には意味不明であった。

Mikadoゲームはどうでもいいとして、最後に残ったグラフの各要素(彼らはリーフ=葉っぱ、と呼んでいた)を順次実装していくと、リファクタリングが完了するのだそうである。今回の例では単純なグラフだったが、実プロジェクトではもっともっと複雑な(本当のMikadoゲームっぽい)グラフになるのだそうな。

こちらはリファクタリングに限らず、複雑な問題を片付けていく際の思考・試行フレームワークに使えると思った。プログラミングに応用する際には、前述のJason Kerney/Llewellyn Falco手法と組み合わせれば、レガシーコードのリファクタリングにも持っていけるのではないか。時間はかかりそうだけど。

最終日夜の打ち上げ

例年だと、締めの基調講演や各種発表に対する表彰式などを行いながらバンケットで食事、というのが普通なんだけど、今日はディズニーの変な催し物*3を見ながら食事しておしまい。

邪推するとディズニーの労組が強すぎて、カンファレンス会場の利用は夜8時までとか制約があるのではないかと。これまでも会議終了後に会場付近でうだうだしていたら、「おいおまえら失せろ」って厳しく追い出されたし。

結果、大規模な人数が集まれる場所はEpcotセンターしかなくて、そこを借りるともれなくディズニーの変な催し物がついてきちゃう、と。ここの会場も時間がきたらささささっと食事片付けられて、「おいおい、俺まだ飲み物券あるんだけど」と文句言うまもなく酒類まで片付けられちゃう時間厳守ぶり。

こんな有様だから、基調講演もへったくれもない。そして金曜午前に回されたが、遠くから来た人達は朝早くからとっとと帰路につく、と。

本来の会場であったナッシュビルならこんなことなかったんだろうけどね。

ついでにいうと、ナッシュビルからディズニーに会場が変更されたのは5月。この規模の人数のカンファレンスを開催二ヶ月ちょっと前に受け入れてくれた、というのは驚異的。それくらい観光客の予約は低調だったのだと思われる。

*1:NTSC_FRAME_RATEがstaticでなければ、こんなバグは起きない。なので、こんなことを軽々しくいう人の記事も信用してはいけない。→http://el.jibun.atmarkit.co.jp/minagawa/2010/04/post-ebc4.html

*2:水曜夜から練習したと言っていたが、冗談を飛ばしながらコーディングしていくというのは中々の芸である

*3:筋肉の塊による偉くゲイ風味の体操と、アメリカサイズの中国雑技団と、スカート吐いたジョナスブラザーズもどきによる毒にも薬にもならない古めのロック演奏