BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース テスト駆動開発とレガシーコードのトラブル

テスト駆動開発とレガシーコードのトラブル

ブックマーク

原文(投稿日:2009/11/19)へのリンク

Complex MazeAlan Beljeu 氏は古い (レガシー) C++ コードベースで TDD を行っていて,トラブルに見舞われた。その理由はこうだ。

機能を完全に実装できていないクラスが最後に残ります。いつか必要になるかも知れない,というやつです。他のクラスからそれを利用しようとして,実装を完成させる時がきた,まさにその時になって当初の設計不足が明らかになるのです。設計は新たにやり直し,外部仕様(とそのテスト)も修正が必要。そのクラスを使っていた既存コードも変更しなければなりません。

そして彼は,"事前の大規模設計 (Big Design Up Front)" がこの問題の解決策ではないか,と考えるのだが,アジャイルコーチである George Dinwiddie 氏は,Alan のこの例が訴えているものを指摘する。すなわち,きれいなコード (clean code) の原理に注意を払わなくてはならない,基本的な結合度 (coupling) と凝集度 (cohesion) が必要である (つまり SOLID),ということだ。

アジャイルコーチ である Mike “Geepaw” Hill 氏は,自身の長年にわたるアジャイルチームのコーチ経験から,次の中のどれかが問題の根源にある,と言っている。

  • リファクタリング作業への習熟不足のため,クラスが最小限になっていない
  • 簡略化に関するスキル不足のため,クラスが最小限になっていない
  • マイクロテスト (別名ユニットテスト) が積極的かつ迅速に行われていないため,コード変更によるテスト失敗の頻度が高すぎる
  • チーム間の依存性,公開された依存性の扱い方が分かっていない 例)API 出荷
  • おそらくは,揺れのないビルド (jiggle-less build) が存在しない
  • 1940年台のツールを使用しているのかも知れない

XP コーチKeith Ray 氏は,レガシーコードのように大きな技術的負債を持つシステムを扱う場合には,その返済コストがストーリの実装コストの大部分を占める,と指摘した上で,次のようなアプローチを提案する。

コードを構造的に改善する (技術的負債の支払いを行う) ためには,新機能の実装が必要なときには常に,新しいコードと古いコード両方の状態に十分な注意を払わなければなりません。その結果としてどちらかのコードにまずい部分が見つかったならば,リファクタの実施を検討するべきです。

リファクタは手作業で,安全な大きさを単位として行うのがよいでしょう (たとえ C++ であっても)。リファクタリングに関する Fowler 氏の著書にある説明を読んで,それが暗記できるまで注意深く従いましょう。Eclipse 上で gcc を使用する場合には,抽出 (Extract) や名前変更 (Rename) などいくつかのリファクタリング機能が利用できます。名前変更はスコープを認識するので,検索・置換よりも安全です。ただし抽出メソッドと,Eclipse のこれ以外のリファクタリングはバグがあります。使用する場合には注意してください。また,関数のシグネチャを変更するような場合には,変更が必要な部分を"コンパイラ頼み"で見つければよいでしょう。

リファクタリングが既存コードを壊していないことを確認するためには,テストを行う必要があります。Feathers 氏の著書である “working with legacy code” には,レガシコードにテストを追加するためのテクニックがたくさん紹介されています。もっと上位の作業レベルから見て,まずいコード (code smells) というのは,よい設計指針に従っていないものです。例えば単一責任原則 (Single Responsibility Principle,SRP)によると,すべてのクラス/メソッド/モジュールの目的はひとつでなければなりません。他にも凝集度,結合度,依存管理などに関する原則があります。一方でこのような抽象的な原則を適用するよりも,まずいコードを探す方が簡単な場合もよくあります。"巨大なクラス","巨大なメソッド" は "クラス抽出" や "メソッド抽出/移動" で治療できます。それでも SRP を知っていた方が,クラスやメソッドのどの部分を抽出すべきかを決定する上で役には立ちます。

最も重要な設計原則はおそらく "命じよ,求めるな (Tell, don't ask)" でしょう。機能とデータを合わせて保持せよ,という意味です。悪いコードでは機能と,それに必要なデータが別々のことがよくありますが,これはローカル性の欠如と依存性の問題を引き起こします。その象徴が "機能追加時に多くのコード変更が必要になる" という現象でしょう。まずいコードのパターンである "ショットガン手術 (shotgun surgery)","機能の嫉妬 (Feature Envy)","長いパラメータリスト" などがこれにあたります。

迅速なフィードバックの獲得がさらなるリファクタリングを可能にして,(ついには)新機能のより早い開発を可能にします。並列ビルド (分散コンパイル) が実行できるようにしましょう。より小さなソースファイル,ヘッダファイルを作りましょう。ヘッダファイルの複雑性を下げるために,前方参照定義の利用,インラインコードの回避を心がけましょう。ヘッダファイル/ソースファイルにはひとつのクラスを記述するようにしましょう。"pimpl" イディオムを広く採用するとコンパイル時間を10%程度減少できますが,"大きなクラス" や "機能の嫉妬 (Feature Envy)" が隠れている場合もあります。

書き直しを行う場合と比較すると,リファクタリングの利点は "動作するコードが常に手元にある" ということです。手動ないし自動で行うテストの内容が十分なものであれば,たとえ状態が悪い設計からよい設計への中間段階にあったとしても,コードの出荷は可能です。

Keith は “Refactoring: Small Steps Guaranteed to Help You Clean Up Your Code” という,C++ コードをリファクタする記事を Better Software Magazine に寄稿してもいる。

これまでに InfoQ で取り上げた関連記事は次のものだ。"Dealing with Legacy Code","Bobおじさんが述べるTDDの適用可能性","TDDを根づかせる:導入の問題と解決策"

この記事に星をつける

おすすめ度
スタイル

BT