# レガシーコード改善ガイド 第1部 ## 第1章 ソフトウェアの変更 ### 1.1 ソフトウェア変更の4つの理由 1. 要件の追加 2. バグの修正 3. 設計の改善 4. リソース利用の最適化 #### 1.1.1 要件の追加とバグの修正 要件の追加とバグの修正は、案件として似通っているものもあるが、その場合でも両者は区別して管理する必要がある会社が多い。 区別するべき本質はそこではなく、「**振る舞いが変化したか**」である。振る舞いが追加されればユーザは喜ぶが、ユーザが求める振る舞いを無くしてしまうと信頼は失われてしまう。 単に追加の振る舞いを表す関数を作っても、呼び出されなければ意味が無い。**純粋にコードの追加だけで振る舞いを追加することは難しく、多くはコードの変更を伴うことになる。** #### 1.1.2 設計の改善 **振る舞いを変えずに設計を変えることをリファクタリングと呼ぶ。** リファクタリングには、振る舞いの変化を確認するためのテストが必要である。 なお、リファクタリング時には機能の変更は行ってはならない。 #### 1.1.3 リソース利用の最適化 こちらはリファクタリングと異なり、プログラムが使用しているメモリなどのリソースに着目して最適化を行う。 #### 1.1.4 4つの変更理由のまとめ * リファクタリングとリソース利用の最適化は、機能を変えない点で等しい。 * 要件を追加しても、バグの修正以外は既存機能は変わらない。また、バグの修正時の機能変更はごくわずかである。 こうした整理は、何に集中すべきかわかりやすくなるメリットがある一方、どうすれば残りの振る舞いを変えずに済ませられるかを考えなければならないデメリットがある。 **変更による振る舞いへの影響を把握しておくことで、変更に専念できる。** ### 1.2 危険な変更 変更のリスクを緩和するには次の3点を考慮する必要がある。 1. どんな変更を行う必要があるのか 2. 変更が正しく行われたとどうすれば確認できるか 3. 何も壊していないとどうすれば確認できるか このリスクを恐れて変更しない方法もあるが、たちまちシステムが煩雑になりバグの温床と化してしまう。 **頻繁な変更や分割は、こうした煩雑化を防ぎ、日々の変更作業を簡単にする効果もある。** ## 第2章 フィードバックを得ながらの作業 システムの変更方法は「**編集して祈る**」と、「**保護して変更する**」の2つに分かれる。前者が業界標準になっている現状がある。 「**編集して祈る**」パターンは、最初に綿密に注意深く計画を立てるため、一見すると良い方法に思われる。しかし、**最初の計画が必ずしも正しいとは限らない**。言い方を変えると「**最初の計画に拘束される**」ことになり、計画に無いエラーや急な追加があった場合に対応できなくなる。 「**保護して変更する**」パターンは、最初に転落防止の網、すなわち**テストを作成してソフトウェアを変更する**方法で、仮に変更が失敗した場合でも、**容易に発見できる。** また、**テストからフィードバックをもらえる**ので**修正も容易**である。 従来の開発が終わってからのテストは、**フィードバックをもらえるまでの期間が長くなり、問題の発見が遅れやすい。** **ソフトウェア万力**とは、変更のためのテストを書き、振る舞いの大部分を万力のように固定することで、**1つの変更に対して1つの振る舞いしか変更していないことを確認できる。** ### 2.1 単体テストとは 単体テストとは、**システムの振る舞いの面から見た最も原始的な単位でテストを行うものである。** 手続き型のコードでは関数、オブジェクト指向型のコードではクラスが単位となる。 単体テストと大規模な複合テストの違いとして、以下の点が挙げられる。 * 単体テストを行えば、エラー箇所を容易に特定できる。大規模なテストでは発見するのに時間がかかる。 * 単体テストは実行時間が短く、手軽にテストを行える。実行時間が長い大規模テストは、そもそも実行したくなくなってしまう。 * カバレッジ測定ツールを用いることで、カバレッジが測定できるが、大規模コードではコードと値の関係性が分かりにくくなる。 #### 優れた単体テストとは 1. 実行が早い。:目安として0.1秒未満 2. 問題個所の特定がしやすい。 3. 単体テストではないものが単体テストとなっている場合もある。これらとは別に単体テストを書くべきである。 * DBとの通信を行う * ネットワークを介したやり取りを行う * ファイルシステムにアクセスする * 実行に特別な環境を必要とする ### 2.2 上位レベルでのテスト 上位レベルでのテストを使えば、複数のクラスの振る舞いをまとめて確認することができる。 ### 2.3 テストによる保護 レガシーコードにおいて、どのようにテストを書けばよいのだろうか。 例えばDB等との直接の依存関係があるクラスだと、クラスもテストも扱いづらいものになってしまう。 コードを変更するためのテストを整備するために、コードを変更しなければならないことを「レガシーコードのジレンマ」と呼ぶ。 こうした場合のリファクタリングは、常に保守的でなければならない。間違いを引き起こす可能性がある場合は、保守的に行うのがセオリーである。 ### 2.4 レガシーコードの変更手順 #### 2.4.1 変更点を洗い出す 変更を行う必要のある場所は、アーキテクチャによって微妙に異なる。 第16章、17章を参照。 #### 2.4.2 テストを書く場所を見つける レガシーコードでは困難な場合が多い。 第11章、12章を参照。 #### 2.4.3 依存関係を排除する 大抵の場合、テストの最大の障害になるのが依存関係である。 テストでオブジェクトをインスタンス化しにくい、あるいはテストでメソッドを実行しにくい。 第10, 23, 25章参照。 #### 2.4.4 テストを書く レガシーコード用と新規コード用では違いがある。 第13章参照。 #### 2.4.5 変更とリファクタリングを行う テスト駆動開発によって行うべきである。 第8章, 20~22章を参照。 ## 第3章 検出と分離 理想はクラスの変更作業を始める前に何もする必要がないことである。しかし、大抵は困難な作業になる。クラス間に依存関係があればあるほど厳しくなる。 単体テストを行うために、しばしば依存関係を排除する必要がある。 またテストを作成するためには、対象とするクラスから他のクラスへの影響を把握する必要がある。 依存関係を排除する必要がある理由として、検出と分離がある。 * 検出:コードの計算した値にアクセスできない時に、それを検出するために依存関係を排除する。 * 分離:コードがテストで実行できない時、分離するために依存関係を排除する。 #### 3.1.1 協調クラスの擬装 単体テストを行う場合、他のコードに対する依存関係を排除する必要がある。ただ、依存関係である他のコードこそが、作業の影響を簡単に検出できる唯一の場所になる場合が多い。もしそこのコードを別のコードに置き換えることができれば、テストが可能になる。 オブジェクト指向ではこのような他の協調クラスになりすますコードを「擬装オブジェクト」という。 #### 3.1.2 擬装オブジェクトの2つの側面 具体的には、本来表すべきコードと共通する振る舞いをインターフェースに移し、それを継承した形で本来表すべきコードと別に擬装オブジェクトを作成する。 この擬装オブジェクトには2つの側面がある。 **通常の呼び出しクラス**からは、**インターフェースのオブジェクト名**でインスタンス化を行うため、**インターフェースのメソッドのみ**が実行できる。 その一方で、**テスト用の呼び出しクラス**からは、**擬装オブジェクトのオブジェクト名**でインスタンス化を行うため、インターフェースからオーバーライドしたメソッドに加えて、**擬装オブジェクト独自のメソッドを実行できる。** #### 3.1.3 擬装のエッセンス オブジェクト指向では、3.2で表した形で、オブジェクト指向以外では代替関数を用いることで、擬装を表現できる。 #### 3.1.4 モックオブジェクト 多くの擬装オブジェクトが必要な場合は、モックオブジェクトを用いる。 モックオブジェクトは、想定される呼び出しを事前に設定し、メソッド呼び出し後に検証を指示して呼び出しが実際に行われたかを確認する。 ## 第4章 接合モデル ### 4.1 巨大な用紙の文字の羅列 既存のコードをテストを書こうとするとき、いかに既存のコードがテストに向いていないかに気づく。 簡単にテストできるようにするには、開発と並行してテストを作成するか、テストの容易性に配慮した設計にするしかない。 再利用可能な小さな部分から構成されるプログラムを書くべきと言われるが、実際に再利用される機会はさほど多くない。むしろ部分的な依存性があり困難である。 ### 4.2 接合部 **接合部**とは、**その場所を直接編集しなくても、プログラムの振る舞いを変えることができる部分**のことである。 ソフトウェアを接合部という観点から見ることで、コードに既に含まれている依存関係を排除するための手掛かりを見出すことが可能になる。 接合部で振る舞いを置き換えることができれば、テスト時に依存関係を取り除ける。 ### 4.3 接合部の種類 #### 4.3.1 プリプロセッサ接合部 C言語ではコンパイルを行う前に、プリプロセッサを用いる。 そのプリプロセッサが提供する接合部をチェックする。 依存関係のある個所を、プリプロセッサを通る時に依存関係のある形に置き換わるように代替関数を定義しておくことで、プリプロセッサを通る前の依存関係を排除できる。 なお、この呼びだしの置き換えは、あらかじめ別の場所で振る舞いを変更する必要がある。この別の場所のことを許容点という。 #### 4.3.2 リンク接合部 コンパイラはコードの中間表現を出力するが、この中間表現には他のファイルに格納されたコードへの呼び出し(リンカ)が含まれている。 import文で他のクラスをインポートする際に、Javaでは**CLASSPATH環境変数**を用いて探す場所を指定する。 この指定場所にあるクラスを、**独自の同名クラスに置き換える**ことが可能である。 依存関係の排除には非常に便利である。 **接合部はインスタンス定義での呼び出し部分、許容点はCLASSPATHとなる。** リンク接合部は分離のためのものであるが、検出も可能である。但し、仕組みがかなり複雑に場合があるため、テスト環境と本番環境との違いを明確にする必要がある。 #### 4.3.3 オブジェクト接合部 オブジェクト指向プログラミングで利用できるもので一番役立つ。 オブジェクト指向における呼び出しが、実際にどのメソッドが実行されるか定義できていない場合、その呼び出しが接合部となる。 許容点は関数の引数となる場合が多い。 **依存関係を直接修正するのではなく、間接的に行う方法は、保守的な変更が求められるレガシーコードにおいては有効である。** ## 第5章 ツール ### 5.1 自動リファクタリングツール **自動リファクタリングツール**があれば、**大幅に時間を節約**することが可能である。ただ、ツールによって**サポートするリファクタリングレベルはまちまち**なので注意。 また、**リファクタリングは変更により振る舞いが変わらないことが大前提**である。これらをチェックしないツールもあるので、**注意深く選定する**必要がある。 テストが不要の場合もあるが、ツールによっては明らかに振る舞いが変わってしまうものもあるため、やはりテストは必要である。 ### 5.2 モックオブジェクト 第3章の検出による依存関係の排除を、比較的簡単に行えるオブジェクトのこと。 これらのモックオブジェクトには、無償で提供されているものもある。 ### 5.3 単体テストハーネス いわゆるテストツールである。 無償の代表的なテストツールにxUnitがある。 xUnitは単体テストフレームワーク用に構築されており、言語ごとに提供されている。Javaの場合、JUnitとなる。 xUnitの利点は以下の通りである。 * 自分が使っている言語でテストを書ける。 * 全てのテストが独立して走る。 * テストをスイートにまとめ、要求に応じて実行・再実行できる。 #### 5.3.1 JUnit Java用単体テストフレームワーク。 最新版はJUnit5(jupiter) 以下JUnit5を用いたテストクラスの例を示す。 ```java= public class calculateTest{ private Calculator calculator; private static tax; //@BeforeAll:定数の読み込み(1度限り) @BeforeAll static void setUpStatic(){ tax = 1.1; } //@BeforeEach:テストごとに読み込み @BeforeEach public void setUp(){ calculator = new Calculator(); } //@Test:テスト本体 //doAddition(足される数, 足す数)の結果をテストする @Test public void doAdditionTest(){ int result = calculator.doAddition(1,4) assertEquals(5, result) } //getTaxIn(税抜価格, 税率)の結果をテストする @Test public void getTaxInTest(){ int result = calculator.getTaxIn(100,tax) assertEquals(110, result) } } ``` #### 5.3.2 CppUnitLite C++向けのテストフレームワーク。 #### 5.3.3 NUnit .NET向けのテストフレームワーク。 xUnit系のため、JUnitと操作方法がほぼ同一である。 #### 5.3.4 その他xUnit系フレームワーク その他にも、多くの言語やプラットフォームにxUnit系のフレームワークが実装されている。 ### 5.4 一般的なテストフレームワーク #### 5.4.1 FIT(Framework for Integrated Test) 統合テスト向けのフレームワーク。 システムに関する文書を書き、その中にシステムの入力と出力について記述した表を含め、HTMLとして保存することで、テストを実行できる。 表のセルの色分けによって成功と失敗を表す。 このフレームワークは、プログラマーと、仕様化の担当者(プロダクトオーナー等)をつなぎ、コミュニケーションを促進できるメリットがある。 #### 5.4.2 Fitnesse wiki上に構築されたFITで、階層構造のWebページの作成が可能となる。 テストテーブルを含んだページは、個々に実行することもまとめて実行することもできる。 ###### 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