# レガシーコード改善ガイド 第2部③ ## 第12章 1か所にたくさんの変更が必要ですが、関係する全てのクラスの依存関係を排除すべきでしょうか。 多くの変更を必要とする場合は、いくつかの変更を一度にまとめてテストできる場所を見つけて、**より粒度の大きなテスト**を行うのが良い。 但し、単体テストの代替というわけではなく、**あくまで単体テストを作る第一歩として整備する。** ### 12.1 割り込み点 **割り込み点**は、**特定の変更による影響を検出できるプログラム上の場所**のことである。 多くの場合見つけるのは困難で、かなりの影響調査と多くの依存関係の排除が必要となる。 最適な方法は、変更すべき複数の場所を特定し、特定できた変更点から順に影響の追跡を始めることである。 #### 12.1.1 単純な場合 単純な場合、**割り込み点は変更点のすぐそばにあるもの**を選択する。 **変更点と割り込み点の間の段階をできるだけ少なくする**ことで分かりやすくなり、テストも作成しやすい。 逆に段階が多いと分かりにくく、テストも困難になる。 #### 12.1.2 上位レベルの割り込み点 多くの場合、変更のための最善の割り込み点は、**変更しているクラスのpublicメソッド**である。 但し、複数のクラスの変更からの影響を一つの場所で受け取れるクラスがある場合は、そのクラスのpublicメソッドが**さらに上位レベルの割り込み点**となる。 このような場所を**絞り込み点**という。影響スケッチ上では→が集約されたメソッドとなる。 この絞り込み点をチェックすることで、より広範囲の変更を一度に検出可能である。 ### 12.2 絞り込み点で設計を判断する **絞り込み点とは、自然なカプセル化の境界である。** コードの大きな塊に関する全ての影響が通過する場所となる。 この絞り込み点でテストを書くことは、困難なプログラム変更作業を始める理想的な手段である。 クラス群を切り出してインスタンス化し、仕様化テストを書くことで安全に変更できるようになる。 ### 12.3 絞り込み点の落とし穴 絞り込み点を使う上で落とし穴となるのが、単体テストを絞り込み点でのテスト(統合テスト)に徐々に成長させてしまうことである。 **新しいコードのための単体テストを書くコツは、できる限り独立してクラスをテストすることである。** テストが大きすぎると分かった場合は、一旦見直して独立した小さな部分に分けるべきである。 **既存のコードのためにテストを書く場合は、絞り込み点のテストから整備する**ことで、それをより限定した単体テストを簡単に書けるようになる。 **あくまで単体テストが主であり、既存のコードのテスト時のみ、大きなテストから単体テストを切り出していくやり方が有効なのである。** ## 第13章 変更する必要がありますが、どんなテストを書けばよいのかわかりません 本来テストはバグを見つけ、デバッグするための手段である。たいていは手動で実行される。 だが、変更の度に手作業で実行しなければならず、予定から遅れる原因にもなる。 バグをチェックして取り除くのは良いことであるが、そもそもバグを埋め込まないような正しいコードづくりができる方が、テストを書くよりも重要である。 自動化したテストはとても重要なツールであるが、直接的にバグを見つけるものではなく、振る舞いを維持するためのものである。 レガシーコードでは、そもそも変更のためのテストが無い場合が多い。 ### 13.1 仕様化テスト **仕様化テスト**とは、**システムの現在の振る舞いをそのまま文書化**したものである。 バグを見つけるテストとは異なり、**現在の実際の振る舞い**をテストするものである。 テストが足りないレガシーコードにおいては、実際の振る舞いを確認することが、最もシステムの動作を理解できる方法となる。 仕様化テストを書くことで、**発見した内容を明確にする**ことには価値がある。 コードの振る舞いに確信が持てない時は、仕様化テストを次々と書き、確信が持てるまで繰り返すと良い。 テストはシステムと開発者間の**コミュニケーションツール**であるともいえる。 #### 手順 1. テストハーネスの中で対象のコードを呼び出す。 2. 失敗すると分かっている表明を書く。 3. 失敗した結果から実際の振る舞いを確認する。 4. コードが実現する振る舞いを期待するようにテストを変更する。 5. 以上の手順を繰り返す。 ### 13.2 クラスの仕様を明らかにする 1. クラスの中で特に入り組んだ処理を探す。コードのある部分を理解できないなら、その仕様を理解するために**検出用変数の導入**を健闘する。検出用変数を利用して、コードの特定部分が実行されることを確認する。 2. クラスあるいはメソッドの責務が特定できたら、一度止まって**失敗する可能性のある事象の一覧**を作る。そしてその失敗をするテストを作成できるかどうかを考える。 3. テストに与える入力値を検討する。**特別な値を与えると何が起きるか**を検討する。 4. オブジェクトの存続期間を通じて、**常に成立する条件(不変条件)** があるかを確認する。不変条件を検証するためのテストを書く。そのためにはリファクタリングが必要な場合もある。リファクタリングを通じて、コードのあるべき姿について新しい知識が得られる。 5. バグを見つけた場合は、システムが稼働していなければ修正し、稼働しているならば、誰かがその振る舞いに依存していないかどうか、多少の解析が必要となる場合もある。 ### 13.3 狙いを定めたテスト 仕様化テストでは、異常な振る舞いを確実に定義する必要がある。 特に注意しなければならないのは、実際には間違っているにもかかわらず、処理結果が結果的に正しくなり、バグを見落としてしまうケースである。 例えば数値の型間違いで、int型とdouble型が混在する場合、範囲の大きな型は小さな型へと自動的に変換される。(暗黙的な型変換) これによりバグを見落としてしまう恐れがある。 このようなテストを作成する場合は、暗黙的な型変換等の影響を検出するために、**検出用変数を用いる**のがベストである。 ### 13.4 仕様化テストを書くための経験則 1. 変更する部分のためのテストを書く。コードの振る舞いを理解するために必要と考えられるテストケースをできるだけたくさん書く。 2. テストを書いた後、変更したい具体的な事柄について調べ、それに関するテストを書く。 3. 機能の抽出や移動をしようとする場合、既存の振る舞いや振る舞い同士の関係を検証するテストを個々に記述する。それにより、移動対象のコードが実行されること、およびそのコードが適切に関係しあっていることを検証する。 4. コードを移動する。 ## 第14章 ライブラリへの依存で身動きが取れません **ライブラリは便利な一方で、簡単にライブラリに依存した状態になってしまう。** ライブラリのクラスへの直接呼出しをコード内に分散させてしまうと、たとえ利用料が上がって変更しようとしても困難になる。 ライブラリのクラスを直接呼出ししている場所は、接合部となり得た部分であり、とてもよくできたライブラリもある。 しかし、具象クラスへ提供するライブラリがfinalやsealedで変更不可となっていたり、仮想関数が使えなかったりとテストが困難なライブラリもある。 こうした場合は、切り離したいライブラリ用のクラスに、**薄いラッパークラスを作成する**のが最善手である。 ライブラリの設計が本番用に特化している場合、テストでほとんど実行できない悲劇を生む。さらにSingletonのジレンマや、制限されたオーバーライドのジレンマ等を生み出す可能性がある。 **ライブラリの機能とテストコードを書くためにすべきこととの間には、根本的に対立していることを念頭に置いてライブラリを利用すべきである。** ## 第15章 私のアプリケーションはAPI呼び出しだらけです ライブラリに代表される、自分たちで変更できないコードを利用する場合、安定性や十分さ、簡易性等をしっかり把握する必要がある。 また、ライブラリばかりを呼んでしまうコードになりやすい。このようなコードは、構造を良くする方法を調べることが難しいため注意が必要である。 こうしたコードを変更する場合、ライブラリ部分はテスト不要と考えてしまうが、そのようなことは全く無い。 単純なコードでも、追加により肥大すると同時に複雑化するため、結果ライブラリの大きな依存から抜け出せなくなってしまう。 ### 依存から抜け出す方法 API依存から抜け出す方法は2つあり、1つは**APIをラップする方法**である。これは、**APIを模したインターフェースを用意してAPIをラップする**方法である。 擬装オブジェクトを簡単に作りやすいのが特徴だが、final等でAPIの値が変更不可になっている場合は使えない。 従って**APIが比較的小さく、APIを通じたテストができないため依存を完全に分離したい**場合に用いる。 もう一つは、**責務を基に抽出する**方法である。これは、責務を識別して、その単位でメソッドの抽出を行うことである。 **APIが複雑であったり、安全なメソッドの抽出をサポートするツールがある場合に限られる。** 多くのチームでは両方の手法を同時に利用する。 ## 第16章 変更できるほど十分に私はコードを理解していません 不慣れなコードに足を踏み入れることは、特にレガシーコードの場合、恐ろしいことになる可能性がある。 このような場合は、まずはしっかりとコードを理解することが必要である。 ### 16.1 メモを取る、スケッチを書く コードを読むだけでは混乱する場合は、絵を描いたりメモを取ったりすることが有効である。 規約に沿った書き方であっても良い。スケッチの優位点は形式にとらわれない影響力がある。 ### 16.2 印をつける 非常に長いメソッドに対しては、印をつけて理解を深めるのが良い。 以下の4つの使い方が紹介されている。 #### 16.2.1 責務の分割 責務ごとに同じ印をつけて分割する。色分けするとなお良い。 #### 16.2.2 メソッド構造の理解 コードのブロックに線を引くことにより、メソッドの開始点・終了点を見極められる。 簡単な方法は、内側からたどっていくことである。 #### 16.2.3 メソッドの抽出 抽出したいコードを丸で囲んでおけば、リファクタリング時に理解しやすい。 #### 16.2.4 変更の影響の理解 影響スケッチの代わりに、変更したいコードに印をつけ、影響を受けるメソッド全てに印をつける。 ### 16.3 試行リファクタリング **試行リファクタリング**とは、動作する状況をgit等の管理ツールで保存した後に、テスト無しでリファクタリングを行うことである。 **テスト無しで自由に変更できる点が特徴で、最後は破棄するため管理ツールに保存した動作するバージョンに簡単にロールバックできる。** 但し、注意点がいくつかあり、ひどいミスをした場合に実際には行っていないことをシステムが行っていると思い込んでしまう点、リファクタリングしたことに固執してしまい、本番リファクタリングでうまくいかなくなる点がある。 試行リファクタリングにこだわりすぎず、広い視野をもって取り組むことが重要である。 ### 16.4 使用していないコードを削除する いわゆるデッドコードの削除。見つけ次第削除する。 ## 第17章 私のアプリケーションには構造がありません 寿命の長いアプリケーションは、無秩序に拡大しがちである。 最初は良い設計だったものが、数年経てば設計自体が良く分からないものになってしまっていることも多い。 原因は、新しい機能を追加する時に、設計を気にせず「やっつけ仕事の場所」に追加してしまう事である。 **チームがアーキテクチャを理解していない場合、アーキテクチャはどんどん退化してしまう。** 複雑で全体像が無い巨大なシステムを作らないように、全体像を明らかにするアーキテクトという役割を設けてきた。 しかし、アーキテクトは常に仕事をチームと共にせねばならず、チームと同一の全体像を持っていなければならない。 万一チームの全体像とアーキテクトの全体像が異なる場合、システムの実態との大きなズレを生み出してしまう。 **アーキテクトの役割自体は重要なものの、任せきりにせずチーム全体がアーキテクチャを理解する努力が必要である。** ### 17.1 システムのストーリーを話す 2人以上で行う手法で、片方がシステムのアーキテクチャは何かと質問し、もう片方はその質問に答え、説明する。 システムの中核部分に関する重要事項を全て話し終えるまで続ける。 アーキテクチャを簡潔に伝える際にポイントとなるのが、単純化してアーキテクチャ自体の簡単なストーリーを説明することである。 このストーリーの説明こそが、システムのロードマップの役割を果たすため、システムへの恐怖をずっと減らすことができる。 また、重要度に応じて取捨選択を容易に行うことができる。単純化した説明は、新たな抽象化の発見にも繋がる。 ### 17.2 白紙のCRC オブジェクト指向言語が出た当初は、その仕組みを理解するために、CRC(Class, Responsibility, Collabolation)と呼ばれる方法が流行った。 現在はUMLに統一された感があるが、このCRCの仕組みを応用して、白紙のカードでインスタンスを表し、関係性を表す線で結んだり、カードを重ねてコレクションを表したりすることでオブジェクトの振る舞いを図解した「白紙のCRC」なる方法がある。 この方法では完全な表記は難しいものの、非常に有効な方法である。 大切なのは、身振りや置く場所によって、システムの一部がどのように相互作用しているかを示せることである。 ### 17.3 会話の吟味 レガシーコードについて話し合う際、話している内容が本当に正しいのかについては、吟味しなければならない。 なぜなら、口では簡単に話せることでも、より強い制約をプログラムは満たさなければならないためである。 コードにチームの理解が反映されていなかったり、誤った解釈をしてしまっていると、会話とコードに共通性が無くなってしまう。 ###### tags: `読書`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up