レガシーコード改善
仕様が失われたレガシーコードに安全にテストを追加し、少しずつ改善していくアプローチを学ぶ
レガシーコードとは何か
「レガシーコード」と聞くと単に古いコードを思い浮かべがちですが、ソフトウェア工学者の Michael Feathersは著書『Working Effectively with Legacy Code』の中で、「レガシーコードとはテストのないコードである」と定義しました。書かれてから日が浅くても、テストがなく安全に変更できないコードはレガシーコードと言えます。
テストがないコードの本質的な問題は、「このコードを変更しても既存の動作が壊れていないか」を 機械的に確認する手段がないことです。そのため変更のたびに手動での動作確認に頼るしかなく、 変更に対して臆病にならざるを得ません。
安全網を作る: 特性テスト(Characterization Test)
仕様書が失われていたり、そもそも仕様通りに動いているか誰も断言できないコードに対しては、 「正しい動作」を新たに定義するのではなく、現在の実際の振る舞いをそのまま記録するテスト(特性テスト)を先に書きます。
// 特性テストの考え方
// 「この関数は正しいはず」ではなく「今こう動いている」を記録する
test("calculateDiscount: 現状の挙動を記録する", () => {
// 既存コードに様々な入力を与え、現状の出力をそのままテストとして固定する
expect(calculateDiscount({ amount: 10000, memberYears: 3 })).toBe(1000);
expect(calculateDiscount({ amount: 10000, memberYears: 0 })).toBe(0);
expect(calculateDiscount({ amount: -500, memberYears: 3 })).toBe(0); // 一見バグに見える挙動も一旦そのまま記録
});一見おかしな挙動もまず記録する
特性テストの段階では、「バグに見える挙動」も含めていったんそのまま固定します。 それが意図した仕様なのかバグなのかの判断は、安全網ができてから関係者と確認し、 必要であれば別途修正します。安全網がないまま「正しさ」を推測して書き換えるのは危険です。
少しずつ改善する: ボーイスカウトルールとストラングラーパターン
レガシーコード全体を一気に書き直そうとすると、期間もリスクも膨らみます。 代わりに次の2つの考え方で、少しずつ改善を進めます。
ボーイスカウトルール
「来た時よりも少しきれいにして帰る」という原則。ある機能を変更するついでに、 その周辺のテストや可読性を少しずつ改善していく。
ストラングラーパターン
古いシステムを一度に置き換えるのではなく、新しい実装を古い実装の隣に少しずつ作り、 機能単位で新実装へ切り替えていくことで、最終的に古い実装を安全に取り除く手法。
安全なリファクタリングの規律
安全網ができた後の改善作業では、以下の規律を守ることでリスクを抑えられます。
- 一度に複数の変更を混ぜず、小さなステップに分けて進める
- 各ステップの後にテストを実行し、振る舞いが変わっていないことを確認してからコミットする
- 「振る舞いを変えないリファクタリング」と「振る舞いを変える機能追加・バグ修正」を同じコミットに混ぜない
ポイント
- レガシーコードとは「古いコード」ではなく「テストがなく安全に変更できないコード」
- 手を入れる前に、現状の振る舞いをそのまま記録する特性テストで安全網を作る
- 一気に書き直さず、ボーイスカウトルールやストラングラーパターンで少しずつ改善する
- 小さなステップとテストの確認を繰り返し、リファクタリングと機能変更を混ぜない
確認クイズ
1 / 3Michael Feathersが提示したレガシーコードの定義として正しいものはどれか?