保守 レッスン3

レガシーコード改善

仕様が失われたレガシーコードに安全にテストを追加し、少しずつ改善していくアプローチを学ぶ

レガシーコードとは何か

「レガシーコード」と聞くと単に古いコードを思い浮かべがちですが、ソフトウェア工学者の 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 / 3

Michael Feathersが提示したレガシーコードの定義として正しいものはどれか?