# レガシーコード改善ガイド:第2部④ ## 第18章 自分のテストコードが邪魔になっています ### 18.1 クラスの命名規約 クラスの命名については、**統一された規約を適用する**ことが望ましい。 以下は、関連する全クラスを同一ディレクトリ内に置く場合の命名規約例である。 * テストクラスは、テスト対象クラスの名前の**後ろにTestをつける**のが望ましい。アルファベット順でテスト対象クラスの下にテストクラスが来るようになる。 * 擬装クラスは、テスト対象クラスの名前の**前にFakeをつける**のが望ましい。アルファベット順で擬装クラスが1か所にまとまる。 * テスト用サブクラスは、テスト対象クラスの名前の**前にTestingをつける**のが望ましい。アルファベット順でテスト用サブクラスが1か所にまとまる。 ### 18.2 テストコードの配置 本番コードと同一ディレクトリにテストコードを置くべきかどうかは、いくつかの点を考慮したうえで決める。 #### ファイルサイズの制約 ファイルサイズがテストコードも含めた状態で許容できるならそのままでも良いが、商用製品のソフトウェア等、第三者のコンピューターで動かす場合は、ファイルサイズの制約を受ける。こうした場合は、なるべく本番コードとテストコードは分離すべきである。 但し、コードがどれだけ参照しづらくなるかをチェックする必要がある。 Javaでは本番用とテスト用で同一名称別配置のディレクトリを、1つのパッケージとして表せるため、参照の制約の考慮は不要である。しかし、その他の言語ではこの限りではないため注意が必要である。 分離の代替案として、スクリプトやビルドの設定を用いて、本番用のファイルからテストコードを削除する方法がある。 ## 第19章 私のプロジェクトはオブジェクト指向ではありませんが、どうすれば変更できるでしょうか。 C言語やCOBOL、BASIC等の手続き型言語については、レガシーコードにおいては特に厄介な存在となる。単体テストを作成するために可能なことは限られており、システムにパッチを当てて祈るぐらいしか簡単な対処法は無い。 そもそもこうしたジレンマが生じる原因は、手続き型言語のレガシーコードに接合部が無く、依存関係の排除が非常に難しいためである。 ### 19.1 簡単なケース ### 19.2 困難なケース しばしば最適な戦略は、変更を行う前にひとまとまりの大きなコードをテストで保護して、フィードバックを得ることである。これには11章, 12章で紹介した、**影響スケッチによる分析**、及び**絞り込み点**を用いる方法が役立つ。**絞り込み点でリンク接合部(又はプリプロセッサ接合部)を用いて**依存関係を排除する。 * **リンク接合部**:実際の動作を持たない**擬装関数にリンク**させて、依存関係を排除する。**関数の定義は1つしか置き換えれない**ため、検出や戻り値にバリエーションを持たせたい場合には不向きである。 * **プリプロセッサ接合部**:C言語のプリプロセッサの特性を生かし、**include文**でテストコードを分離させる。テストコード単体で呼び出し、動作が可能である。 ### 19.3 新しい振る舞いの追加 新しい振る舞いを追加するには、**テスト駆動開発**を行うのが最善である。 テスト駆動開発を行うにおいては、考え方を変えて、純粋な処理を別の関数に抽出することで、問題の多い依存関係を排除することができる。最終的にはラッパー関数が出来上がり、処理ロジックと依存関係を結びつけている。依存関係が広範囲でない場合は効果を発揮する。 #### 多くの外部呼出しがある場合 多くの外部呼出しがある場合、**呼び出し順が非常に重要**となる。 こうした場合、テストをあきらめることもあるが、上位レベルでテストが可能である可能性もある。 C言語等では、**関数ポインタを接合部として用いる**ことで、振る舞いを本番とテストで変えることができる。 ### 19.4 オブジェクト指向言語の長所の活用 オブジェクト指向言語の場合には、**オブジェクト接合部**が利用可能である。 オブジェクト接合部には、以下の優れた特性がある。 * コードの中で**簡単に見つけることができる。** * コードを**より小さく、より理解しやすい部品**に分解できる。 * **より高い柔軟性**を提供する。導入した接合部はテスト以外にもソフトウェア拡張にも役立つ。 #### 手順 1. まずは**オブジェクト指向言語としてコンパイル**してみる。C言語ならC++,その他の言語においてもオブジェクト指向の拡張機能を利用する。 2. 依存関係のあるコードをクラスでラップする。 3. 定義したクラス用に新しいソースファイルを作成する。この際、**シグネチャの維持**を適用する。 4. 関数をコンパイルできるようにする。 5. グローバル参照のカプセル化を利用して、本番コードとテストコードで使用するオブジェクトを分ける。 6. コンストラクタのパラメータ化を適用して、新しいクラスのものを使うようにする。 ### 19.5 すべてはオブジェクト指向 以上のように、オブジェクト指向機能を持つ手続き型言語においては、**オブジェクト指向の特性を生かした手法**を実施できる。 本格的にオブジェクト指向を導入しているわけではないが、**より優れたオブジェクト指向設計に向けて改良を行う余地はある。** ## 第20章 このクラスは大きすぎて、もうこれ以上大きくしたくありません 機能を追加する際、簡単なのは既存のクラスへの追加である。しかし、機能が次々と追加されると、クラスが肥大化してしまう。 巨大なクラスでは、理解するのが困難なだけでなく、複数責務を同じクラスで持っている場合に変更の際の作業計画に混乱をきたし、依存関係がひどくテストしずらいコードになるという問題がある。 こうした大きなクラスに取り組むときの最初の課題は「状況をより悪化させないためにどう作業すべきか」である。第6章の**スプラウトクラス**、**スプラウトメソッド**の手法を用いることで、テスト可能な追加コードを容易に加えられる。 大きなクラスを小さくする特効薬はリファクタリングである。その際はクラスの単一責務の原則を指針とする。(単一責務の原則についてはクリーンコードを参照) 実際に大きなクラスを扱う上で重要なことは、**異なる責務を識別し、それらの責務をより適切な場所に移していく方法を見つける**ことである。 ### 20.1 責務の把握 責務を識別するには、「なぜこのメソッドがここにあるのか」「このメソッドはクラスのために何をしているのか」を考える必要がある。その上で、メソッドをまとめてグループに分類していく。これを「メソッド分類法」と呼んでいる。 #### メソッド分類法の経験則 1. **名前が似ているメソッドを探す。** 全てのメソッドをアクセス属性と共に書き出し、一緒にできそうなものを探す。 2. **隠蔽されたメソッドを調べる。** privateやprotectedのメソッドが多いクラスは、別のクラスを取り出せることを示唆している場合がある。 3. **変更可能な決定事項を探す。** 既存の決定事項(DBアクセスや他のオブジェクトの利用)に対して、何らかの方法としてハードコードされているものがあるかチェックし、それを変更できるかを考える。 4. **内部的な関係を探す。** インスタンス変数とメソッドの関係(まとまり)を探す。あるインスタンス変数は、一部のメソッドでしか使われていない可能性がある。→機能スケッチの項目も参照。 5. **クラスの責務を一言で説明してみる。** 単一責務の原則を守っていれば、一言で言えるはずである。また、複数ある場合でもその中で主要な責務が見つかれば、抽出するきっかけになる。 6. 他のすべての経験則が役に立たない場合、**試行リファクタリング(第16章参照)を行う。** 7. **現在の作業に集中する。** ひとまず現在の作業に集中し、その中で実現方法が見つかれば責務を識別できた可能性がある。 #### 機能スケッチ **機能スケッチ**とは、メソッドとインスタンス変数が、クラス内のどのメソッドで使われているかを表したものである。変更による影響を書き出す影響スケッチとは逆で、インスタンス変数の影響を受ける側のメソッドから変数へと→を引く。機能スケッチでは、**大きなまとまり同士を結びつける小さな線となる部分が絞り込み点となる。** 1. インスタンス変数をそれぞれ円の形で書き出す。 2. インスタンス変数を使うメソッドを円の形で書き出し、インスタンス変数に向けて→を引く。 3. インスタンス変数を使う全メソッドを書き出し、使用する変数に向けて→を引くことを繰り返す。 4. 描き上げた図から、同じメソッドから呼ばれている変数及び呼び出すメソッドのまとまりを探す。そのまとまりを、まるごと1つのクラスに抽出することができる。 5. 抽出した新しいクラスが、他のクラスと異なる適切な責務を持つかを確認する。その上で適切な名前をつける。これは、抽出していない残ったクラスの名前にも適用できる。 #### 単一責務の原則の違反 単一責務の原則を違反する状態は2つある。 * **実装レベルの違反**:**1クラスに複数責務が実装されている**状態。実際にクラス内で責務が処理されているのか、他のクラスに委譲しているだけであるのかに着目する。実際に責務が処理されている場合は、クラスを抽出する。 * **インターフェースレベルでの違反**:**外部から呼び出されるインターフェースとして使うべきメソッドとそれ以外のメソッドが混ざっている**状態。呼び出し側から直接的に使えるメソッドがあるかを調べ、メソッドをインターフェースに抽出して抽象メソッドとする。**インターフェース分離の原則に基づく**ことで、**依存関係を弱め、情報の隠蔽にも役立つ。** ### 20.2 その他の技法 20.1で上げた経験則は、あくまで一つのやり方にすぎない。責務の識別を本当により良く行うための方法は、多くの文献やコードにあたり、学習することである。**多くの知見を得て技法を盗むことは、知らないコードで責務を見つける力となる。** ### 20.3 先へ進む 責務がハッキリしたら、残る問題は戦略と戦術である。 #### 20.3.1 戦略 責務が分かっても、実際にクラスを分割するには大きなリスクを伴う。 初期段階で時間がある場合にのみ、大規模なリファクタリングを行うことは有効である。 大きなクラスを分解する最適なアプローチは、責務を識別したうえで、チームの全員に責務を理解させ、必要に応じてクラスを分割することである。こうすることで、リスクが分散され、他の作業を進めることができる。 #### 20.3.2 戦術 多くのレガシーコードでは、クラスを分割するだけで精一杯である。しかしながら、**単一責務の原則を実装レベルで導入しておくと、将来的にインターフェースレベルでも実現しやすくなる。** 実際にクラスを分割できるかどうかは、**影響を受けるメソッドのテストをどれほど簡単に用意できるか**にかかっている。レガシーコードではインスタンス化が困難な場合も多いため、第9,10章の技法を用いる必要があるかもしれない。 テストを整備できるならば、**クラスの抽出**(「リファクタリング」を参照)を行い、保守的にクラスの抽出を行う。 但し、バグが発生する可能性は残る。特に継承に関するバグ(別のメソッドや変数をオーバーライドするメソッドや変数の移動によるもの)には注意が必要である。 この場合は、元のメソッドを移動せず、古いメソッドの本体を抽出して新しいメソッドを作ることで避けられる。 またインスタンス変数の処理も、間違えやすいため注意が必要である。 ### 20.4 クラスの抽出後 クラスの抽出は、たいていの場合幸先の良い一歩となる。だが、最も危険なのは色々とやりすぎてしまい、システムに関する別の見方を作り出している可能性がある点である。 現時点でのアプリケーションがちゃんと動くことは忘れないこと。 ###### 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