Патч небольшой, тесты зелёные, а бронирование четырёх мест проходит при наличии только трёх. Остаток даже выглядит разумно: ноль. Проблема ясна, если открыть тесты в том же 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 проверьте ревизии и спросите:

Найди удалённые правила принятия и изменённые вместе с ними утверждения. Проследи неизменённые вызовы, зависящие от ошибок. Для каждого риска дай конкретный ввод и ожидаемый результат со ссылками на исходники. Отдели регрессии от поведения базы.