「このコードは、触ったらどこで壊れるか分からないので直せません。」
レガシーコードを前にすると、誰もが一度は口にしたことがある言葉ですよね。
結論から言います。リファクタリング前にやるべきことはただ一つ、**「今のコードの動作をそのまま捉えるテスト」**を先に作ることです。
コードをきれいに直す前に、今このコードが何をしているのかをまず確定させます。
今日は、実際にレガシーコードへテストを追加したときの手順を、そのまま紹介します。順番を守るだけで、「直している途中で壊れる」事故は大幅に減ります。
なぜリファクタリングより先にテストを行うのか?
まず、リファクタリングの定義を確認しましょう。
リファクタリングとは、外から見える動作を変えずに内部構造だけを改善する作業です。
ここで重要なのは、「動作を変えない」という約束です。
その約束を守れたか、何で確認するのでしょうか。テストです。
テストがなければ、「変わっていないようだ」という感覚でリリースすることになります。感覚で決済コードをリリースするなんて……想像するだけでぞっとします。
だからマイケル・フェザーズは著書『レガシーコード改善ガイド』で、こう定義しています。**「レガシーコードとはテストのないコードである」**と。
基準は古いかどうかではなく、安全網があるかどうかということです。
リファクタリング前のチェックリスト(要点)
忙しい方のために、まず順番を整理します。
- 触るコードの**範囲(境界)**を先に決める
- 現在の動作を記録する特性化テストを追加する
- テストが**緑(成功)**であることを確認する
- そのうえで小さな単位でリファクタリングする
- 各ステップでテストを再実行する
この5つを順番どおりに行うだけで、半分は成功です。以下で一つずつ説明します。
特性化テストはどう追加するのか?
最もよく聞かれる質問です。「動作が分からないのに、どんなテストを書けばいいの?」
ここでは発想を逆転させます。正解を知っていて書くのではなく、コードが現在返す結果をそのまま正解として固定するのです。
これを特性化テスト(Characterization Test)と呼びます。
方法は意外と単純です。まず適当な値を入れてテストを実行します。失敗すると「実際の値はこれでした」と教えてくれるので、その値をそのまま貼り付ければ完了です。
// 1) 実際の戻り値が分からないため、意図的に間違った値を入れる
@Test func 割引計算_現在の動作() {
let result = calcDiscount(user: user, cart: cart)
#expect(result == 0) // 失敗して実際の値を知らせる
}
// 2) 失敗メッセージに表示された実際の値(例: 1500)をそのまま固定する
// #expect(result == 1500)
これでこのテストは、「このコードはもともと1500を返す」という事実を守る番人になります。
後でリファクタリング中に誤って1200が返ると、テストが赤信号ですぐ知らせてくれます。
コードが良いか悪いかは、今は問いません。まず現在の姿を捉えることが目的だからです。
テストを追加できないコードはどうする?
レガシーコードの本当の壁はここです。DB、外部API、現在時刻のように変更できないものが関数の真ん中に埋め込まれていると、テストを実行できません。
このとき、フェザーズが提唱した**「イームズ(Seam)」**の概念が役立ちます。コードの流れを少し切り、偽の値を差し込める場所を作るのです。
最も安全な方法は、必要な1行だけを関数パラメータに切り出すことです。
たとえば関数内で直接현재시간()を呼び出しているなら、それを引数で受け取るように変更するだけです。テストから好きな時刻を渡せるようになります。
注意点が一つあります。テストを追加するためのこの最小限の変更も、できるだけ機械的かつ慎重に行ってください。まだ安全網のない部分だからです。
よくある質問(Q&A)
Q. 何%までカバレッジを埋めてから始めるべきですか?
全体を埋める必要はありません。今すぐ触る部分、その境界だけを覆えば十分です。100%は目標ではなく、罠になることが多いです。
Q. テストを追加する時間がなかったら?
そのプレッシャーは私もよく分かります。そんなときは、直す関数を一つだけ特性化テストで覆ってから始めてください。5分あれば最悪の事故を防げます。
Q. テストが汚くても大丈夫ですか?
大丈夫です。特性化テストは仮設の足場のようなものです。リファクタリングが終わってコードがきれいになったら、テストも自然に整えればよいのです。
結局、順番は一つにまとまります。捉える(テスト)→直す(リファクタリング)→確認する。
レガシーコードが怖いのは、コードが悪いからではなく安全網がないからです。今日は関数を一つだけ選び、特性化テストを追加してみてください。その先は、ずっと気楽に進められます。応援しています!

