Вспомогательная функция начинает возвращать секунды вместо миллисекунд. Аргументы и тип результата выглядят прежними. Один вызов перестаёт запрашивать обновление, другой продолжает правильно сортировать, а столбец CSV незаметно уменьшается в тысячу раз. Один список ссылок не объяснит, какой из этих эффектов произошёл.
Допустим, небольшое приложение показывает сессии, решает, когда обновлять их, и экспортирует их возраст. Это учебный пример; результаты ниже получены запуском JavaScript с фиксированными входами и неизменёнными утверждениями. Скачайте полный пример, чтобы воспроизвести их.
Уточните изменение до перечисления вызовов
Архив содержит обе версии функции в elapsed.mjs, чтобы одни и те же потребители могли работать с любой из них:
export function elapsedBefore(nowMs, startedMs) {
return nowMs - startedMs;
}
export function elapsedAfter(nowMs, startedMs) {
return (nowMs - startedMs) / 1000;
}
Оба входа остаются временными метками в миллисекундах. Предлагаемый результат — секунды, включая дробную часть: 2_500 миллисекунд превращаются в 2.5, а не 2. Для nowMs = 60_000 и startedMs = 15_000 результат меняется с 45_000 на 45.
Это задаёт точный вопрос: каким потребителям нужна старая единица, а каким — лишь свойство, сохраняемое преобразованием? Проверка типов, видящая число в обеих версиях, на него не ответит.
Если входные значения незнакомы, сначала проследите одно значение от источника по коду. Изменение единицы входа и выхода — разные правки; здесь меняется только выход.
Соберите список кандидатов по ссылкам
Найдите 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 сравнивает два значения той же функции. Отметьте оба как кандидатов и запишите обещания вызывающему коду: обновлять при возрасте тридцать секунд; возвращать 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 он вызывает registerAgeColumn, реализованный в age-column.mjs:
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; используйте Node.js 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 |
Влияние подтверждено | Неизменённые тесты порога тридцать секунд падают с функцией в секундах. |
oldestFirst |
Регрессия исключена для проверенных входов | Разный возраст, дробные секунды, равный возраст, разница в миллисекунду, пустой список и один элемент сохраняют ожидаемый порядок; вход не меняется. |
Выбранный экспортёр age-column |
Влияние подтверждено для этой конфигурации | Тест выбирает фабрику и сравнивает полную строку CSV. После изменения оба возраста в тысячу раз меньше нужного. |
| Другие конфигурации развёртывания или потребители вне архива | Неизвестно | Они не предоставлены и не запускались. |
Результат сортировки тоже объясним: сравнение использует разность двух возрастов. Деление обоих на один положительный множитель сохраняет знак для этих входов. Сортировке нужен относительный порядок, а порогу и экспортёру — конкретная единица.
Это рассуждение помогает выбрать тесты, но не позволяет утверждать, что проверены все числовые входы. Фикстуры используют небольшие конечные целочисленные метки миллисекунд. Экстремальные числа с плавающей точкой, неверные входы, асинхронное планирование обновлений и реальный читатель CSV не входят в запуск. Архив содержит один экспортёр и ничего не утверждает о производственном наборе плагинов.
Обновите потребителей, зависящих от единицы
Оставьте elapsedAfter. В consumers.mjs обновлённая функция сравнивает секунды с секундами:
export function needsRefreshSeconds(elapsed, nowMs, lastSyncMs) {
return elapsed(nowMs, lastSyncMs) >= 30;
}
В age-column.mjs функция registerAgeColumnSeconds выводит measureAge(...) напрямую, убирая второе деление. app.mjs выбирает две обновлённые реализации в режиме migrated. oldestFirst не меняется.
Тесты по-прежнему требуют те же видимые результаты: обновление через тридцать секунд, те же отсортированные ID и секунды в CSV. Семь успешных финальных проверок показывают восстановление результатов для фикстуры. Если патч меняет ожидания, проверьте утверждения вместе с реализацией, прежде чем считать зелёный запуск эквивалентным.
Попросите ИИ предложить следующий файл для изучения
При чтении необязательный вопрос ИИ может сузить поиск:
Проследи функцию, переданную как
elapsed, включая псевдонимы и зарегистрированные фабрики. Для каждого кандидата покажи код, предполагающий единицу или использующий только порядок. Укажи выбор во время выполнения и тестовый ввод для проверки последствия. Отделяй непроверенные вызовы от выполненных результатов.
Проверяйте предложенные пути по исходникам и запускайте команды сами.