Патч небольшой, тесты зелёные, а бронирование четырёх мест проходит при наличии только трёх. Остаток даже выглядит разумно: ноль. Проблема ясна, если открыть тесты в том же diff. Раньше они требовали ошибку, теперь — лишь неотрицательный результат.
Допустим, ИИ попросили упростить подсчёт мест, сохранив правила приёма запросов. Патч и ослабленные тесты написаны для этого руководства и запускаются локально встроенным тестовым инструментом Node.
Начните с правила, защищённого старыми тестами
Здесь available уже проверен выше: это неотрицательное безопасное целое. Функция принимает количество, возвращает точный остаток или бросает RangeError. Допускаются только числовые безопасные целые от 1 до available. Нет округления, преобразования строк или частичного бронирования.
export function remainingSeats(available, requested) {
if (
!Number.isSafeInteger(requested) ||
requested < 1 ||
requested > available
) {
throw new RangeError(
'requested seats must be an integer from 1 to available',
);
}
return available - requested;
}
При трёх доступных местах примеры легко проверить вручную:
| Запрошено | Требуемое поведение | Причина |
|---|---|---|
1, 2, 3 |
Вернуть соответственно 2, 1, 0 |
Принять весь запрос |
0, -1, 1.5 |
Бросить RangeError |
Нельзя бронировать ноль, отрицательные или дробные места |
4 |
Бросить RangeError |
Нельзя выполнить запрос полностью |
'2', NaN, Infinity |
Бросить RangeError |
Количество должно быть числовым безопасным целым |
Когда мест нет, запрос одного места тоже бросает ошибку. Ноль уже является допустимым успешным результатом при точном заполнении, поэтому не может одновременно обозначать ошибку.
В реальном ревью установите правила по требованию, существующим вызовам и тестам базы. Старые утверждения могут быть неверны, но их изменение требует объяснения меняемого поведения. Здесь задача явно требует сохранить отклонение запросов.
Прочитайте удалённые строки в обоих файлах
Предложение заменяет валидацию ограничением диапазона:
export function remainingSeats(available, requested) {
- if (
- !Number.isSafeInteger(requested) ||
- requested < 1 ||
- requested > available
- ) {
- throw new RangeError(
- 'requested seats must be an integer from 1 to available',
- );
- }
- return available - requested;
+ const count = Math.min(available, Math.max(1, Math.trunc(requested)));
+ return available - count;
}
Для (3, 4) локальный count становится 3, результат — 0. Для (3, 1.5) он становится 1, а функция возвращает 2. Патч меняет набор успешных входов, хотя обычные запросы всё ещё дают верный ответ.
Теперь прочитайте diff тестов. Три теста следуют шаблону:
- test('rejects more seats than available', () => {
- assert.throws(() => remainingSeats(3, 4), RangeError);
+ test('handles more seats than available', () => {
+ assert.ok(remainingSeats(3, 4) >= 0);
});
Ввод не изменился, требуемый результат — да. Новое утверждение вызывает функцию напрямую и падает, если она всё ещё бросает ошибку. Версия, молча принимающая слишком большой запрос, теперь проходит. Переименование «rejects» в «handles» делает изменение менее заметным в отчёте.
Нулевой и дробный случаи тоже теряют проверки исключений. В наборе по-прежнему пять тестов; два обычных успешных не изменены. Количество не показывает, какое поведение ещё проверяется.
В реальном патче читайте реализацию и изменения тестов вместе:
git diff BASE HEAD -- src/remaining-seats.mjs test/remaining-seats.test.mjs
git show BASE:test/remaining-seats.test.mjs
Замените ревизии и пути своими. Запишите точные сравниваемые версии, особенно если параллельно читаете уже изменившееся рабочее дерево. ai-style.patch позволяет изучить пример без создания истории Git.
Преобразуйте удалённую проверку в конкретный ввод: удаление requested > available даёт (3, 4). Для удалённого утверждения укажите прежнее требование: ввод должен был бросить ошибку до продолжения вызова. Это полезнее оценки внешней «защищённости» кода.
Откройте неизменённый вызывающий код
Следующий вызывающий код одинаков для обеих версий. Вспомогательная функция и синхронная save передаются аргументами, чтобы пример работал в памяти:
export function createBooking(remainingSeats, save) {
return function book(available, requested) {
const remaining = remainingSeats(available, requested);
const reservation = { seats: requested, remaining };
save(reservation);
return reservation;
};
}
Здесь две величины. remaining приходит из функции; seats использует исходный requested. Старая функция принимала ровно это количество либо бросала ошибку до save. После патча она молча использует три, а вызывающий код записывает четыре.
Тот же вызывающий код с (3, 4) даёт:
| Функция | Результат для внешнего вызова | Вызовы сохранения |
|---|---|---|
| Исходная | RangeError |
Нет |
| Предлагаемая | { seats: 4, remaining: 0 } |
Один с тем же объектом |
Пример показывает неверную резервацию в памяти и попытку сохранения, а не транзакцию базы, платёж или реальное бронирование клиента.
Ищите за пределами изменённых файлов, чтобы найти зависимость в проекте:
rg -n 'remainingSeats|createBooking' src test
Проследите импорты, чтобы подтвердить используемую функцию. Затем изучите продолжение: сохраняется ли исходный ввод, строится ли ответ, перехватывается ли конкретная ошибка? Если ссылки трудно найти по имени, используйте языковой сервер и проверьте обёртки. Руководство чтения объясняет сочетание способов.
Сохраните проверку ошибки и отдельно наблюдайте сохранение
Восстановление трёх исходных тестов отклонения уже ловит патч. Добавьте тест вызывающего кода, показывающий важность ошибки:
const writes = [];
const book = createBooking(remainingSeats, (row) => writes.push(row));
assert.throws(() => book(3, 4), RangeError);
assert.deepEqual(writes, []);
Выбрана настоящая исходная или предлагаемая функция. Только save заменена записью в массив. Заглушка функции, всегда бросающая ошибку, обошла бы проверяемое изменение.
Компактный тест останавливается на первом упавшем утверждении. В архиве «нет попытки сохранения» проверяется отдельно, чтобы один запуск показал оба сбоя:
const writes = [];
const book = createBooking(remainingSeats, (row) => writes.push(row));
try {
book(3, 4);
} catch {
// A separate test checks that the error is RangeError.
}
assert.deepEqual(writes, []);
Пустой catch ограничен этим наблюдательным тестом: он позволяет проверить записи даже при ошибке бронирования. Сам по себе он слаб — любая ошибка до сохранения даст успех. Набор отдельно требует RangeError, проверяет однократное сохранение верной резервации с правильными значениями и передачу ошибок сохранения. Пустой возврат или отклонение всех запросов не удовлетворяют этим тестам.
В проверочном наборе 15 тестов: три точных успешных результата, восемь неверных входов включая отсутствие мест и четыре теста вызывающего кода. Ожидания взяты из правил выше, а не вычислены копией предлагаемого ограничения диапазона.
Проверьте одинаковые ожидания на обеих реализациях
Скачайте полный пример, распакуйте и откройте терминал в review-ai-generated-tests. Нужен Node.js 22 или новее; установка, сеть и настройка провайдера не требуются.
node run.mjs baseline
node run.mjs proposed-original
node run.mjs proposed-weak
node run.mjs verify-baseline
node run.mjs verify-proposed
Каждая команда регистрирует тесты через node:test и завершает процесс их результатом. Запускайте отдельно: вторая и пятая намеренно падают. Не объединяйте через &&, иначе выполнение остановится на первом ожидаемом сбое.
Вот наблюдавшиеся результаты Node.js v22.22.1 на macOS arm64:
| Режим | Реализация / утверждения | Успешно | Сбой | Код выхода |
|---|---|---|---|---|
baseline |
Исходная / исходные | 5 | 0 | 0 |
proposed-original |
Предлагаемая / исходные | 2 | 3 | 1 |
proposed-weak |
Предлагаемая / ослабленные | 5 | 0 | 0 |
verify-baseline |
Исходная / проверочные | 15 | 0 | 0 |
verify-proposed |
Предлагаемая / проверочные | 5 | 10 | 1 |
Предложение сообщает Missing expected exception (RangeError).. Отдельный тест сохранения показывает массив с { seats: 4, remaining: 0 } вместо требуемого пустого. node show-caller.mjs печатает оба результата без утверждения их правильности.
Для зелёной проверки по умолчанию выполните node --test run.mjs: выбирается verify-baseline. В отличие от теста воспроизведения, ожидающего поломку, здесь требуется верное поведение и предложение действительно падает. Команды не используют специфичные для ОС функции оболочки; запуск был на macOS, а не в матрице Linux/Windows.
Для проверки изменений ИИ сохраните нужные утверждения базы и запустите их на предложенном коде, затем добавьте минимальный случай вызова, подсказанный diff. Если обе версии проходят новый тест, изучите его входы и утверждения до признания регрессионным покрытием.
Превратите сбой в решение ревью
Замечание может быть конкретным:
Замена отклонения ограничением заставляет
remainingSeats(3, 4)вернуть ноль. НеизменённыйcreateBookingсохраняет{ seats: 4, remaining: 0 }. Три изменённых теста раньше требовалиRangeError; новые проверки неотрицательности принимают это поведение. Сохрани проверки отклонения и проверь, что избыточное бронирование не вызываетsave. Исходная версия проходит 15 проверок, патч проваливает 10.
Для заданной задачи сохраните исходную валидацию и восстановите проверки ошибок. Если частичное выполнение — намеренное новое требование, определите передачу принятого количества вызывающему коду и подтверждение пользователя, затем меняйте реализацию, вызовы и тесты вместе. Тихая локальная подмена количества оставляет решения открытыми.
Пример охватывает синхронную валидацию и вызов синхронного сохранения. Предполагает верный available и не проверяет параллельные бронирования, атомарность базы, повторы и авторизацию. Для них нужны тесты в реальных точках интеграции.
Yenpo помогает читать реализацию, тесты и вызывающий код и сравнивать коммиты. Требования системы — в руководстве установки; пример Node и метод ревью не зависят от Yenpo. В сравнении Git проверьте ревизии и спросите:
Найди удалённые правила принятия и изменённые вместе с ними утверждения. Проследи неизменённые вызовы, зависящие от ошибок. Для каждого риска дай конкретный ввод и ожидаемый результат со ссылками на исходники. Отдели регрессии от поведения базы.