# レガシーコード改善ガイド 第2部① ソフトウェアの変更 ## 第6章 時間が無いのに変更しなければなりません 実務では、納期の制約の中でプログラムを作成しなければならない。 例えば、テストが無い中で急な追加案件の実装を求められた場合、テストが無いまま変更して間に合わせるか、時間をかけてテストを作成してから行うかの2択を迫られる。 追加要件にかかる時間が分からなければその判断はより困難となる。 以下は、テスト環境をすぐに整えられない場合に、全く新しいコードを書くことで変更に対応するための技を紹介する。 ### 6.1 スプラウトメソッド 追加部分をメソッドとして呼び出す方法である。 独立した1つの機能としてコードを追加したり、メソッドのテストを整備していない場合に効果的である。 #### 手順 1. 変更が必要なコードを洗い出す 2. 変更がもしメソッド中の1つの命令文で実現できるなら、新しいメソッドの呼び出しコードを書いてコメントアウトしておく。 3. 呼び出されるメソッドで必要なローカル変数を特定し、それらを新しいメソッドの呼び出しの引数にする。 4. 新しいメソッドから呼び出し側のメソッドに値を返す必要があるかを決定する。必要があるなら、戻り値を変数に代入するように呼び出し側を変更する。 5. 新しく追加するメソッドをテスト駆動開発によって作成する。 6. 呼び出し側のコメントを外して、呼び出しを有効にする。 #### 長所と短所 * 長所 * 古いコードと新しいコードの区別を明確にできる。 * 変更の影響を受ける変数を全て把握できる。 * 短所 * 元のメソッド及びそのクラスを放置することになる。 * 一部分のみの変更だと、意図が分かりづらい。 ### 6.2 スプラウトクラス 新たなクラスとして作成する。 新たな機能追加の際に、依存関係が激しくクラスがテスト可能な状況でない場合や、変更のための全く新しい責務を追加したい場合に用いる。 #### 手順 1. 変更が必要なコードを洗い出す 2. 変更がもしメソッド中の1つの命令文で実現できるなら、新しいクラスにふさわしい名前を考える。その後、変更場所に新しいクラスのオブジェクトを生成し、そのオブジェクトに対し必要なメソッドを呼び出すコードを書いてコメントアウトしておく。 3. 呼び出し元のメソッドで必要なローカル変数を特定し、それらを新しいクラスのコンストラクタの引数にする。 4. 新しいクラスから呼び出し側のメソッドに値を返す必要があるかを決定する。必要があるなら、値を提供するメソッドをクラスに用意し、呼び出し側にその値を受け取るための呼び出しを追加するる。 5. 新しく追加するクラスをテスト駆動開発によって作成する。 6. 呼び出し側のコメントを外して、呼び出しを有効にする。 #### 長所と短所 * 長所 * 直接書き換えるよりも、確信をもって変更を進められる。 * 既存のヘッダーファイルの修正が不要である。(C++) * 呼び出し元のコンパイルの負荷を減らせる。 * 短所 * 抽象的な処理部分と、別の多くの処理から構成されるため、仕組みが複雑になる。 * 状況的に追い込まれただけの理由で適用すると、本来1つのクラスにあるべきものがバラバラになってしまう恐れがある。 ### 6.3 ラップメソッド 新たなメソッドで旧来のメソッドを呼び出し、新旧を分離する方法である。 元のメソッドの処理の前後に、新しい振る舞いを追加したい場合や、まだどこからも呼び出されていないメソッドを追加したい場合に適用できる。 #### 手順 1. 変更が必要なコードを洗い出す 2. 変更がもしメソッド中の1つの命令文で実現できるなら、メソッド名を変更して、古いメソッドと同じ名前、シグネチャを持つ新しいメソッドを作成する。その際にはシグネチャの維持を必ず行う。 3. 古いメソッドと同じ名前でなくてよい場合は、テスト駆動開発により変更を実現する新しいメソッドを作成し、新しいメソッドと古いメソッドを呼び出す別のメソッドを作成して終了する。 4. 新しいメソッドから古いメソッドを呼び出すようにする。 5. 新しい要件のためのメソッドをテスト駆動開発によって作成する。 6. 作成したメソッドを2で作成した新しいメソッドから呼び出す。 #### 長所と短所 * 長所 * 既存のメソッドの大きさを変えずに済む。 * 既存の機能から新しい機能を明確に独立させられる。 * 短所 * 不適切な命名を行いがちである。 ### 6.4 ラップクラス ラップメソッドをクラスのレベルに拡張したもの。 別のクラスをラップするオブジェクト群を呼び出し、順番に渡す手法をDecoratorパターンという。実行時にオブジェクトをくみ上げて、複雑な振る舞いを可能にする。 追加しようとしている振る舞いが完全に独立しており、既存のクラスを汚染したくない場合や、クラスが巨大化しており、これ以上増やせない場合に用いる。 #### 手順 1. 変更が必要なコードを洗い出す 2. 変更がもし1つの命令文で実現できるなら、ラップ仕様としているクラスをコンストラクタの引数として受け取るクラスを生成する。元のクラスをラップするクラスがテスト困難の場合、ラップされたクラスに対して実装の抽出やインターフェースの抽出を行う必要がある。 3. ラップするクラスに新しい処理を行うメソッドを、テスト駆動開発によって作成する。もう一つメソッドを作成して、そこから新しいメソッドとラップされたクラスにある元のメソッドを呼び出す。 4. 新しい振る舞いを有効にしたい場所で、ラップのインスタンスを生成する。 ## 第7章 いつまで経っても変更作業が終わりません ### 7.1 理解すること 変更作業にかかる時間は様々だが、コード量が多いほど、変更内容の理解に時間がかかってしまう。 ただ、小さく理解しやすく適切な名前のついたコードでは、変更内容を理解してからの変更がスムーズである。 一方レガシーコードでは、理解も変更も難しいものになることが多い。 ### 7.2 遅延時間 遅延時間とは、変更から結果を得るまでにかかる時間のことである。 これらの遅延時間は、多くの言語で10秒以内、優秀なチームは5秒以内に完了する。そのために必要なことは、システムのすべてのクラスやモジュールを他のクラスやモジュールとは独立して、各テストハーネスでコンパイルできるようにすることである。 ### 7.3 依存関係 依存関係は問題になることがあるが、幸いにも排除できる。 多くの場合、テストハーネスで必要なクラスのインスタンス化を行うことである。こうすることで、編集→コンパイル→リンク→テストにかかる時間が非常に短くなる。 だが、巨大なクラスがあったり、あまりにも大きな依存関係を持つクラスの場合、難しい場合もある。この場合は絞り込み点(第12章参照)の活用が役立つ。 #### 7.3.1 ビルドの依存関係 ひとかたまりのクラス群のビルドは、クラスをテストで使う事で簡単にチェックできる。ただ、インスタンス化できたクラスに依存するすべてのクラスは、再ビルドする際に再コンパイルが必要である。 この時間を最小限にするために、外部から使用されているクラスに対して、インターフェースの抽出や実装の抽出を行い、外部呼出しを切り分ける方法が有効である。 #### 7.3.2 インターフェースの有用性 インターフェースの活用は、以下のような大きなメリットをもたらす。 * **インターフェースへの変更がクラスの変更に比べて稀なため、再コンパイルの時間を減らせる。** * **インターフェースを利用するコードに影響を及ぼすこと無くインターフェースの実装クラスを編集できる。** * 他からの呼出しをインターフェース経由にすると、インターフェースの分だけファイルが増えたため、システム全体の再ビルドにかかる時間は増えるが、**必要なものだけ再コンパイルする際のビルド時間を大幅に減らすことができる。** ## 第8章 どうやって機能を追加すれば良いのでしょうか 第6章のスプラウトやラップを行う手法は効果的だが、課題は残る。 * 既存のコードを大きく変更しない→しばらく放置されてしまう。 * 追加したコードとの重複箇所が生まれてしまう可能性が高い。 * 変更の効果が現れないことを恐れたり、無理だと諦めたりしてしまう。 こうした状態にならないように立ち向かうための手法を紹介する。 ### 8.1 テスト駆動開発(TDD) 初めにテストを作り、テストを通るようにコードを作り、それをリファクタリングする。インクリメンタルに開発を行う、おなじみの手法である。 コードを書くか、リファクタリングを行うかのどちらか一方のみを行うことは、レガシーコードの場合、古いコードの独立性を保ちながら追加できるため、特に価値がある。 ### 8.2 差分プログラミング オブジェクト指向の場合、継承関係を用いて、クラスを直接修正せずに新しい機能を取り入れる、差分プログラミングと呼ばれる方法がある。 継承関係が盛んに取り入れられた時代に行われていた古い手法だが、一時的に継承関係を作る分には全く問題ない。 継承関係の問題点は、1度に複数の子クラスの機能を使うことができないことである。この場合でも、テストを通じて適切にリファクタリングを行うことで解決できる。 但し、差分プログラミングを利用する場合、いくつか注意事項がある。 例としてLiskovの置き換え原則を守ることが挙げられる。 #### 8.2.1 Liskovの置き換え原則 サブクラスに含まれるオブジェクトは、**どのような場合でもスーパークラスのオブジェクトの代わりに使えなければならない。** クラスの利用者がサブクラスのオブジェクトだと意識することなく使えなければならない。 この原則を守るためには、具象メソッドのオーバーライドは避けるべきである。 実装を表す具象メソッドは原則サブクラスに移し、スーパークラスには抽象メソッドのみを集約すべきである。 こうした整理された階層は「正規化された階層」となる。 ###### 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