【ITニュース解説】Replay Captured Inputs, Then Move One Function: A Flake-Trial Gate for Messy Refactors
2026年10月17日に「Dev.to」が公開したITニュース「Replay Captured Inputs, Then Move One Function: A Flake-Trial Gate for Messy Refactors」について初心者にもわかりやすく解説しています。
ITニュース概要
コード改修(リファクタリング)でバグを出さない手法。実際の入力データを記録し、繰り返し実行して出力が常に同じか検証する。これにより、テストで検出しにくい不確定な動き(非決定性)を特定できる。不確定要素をなくした上で機能を少しずつ移し、本番環境でのエラーを未然に防ぎ、安全な変更を可能にする。
ITニュース解説
システムを改修する際、特にコードの構造を改善する「リファクタリング」は、予期せぬ不具合を引き起こすことがある。多くの場合、開発者が書くテストは、開発者が「こう動くはず」と推測した入力データに基づいて行われる。しかし、実際のシステムが稼働する本番環境では、多様な入力データが送られてくるため、開発者が推測した入力だけでは不十分なケースが多い。これにより、テストは通るのに本番で問題が発生するという状況が起こりかねない。特に、複雑で長期間運用されているシステムでは、システムの初期設定、データの読み込み順序、時刻の取得方法など、さまざまな要因が絡み合い、テストの再現性を阻害することがある。本記事では、このような問題を解決し、より安全にリファクタリングを行うための具体的な手法を提案する。
この手法では、まず実際にシステムに入力されたデータを記録する。次に、その記録されたデータを使ってテストが常に同じ結果を出すか(安定しているか)を徹底的に確認する。そして、安定性が証明された上で、最小限の変更(一つの関数を移動するなど)のみを行う。この手順を踏むことで、不確実性を排除し、安全にコードを変更することが可能となる。
リファクタリング対象の関数を決めたら、その関数を「レコーダー」と呼ばれる仕組みで囲む。このレコーダーは、対象関数が実際に呼び出された際の引数やキーワード引数といった入力データを、逐一JSONL形式(各行がJSONオブジェクトのテキストファイル)でファイルに書き出す。このファイルは後でテストの入力として利用される「カセットファイル」である。重要なのは、この段階では関数の「出力」ではなく「入力」のみを記録する点である。既存のテストスイートを実行したり、本番に近い環境で一時的にシステムを動かしたりして、実際の入力データを収集する。記録するデータは、JSON形式で扱えるものである必要がある。また、記録されたデータには、顧客情報や認証トークンといった機密性の高い情報が含まれる可能性があるため、コミットする前にこれらを確実に削除し、匿名化する「サニタイズ」作業が必須である。
カセットファイルに記録された各入力データは、一つずつのテストケースとして再利用される。例えば、Pythonのpytestフレームワークでは、記録されたJSONLファイルの各行を読み込み、それぞれの入力で対象関数を呼び出すパラメーター化されたテストを作成する。このテストでは、関数が生成した出力結果を「ゴールデンファイル」(golden.json)と呼ばれる事前に定義された正しい出力と比較する。ゴールデンファイルは、通常、初めてテストをクリーンに実行した際に自動的に生成され、以降は手動で編集してはならない。
ゴールデンファイルが一度生成され、テストが一度成功しただけでは、そのテストスイートが本当に安定しているとは限らない。多くのシステムは、同じ入力に対して常に同じ出力を返すとは限らない「非決定性」を抱えている場合がある。これを検出するために、「Flake Trial」(不安定性試行)と呼ばれる重要なステップを実行する。これは、作成したテストスイートを同じカセットファイルを使って複数回(例えば5回)繰り返し実行し、それぞれの実行で得られた最終的な結果の要約を比較するというものである。もし、すべての実行で要約結果が完全に一致すれば、そのテストスイートは十分に安定していると判断でき、次のステップに進める。しかし、もし一つでも異なる結果の要約が出た場合、それはシステムが非決定的な振る舞いをしていることを意味する。非決定性の原因としては、辞書やセットの要素の順序が実行ごとに変わる、プログラムが実行時の時刻やタイムゾーン、ロケールに依存している、あるいはネットワークアクセスやデータベースの読み込み、ファイルシステムのスキャンといった外部の状態に依存しているなどが挙げられる。この段階で非決定性を発見し修正することは、本番環境で予期せぬ問題が発生するのを防ぐ上で極めて重要である。
Flake Trialによって非決定性が明らかになった場合、その原因に応じて適切な修正を行う必要がある。比較的「安価」に修正できる非決定性としては、タイムゾーン、ロケール設定、ハッシュ関数のシード値、乱数発生器のシード値などがある。これらは、環境変数を固定したり、テストフィクスチャを使ってテスト実行時に一時的に値を固定したりすることで、簡単に決定論的な状態にできる。一方、ネットワーク通信、データベースからの読み書き、ファイルシステムのスキャンといった外部リソースに依存する非決定性は「高価」な原因とみなされる。Flake Trialでこれらの高価な非決定性が示された場合、安全なリファクタリングを行うための最小限の変更は、関数を移動することではなく、対象関数とその外部リソースの間に「アダプター」(隔離層)を導入し、外部依存を制御可能にすることである。これにより、テストが外部環境に影響されずに安定して実行できるようになるまで、本来のリファクタリングは一時的に待機する。
テストスイートの安定性が確認され、非決定性が適切に処理されたら、いよいよリファクタリングの本題である関数の移動を行う。このステップでの変更は、可能な限り最小限に留めることが重要である。例えば、一つの関数を新しいモジュールやディレクトリに移動する。重要なのは、関数を移動した後も、古い場所から新しい場所へ参照を転送する「再エクスポート」の仕組みを残すことだ。これにより、この関数を呼び出している他のコード(呼び出し元)は、直ちに変更する必要がない。つまり、関数の移動と呼び出し元の更新を異なるコミットに分けることができ、変更の影響範囲を局所化できる。結果として、カセットファイルに記録された入力データは、関数が移動したことを知ることなく、以前と同じようにテストを実行できる。
関数を移動した後、再度テストスイートを実行し、移動前と移動後のテスト結果の要約を比較する。具体的には、移動前にテストを実行してその結果をファイルに保存し、関数を移動した後に再度テストを実行して結果をファイルに保存する。これら二つのファイルの差分を比較し、完全に一致することを確認する。差分がなければ、関数の移動によってテストの振る舞いが変わっていない、つまり変更が安全であると判断できる。この「差分なし」という結果は、コミットを承認するための重要な基準となる。しかし、これは「必要条件」であって「十分条件」ではないため、最終的な変更が適切であるかを確認するためには、引き続き人間によるコードレビューが不可欠である。
Flake Trialで不安定性が検出された場合、それは通常、以下の三つのクラスに分類できる。一つ目は「順序のずれ」で、リストやセットの要素の順序が実行ごとに変わる場合に発生する。出力データをシリアライズする際にソートを適用することで解決できる。二つ目は「時刻とロケールのずれ」で、出力にタイムスタンプや地域に特化した書式設定が含まれる場合に生じる。テストフィクスチャで時刻やロケールを固定するか、時刻情報を引数として明示的に渡すことで制御できる。三つ目は「環境のずれ」で、環境変数、カレントディレクトリ、ディスク上のキャッシュといった実行環境に依存して出力が変わる場合である。グローバルな状態をパッチせず、依存するパスやマッピングを関数に注入する形で修正することが推奨される。これらの問題を誤って分類し、不適切な修正を施すと、開発環境ではテストが成功するのに、継続的インテグレーション(CI)環境では失敗するといった、信頼できないテストスイートを生み出すことになり、結果として開発者がテストを無視し始めるという最悪の状況を招くため、注意が必要である。
この手法は非常に強力だが、常に万能というわけではない。既に高品質で網羅的なテストスイートを持つ関数や、呼び出し元が少なく変更の影響範囲が明確な関数に対しては、カセットファイルの作成やFlake Trialの実行は過剰なオーバーヘッドとなるため、適用を避けるべきである。また、顧客データなど機密性の高い情報を扱う関数で、適切なサニタイズ機構が確立されていない場合は、実際の入力を記録すること自体がリスクとなるため、使用してはならない。さらに、この手法は「既存の振る舞いが変更後に維持されているか」を保証するものであり、「既存の振る舞いがそもそも正しかったか」を証明するものではない。したがって、設計レビューの代替にはなり得ないことを理解しておく必要がある。最も重要なのは、まず実際の入力をキャプチャし、その入力に対するシステムが安定していることを徹底的に検証し、それから最小限のコード変更を行い、最後に安定した検証結果によってコミットをゲートするという手順である。特に、多くの人が見過ごしがちなFlake Trialのステップは、リファクタリングが本番環境で問題を引き起こす前に、システムの非決定性を発見する最も安価で効果的な手段となる。