ヘルパーがミリ秒の代わりに秒を返すようになっても、引数と戻り値の型は同じに見えます。一方の呼び出し元は更新しなくなり、他方は正しく並べ替え続け、CSV の列は千分の一になります。参照一覧だけでは、どの結果が起きたかは分かりません。
セッションを一覧表示し、更新時期を判断し、経過時間を出力する小さなアプリを考えます。固定入力と変更していないアサーションで JavaScript を実行した学習用の例です。サンプル一式で結果を再現できます。
呼び出し元を列挙する前に、変更を明確にする
elapsed.mjs にヘルパーの両方の版を置き、同じ利用側をどちらでも実行できるようにしています。
export function elapsedBefore(nowMs, startedMs) {
return nowMs - startedMs;
}
export function elapsedAfter(nowMs, startedMs) {
return (nowMs - startedMs) / 1000;
}
入力はどちらもミリ秒のタイムスタンプです。新しい戻り値は端数も含む秒で、2_500 ミリ秒は 2 ではなく 2.5 になります。nowMs = 60_000、startedMs = 15_000 なら 45_000 から 45 に変わります。
調査すべきことは、旧単位を必要とする利用側と、変換で保たれる性質だけを必要とする利用側を区別することです。どちらも number と判断する型検査では答えられません。
入力が不明なら、まず値を取得元から追ってください。入力単位の変更と出力単位の変更は別です。この例は出力だけを変えます。
参照から候補を集める
エディターで elapsed を検索するか、次を実行します。
rg -n 'elapsed' --glob '*.mjs' .
consumers.mjs では、二つの呼び出し元を確認します。
export function needsRefresh(elapsed, nowMs, lastSyncMs) {
return elapsed(nowMs, lastSyncMs) >= 30_000;
}
export function oldestFirst(elapsed, nowMs, sessions) {
return [...sessions]
.sort((a, b) => elapsed(nowMs, b.startedMs) - elapsed(nowMs, a.startedMs))
.map((session) => session.id);
}
needsRefresh は固定の閾値と比較し、oldestFirst は同じヘルパーが出した二つの値を比較します。どちらも候補とし、それぞれの約束を記録します。前者は 30 秒に達したら更新、後者は入力配列を変更せず古い順にセッション ID を返します。
インストールと設定が済んだ言語サーバーの参照検索は移動の助けになります。ただし、別名、関数の注入、登録、実行時設定も調べる必要があります。参照表示は、ある環境が選び得るすべての利用側の完全な一覧ではありません。
レジストリを通って関数を追う
app.mjs は二つの直接利用側以外にもヘルパーを渡します。設定でファクトリーを選び、別の名前でヘルパーを渡しています。
const factory = registry.get(config.exporter);
if (!factory) throw new Error(`Unknown exporter: ${config.exporter}`);
const exporter = factory({ measureAge: elapsed });
同じファイルの前半にある register(registry) を追います。before と after は age-column.mjs の registerAgeColumn を呼びます。
export function registerAgeColumn(registry) {
registry.set('age-column', ({ measureAge }) => ({
kind: 'age-column',
rows(nowMs, sessions) {
return [
'id,age_seconds',
...sessions.map(
(session) =>
`${session.id},${measureAge(nowMs, session.startedMs) / 1000}`,
),
].join('\n');
},
}));
}
CSV ヘッダーは秒を約束しています。旧ヘルパーなら 1000 で割ると秒になりますが、新ヘルパーでは二重に割ります。
elapsed だけを検索しても、このファイルの measureAge(...) は見つかりません。別名と登録キーが分かったら検索を広げます。
rg -n 'measureAge|age-column|exporter' --glob '*.mjs' .
テストアプリは明示的に { exporter: 'age-column' } を選び、selectedExporter を確認します。これで登録項目と実際に走った経路がつながります。登録を見つけただけでは使用されたかは未確認です。サーバーでは、リクエスト経路を選ぶルートと設定から同じ調査を始める場合があります。
同じ期待値を両方の版に適用する
展開して code-change-impact-analysis に移動します。外部依存はありません。記録した実行環境は Node.js 22.22.1 です。22 以降を使い、ターミナルで各コマンドを個別に実行します。
node run.mjs before
node run.mjs after
node run.mjs migrated
after の終了コード 1 は想定内です。&& でつなぐと移行済み版の前に止まるため、別々に実行します。三つのモードは同じ tests.mjs のアサーションを使います。
| 版 | 成功 | 失敗 | 終了コード |
|---|---|---|---|
before:ミリ秒、元の利用側 |
7 | 0 | 0 |
after:秒、元の利用側 |
4 | 3 | 1(想定した失敗) |
migrated:秒、単位依存の利用側を更新 |
7 | 0 | 0 |
after の失敗は 30_000 と 31_000 ミリ秒での更新判定と CSV 出力です。閾値直前の確認、二つの並べ替えテスト、不明な出力名の拒否は成功します。
アサーションと値を併せて見るには、次を実行します。
node demo.mjs
nowMs = 60_000 で、alpha は 15_000、beta は 57_500 に始まります。観察結果には次を含みます。
| 観察項目 | before |
after |
migrated |
|---|---|---|---|
| 経過 30 秒で更新 | true |
false |
true |
| 古い順の ID | alpha, beta |
alpha, beta |
alpha, beta |
alpha の CSV 経過時間 |
45 |
0.045 |
45 |
beta の CSV 経過時間 |
2.5 |
0.0025 |
2.5 |
demo.mjs は観察値を表示し、tests.mjs が期待どおりかを判断します。既定の node --test run.mjs は before だけを実行するため、その成功だけでは変更案を検証できません。
壊れた利用側と、検証して問題を除外した利用側を分ける
結果は単なる参照数より具体的です。
| 利用側・範囲 | 結論 | 裏付け |
|---|---|---|
needsRefresh |
影響を確認 | 秒を返す版で、未変更の 30 秒閾値テストが失敗 |
oldestFirst |
テストした入力では回帰を除外 | 異なる経過時間、端数秒、同値、1 ミリ秒差、空と単一のリストで順序を保持し、入力も不変 |
選択された age-column |
この設定で影響を確認 | ファクトリーを選び CSV 全体を比較。両方の時間が千分の一 |
| 他の配置設定や配布外の利用側 | 未確認 | 提供も実行もされていない |
並べ替えが保たれる理由もあります。二つの経過時間の差を使うため、同じ正の数で割っても、この入力では比較の符号が変わりません。必要なのは相対順序であり、更新閾値と出力側は特定の単位を必要とします。
この考察はテスト選択の助けになりますが、全数値を検証したことにはなりません。fixture は小さく有限な整数のミリ秒です。極端な浮動小数点、不正入力、非同期更新スケジュール、実際の CSV 読み取り側は範囲外です。含まれる出力プラグインは一つで、本番の全プラグインについては主張しません。
単位に依存する利用側を移行する
elapsedAfter は残し、consumers.mjs の移行済み更新判定では秒と秒を比較します。
export function needsRefreshSeconds(elapsed, nowMs, lastSyncMs) {
return elapsed(nowMs, lastSyncMs) >= 30;
}
age-column.mjs の registerAgeColumnSeconds は measureAge(...) を直接出力して二度目の除算をなくします。app.mjs は migrated でこの二つを選び、oldestFirst は変更しません。
テストが求めるユーザー向けの結果は同じです。30 秒で更新し、同じ順序の ID と秒単位の CSV を返します。最後の 7 件の成功は、この fixture で結果が戻ったことを示します。期待値も変更したなら、同等の成功と見なす前に実装とアサーションを一緒にレビューしてください。
次に読むファイルの候補を AI に尋ねる
必要なら、次の質問で探索を絞れます。
elapsedとして渡した関数を、別名と登録ファクトリーも含めて追ってください。各利用候補が単位を仮定するのか順序だけを使うのか、ソースを示してください。影響を確認できる実行時の選択とテスト入力を挙げ、未テストの呼び出し元と実行結果を分けてください。
提案された経路をソースで確認し、自分でコマンドを実行します。