コードのクリーンアップ
技術的負債の返済に役立つ 9 つの戦術
David Laribee
MSDN Magazine の 2009 年 12 月号では、技術的負債に取り組むために、問題を特定して主張を展開するためのアドバイスを書きました。簡単に言えば、近い将来に問題になりそうな負債を特定することが重要だと信じています。コードベースのほとんど触れられない部分で、価値ある技術を確立しても、明日の生産性向上の実現には役立ちません。
また、負債返済の重要性について経営陣からお墨付きを得ることの重要性を理解し、同じ理由から手堅い主張を展開するための基本ツールを用意してください。
では、利息の高い技術的負債を返済するうえで役立つ可能性がある戦術に話を移しましょう。技術的負債に対処する際に実績のある戦術はたくさんあります。難しいコードと争うためのパターン、ツール、および手法をすべて紹介すると、この記事にはとても収まりません。そこで、長年をかけてレパートリーに追加してきた、より的を射た技法をいくつか紹介します。
学習、学習、学習
問題があることはわかっていても、それを解決する方法がよくわからないときは、コードを泥沼から救い出すために新しい知識やスキルを身につける時期が来たのかもしれません。よく言われるように、学習が基本です。
学習の方法は 1 つではありません。コンサルタントやスクールなどの形式で、外部からの助けが必要になることも考えられますし、書籍で対応できるかもしれません。
学習プロセスには、チーム全員を巻き込むようにします。手始めにチーム内に "読書クラブ" を設けるのも一案です。コースやカンファレンスで学んだことを学習用のプレゼンテーションにまとめて、自分のチームに提供することもできるでしょう。
チーム全員を巻き込むための、共同作業型で実践的な手法がコーディング道場です。基本的なコーディング道場では、プログラミングの課題を取り上げ、それにグループで取り組みます。私は、2 人を基本単位とし、1 人ずつ入れ替えて、他のメンバーが見ている中で課題に取り組むという方法を試しています。この方式では、2 人のメンバーが 1 つのプログラミング作業に共同で取り組みます。このとき、"タグ" を使用して作業を区切ります。このタグまで来たら、メンバーを交代して別のメンバーが道場に入ります。
独自のペースで学習するにしても、読書クラブを始めるにしても、レガシ コードの保守性を向上することをテーマとした推薦図書が 2、3 あります。
まさにぴったりのタイトルが付いた、Michael Feathers の『レガシーコード改善ガイド (翔泳社、2009 年) では、レガシ コードを丹念に調べるためにパターン ベースの方法を紹介しています。著者は、レガシ コードはテストされていないコードであると断じています。レガシ コードは変更が難しく、変更の結果回帰エラーが発生しないことを保証できません。この書籍では、コード内での結合を減らし、コードをテストしやすくすることに的を絞った多数の戦略と戦術が紹介されています。
Kyle Baley と Donald Belcham による『Brownfield Application Development in .NET』(Manning Publications、2010 年) は、この分野の最新刊の 1 冊です。この書籍では体系的な方法で、いわゆるブラウンフィールド (反義語はグリーンフィールドで、新規開発のこと) のコードベースの改善を図っています。この本が優れている点の 1 つは、推奨方法は幅広く適用可能ですが、コード例が Microsoft .NET Framework を基に設計されていることです。この記事をお読みになっている方にとっては都合がよいことでしょう。ここで扱われているチーム開発の方法も、非常に優れていると思います。つまり、整備されていないコードベースに変更を加えるときは、継続的統合やバージョン管理などの基本を実装することで得られる自信が値千金です。
外交手腕
対処しなければならない問題のあるコードを作成したのが自身の現チーム メンバーであることは大いに考えられます。このような状態でコードを検証するときは、十分な配慮が必要です。感情を傷つけると自己防衛本能が働き、そのため改善作業がなかなか先に進まなくなります。
自身の過去の失敗談を話して、状況を和らげるよう努めます。あくまでもプロとして、個人攻撃を避け、元のコードの作者から改善策を提案をするよう働きかけます。
また、自分自身が、今の問題の原因を作った開発者の 1 人である可能性も十分にあります。次の文を声に出して読んでください。「私は私のコードではありません。毎日が学習であり、私は先に進むため、よりよい手段を探すことに打ち込んでいます。同僚の批判や自分自身のエゴによって、改善にかけるチームの取り組みを邪魔するようなことはしません。」
実際には、このような問題を乗り越えるには時間がかかります。私は、改善について判断し、話題にする最善の方法は、過去ではなく、現在や近い将来に集中することだと気が付きました。このコードはどのようになり得るのか、どのように変えていきたいかを考えます。
既に取り組んでいる仕事でのちょっとした外交手腕と他人の感情に対する配慮は、先に進む長い長い道程で大きな役割を果たします。
形にする
中には、あまりにもひどい状態で、まったく何がどうなっているかを理解するのが難しいコードがあります。おそらく、すべてのクラスが同じ名前空間に含まれています。依存関係が複雑にもつれ合い、履歴をたどっていくと、短期記憶の能力をはるかに超えて、自分の居場所がわからなくなってしまうようなコードベースです。
このような症状では、多くの場合、実装のレベルではなく、アーキテクチャのレベルおよび設計のレベルで負債を診断することになります。これは、私の知る限り、最もたちの悪い負債で、通常、変更のコストが最も高くつきます。
Brian Foote と Joseph Yoder は、あらゆるものが他のすべてに依存している、認知できる形のないアーキテクチャーを "大きな泥団子" (laputan.org/mud、英語) と呼んでいます。
「大きな泥団子は、無計画に、それどころかでたらめな構造を持つシステムです。構造と呼べるものがあるとすれば、それは設計ではなく、ご都合主義によって決められています。しかし、このようなシステムが依然として数多く存在しているからといって、アーキテクチャ全般が軽視されていることにはなりません。」
現在運用中のソフトウェア アプリケーションのほとんどが、大きな泥団子であることはまず間違いないでしょう。これは、必ずしも価値観の問題ではありません。この世の中には、巨額の富を生む、膨大な量のひどいコードが出回っているのです。大きな泥団子が、事業主や株主の華美な夢と贅沢な望みを満たしているのであれば、それも当然です。
問題は、その泥団子アプリケーションの変更に費用が嵩むようになっていることです。ビジネス環境は動的なのに、ソフトウェアの柔軟性は失われていくばかりです。これに対処するための代表的な戦略は、ソフトウェアの核爆弾とも言える大規模な書き直しです。大規模な書き直しにはさまざまなリスクが伴うため、多くの場合、現行のシステム設計の改善を図る方が賢明です。
もう少しレベルの低い手法を使用できるようにするには、システムを形にすることが有効な場合がよくあります。この典型的な例が、階層型アーキテクチャです。従来からこれは、UI がサービスと通信し、サービスがなんらかのモデルと通信し、モデルが永続化層と通信する形をとります。
コードを階層型の形にするのは、忠実度の低い作業になり得ます。まず、手始めに、アーキテクチャ各層の名前を付けた名前空間にコードを整理します。
これで、駒を進める準備ができました。つまり、上位の層 (ユーザー インターフェイス層) は、次のレベルの層 (サービス層) にしか依存できないという規則を当てはめます。この規則を当てはめる簡単な方法は、Visual Studio を使用して、各層を個別のプロジェクトに移行することです。この規則に違反していると、ソリューションをコンパイルできません。
規則に違反しないようにすると、結合の度合いが緩くなります。モデルとアプリケーションのビューとの結合は解消されます。形にすることで、意味のあるまとまりが強化されます。層内の各クラスは、それがデータをエンド ユーザーに表示することであれ、ビジネス動作をカプセル化するのであれ、すべて同じ目的に向かって働きます。
層と層の間にファサードを作り、UI など上位の層は、層内の細かいクラスではなく、下位の層が提供するファサードに依存するようにします。この手法は、段階的に、チャンスをうかがいながら適用できます。
一枚岩のような大きな泥団子を形にすることで、より的を絞って技術的負債を返済する機会を特定できるようになります。つまり、たとえば CompanyX.ProductY.Model でさまざまな作業をしているのであれば、静的分析ツールを使用してドリル ダウンし、最も結合度が高いまたは最も複雑なクラスを見つけることができます。
テストによる近接航空支援
システムの動作を変更しないで、変更を行うプロセスを、リファクタリングと呼びます。オブジェクト指向 (refactoring.com、英語) のコード用にも、リレーショナル データベース (agiledata.org/essays/databaseRefactoringCatalog.html、英語) のコード用にも、メソッドの抽出 (Extract Method) やテーブルの分割 (Split Table) といった専用の完全なリファクタリング パターン言語が存在します。ただし、コードベースを完全に理解していなければ、このような詳細で安全な方法を適用することは難しいのが事実です。
では、レガシ プロジェクトの変更に取り掛かるにはどうすればよいでしょう。可能であれば、変更を行う前後にテストを実施するのが常に安全であることを、まず認識すべきです。コードを変更するということは、エラーを生み出す可能性があるということです。しかし、コードを変更する前にコードをテストしておけば、間違いに気付く確率は高くなります。
自身の変更によって危険な問題が発生することはないと心から確信できないままコードに頭から飛び込んで、手当たり次第に変更を加えることが唯一の方法ではありません。
コードの変更に取り掛かる前に、テストを作成する基盤にできるハード インターフェイスがシステムにあるかどうかを確認します。この場合のテストは、一種のブラックボックス テストです。つまり、システム入力を渡し、出力を調査します。変更を行うときは、絶えずテストを実施して、変更によって既存の動作に影響を与えていないことを確認してください。
密結合システムの一部に取り組んでいるときには、この戦術の適用が難しい場合があります。テストのコストが、負債を取り除くメリットをはるかに上回る可能性があります。コードベースを立て直すプロセス全体で、このコスト対メリットの分析を絶えず行います。アプリケーションまたはアプリケーションのコードベースの広い範囲を完全に書き直す方が、コスト効率がよくなる場合もあります。
識別可能な効果の計測
改善対象のコード領域に関する計測基準を確立します。説明のために、ここではアプリケーションのコア ビジネス ロジックを整理しているとします。この名前空間の型のメンバー間には、switch ステートメント、入れ子にされた if ステートメントなど、さまざまなパスがあります。サイクロマティック複雑度などの計測基準を使用すると、改善作業によってコードが簡素化されるかどうかを大まかに確認できます。
NDepend コード分析ツール (ndepend.com、英語) を使用すると、コードベースの特定の部分について、非常に厳密な計測結果が得られます。NDepend では、.NET アセンブリの名前空間、型、およびメンバーに対して使用できる、強力なコード クエリ言語 (CQL: Code Query Language) を提供します。
図 1 の CQL ステートメントについて考えてみましょう。ここでは、特定の名前空間内の結合度や複雑度などの計測基準 (NDepend により測定できる多くのメトリクスのいくつかのみ) を調査しています。既に形にしてあるため、コードの特定部分に集中して作業できることがわかります。有効な変更を実施できていれば、結合や複雑度といった計測結果は時間の経過と共に減少して行きます。
図 1 NDepend の CQL
-- Efferent coupling outside a namespace
SELECT TYPES
WHERE TypeCe > 0
AND (FullNameLike "MyCompany.MyProduct.Web")
-- Afferent coupling inside a namespace
SELECT TYPES
WHERE TypeCa > 0
AND (FullNameLike "MyCompany.MyProduct.Web")
-- Top 20 most complicated methods
SELECT TOP 20 METHODS
WHERE CyclomaticComplexity > 4
AND FullNameLike "MyCompany.MyProduct.Web"
この戦術にはすばらしい二次効果があり、負債が取り除かれた後に、その状態を維持し、規律を守るうえで、この計測結果が役立ちます。既に改善を施した部分に新たな負債が再び発生するのを防ぐ、初期警告システムとして計測結果を利用できます。
改善専用の流れ
隔絶された世界に住んでいるわけではないので、改善に取り組んでいる間も、おそらく、新機能の開発や既存機能の変更を引き続き行うように求められます。納品のプレッシャーから、銃を突き付けられているような気分に襲われます。しかし、メンテナンスは、無視しようとするのではなく、喜んで受け入れるべき現実の 1 つです。
この対策の 1 つは、新機能の開発と並行して、負債項目を改善する専任の担当 (1 人、2 人、またはチーム全員) を割り当てられるように、会社側から承認を得ることです。
これは非常に有効な戦略ですが、チーム全員 (コードベースを変更するすべての開発者とテスト担当者) が、実施する改善作業に関与するのが一番です。2 人単位で、定期的に 1 人ずつ交替するようにします。改善の流れに最も長く従事している開発者が抜け、もう 1 人の開発者が新しく相棒になった相手に状況を説明します。
知識を広めることで共同所有の実現に近づき、その結果、リスクが軽減され、設計が改善されます。自身が開発に取り組んでいる新機能の直接の障害になっている問題を改善する機会が見つかることもあります。機能の新規作成や変更に着手する場合は、必ず一覧を参照して、自身が取り組もうとしている作業に関係する改善対象が既にチームによって特定されていないかどうかを確認することをお勧めします。
改善の機会は常にあります。何かの作業の途中で改善の機会が特定されて、次回チーム メンバーがそのコードを使用する場合に違いを生む、簡単ないくつかのリファクタリングによって改善できてしまうこともよくあります。
新機能を開発しながら、既存のコード ベースを改善する場合、コスト対メリットの分析を絶えず行います。改善コストがあまりにかかるようであれば、一覧に戻し、改善計画を立てる中で検討します。
繰り返し、繰り返し、繰り返し
いくらかの負債を返済しました。これで、また最初の手順に戻って、修正が必要な次の項目を特定し、優先順位を付けて、コンセンサスを得る段階に達しましたね。
たしかにそうですが、深く考えずに一覧を少しずつこなすだけではなく、もう少しすべきことがあります。つまり、返済した負債以上に新たな負債を作り出していないことを確認する必要があります。また、新規開発や同様の改善プロジェクトの将来の取り組みの中で、定期的に、学習した内容を取りいれてください。
コードベースを改善する機会は、定期的に変化します。機会が浮上したり、それぞれの重要性が上下したりします。利息の高い負債の状態がこのように変化する理由は、リリースごとに異なります。
私にとって有効だったのは、開発者と毎週短時間の会議を開いて、新しい負債項目を見直し、既存の負債項目の未処理分に優先順位を付けることでした。これにより、確立したコンセンサスを有効な状態に保ち、一覧を最新の状態に保つことができます。この場合も、現在のリリースまたはプロジェクトの進行を遅らせる可能性のある負債の返済を優先します。
会議では、まず最初に、新しい項目を見直します。各項目を特定したメンバーの主張を発表してもらい、その項目に未処理項目として追加するだけの価値があるかどうかを投票して決めます。新しい項目をすべて見直したら、古い項目を見直します。該当しなくなった作業はないか、また、その作業を完了することですぐに価値が実現されるか、つまり、日々の障害が取り除かれるかを確認します。最後に、その改善の機会をその他の機会と比較して優先順位を決め、一覧内の優先順位設定し直します。一覧内で最上位にある項目が、次の改善作業の対象です。
改善後の状態維持
意気揚々と利息の高い技術的負債を返済しながらも、新しいソフトウェアの開発も並行して進めることになるでしょう。堅実なプログラミング手法について学習し、新しいパターンをコードに取り入れながら、この知識をゆくゆくは応用してください。追加の作業が、既存の技術的負債の上に重ねられ、手の施しようがなくなっていく可能性があります。
新しい作業についての会社側関係者の期待値を設定することは重要です。急いで、とにかく完成させるスタイルのコードよりも、品質が高いコードの方が時間がかかります。この事実は、私の 2009 年 12 月の記事で紹介した体系的な思考の概念を思い出させます。私とっては、これは文化的な属性です。つまり、組織が長期にわたる持続性を意識して考えられるのか、今買っておいて支払いは後回しにする考え方 (技術的負債の温床) で進み続けるのかの違いです。「そもそも、私たちはどうしてこんな状況に陥ってしまったのか」という核心の問いを決して忘れないでください。
コードベースを改善する方法を学習しながら、おそらく、新しいコードに適用するチームの標準操作を開発することになるでしょう。このような操作を wiki などのツールに記録したり、規模の小さな非公式の勉強会を設けて、学習したことをチームと共有したりすることをお勧めします。また、同じような改善項目を処理する手法も開発します。問題のある設計の修正や実装のクリーンアップのために同じ作業を 3 回、4 回と繰り返し行っている場合は、チームのドキュメントにまとめます。つまり、よく知られている場所に情報を記録し、情報がそこにあることをメンバーに連絡します。
共同作業
技術的負債は、人間の問題です。無知であるか非現実的な期待のために、人間は問題を生み出し、その後始末に追われるようになります。そして、この問題を解決するには、グループで作業することが必要です。
このようなアドバイスは当たり前のことです。ソフトウェアの専門家であり、おそらくその作品に熱心に取り組んでいる皆さんに、全面的に賛成していただけないとしたら、私は驚きます。
改善を成功させるには、全員が関わる価値体系、つまりチーム全員の根本的変化が必要です。品質の経済は最終的には報いがあることが証明されていますが、この信念を早い時期に受け入れてください。文化を変えるには、心をつかむ必要があり、これは本当に大変な仕事になる可能性があります。私ができる最も役に立つ提案は、"一人でするな" ということです。取り組みに対するチームの支持を取り付け、必ず全員が結果に関心を寄せるようにします。
「90% のカバレッジを達成する」や「常にテスト駆動開発 (TDD) を実行する」などの目標を設定することは、あまり意味がありません。現時点と近い将来に足かせとなる問題に取り組みます。これは、TDD を採用することやカバレッジ レポートを指針にすることになるかもしれませんし、ならないかもしれません。あるいは、チームがオブジェクト指向の分析と設計の基本を理解するといった、もっと初歩的なことになるかもしれません。
変化を起こす
負債に取り組むツールや手法をご紹介できたか、少なくとも、これまで明示されていない考えや経験を明らかにできていればさいわいです。しかし、重要なのは、技術的負債に対処することは、基本的に、製品ごとに異なる問題であることに気づくことです。たとえば、開発と営業の間の信頼関係が低い環境に置かれていて、弁護士の準備をして主張しなければならない状況に陥る可能性もあります。
負債を減らす方法を教えてくれる、すぐに使えるプロセスはありませんが、"いつ" そして "どの段階で" 変化を起こすかについては、今日が好都合です。価値ある技術への歩みは、最初は遅々として進まず、厳しい道程になる可能性があります。粘り強い取り組み、継続的な学習、なによりも本気の姿勢が、厳しい時期を耐えしのぎ、負債で身動きがとれなくなったコードを黒字に転換する唯一の道です。このプログラムに取り組み続けることをお勧めします。顧客にとっての価値を向上できるだけでなく、自身の仕事道具箱も大きく拡充できます。
Dave Laribee は、VersionOne の製品開発チームを指導しています。全米各地の開発者イベントで頻繁に講演を行っており、2007 年と 2008 年に Microsoft Architecture MVP を受賞しました。CodeBetter ブログ ネットワークで、ブログ (thebeelog.com、英語) を執筆しています。