패치는 작고 테스트는 통과합니다. 그런데 남은 좌석이 세 개뿐인데 네 좌석 예약이 처리됩니다. 남은 좌석 수도 0으로 그럴듯합니다. 같은 diff에서 테스트를 열면 문제가 드러납니다. 예전에는 오류가 발생해야 했지만 이제는 결과가 음수가 아니기만 하면 됩니다.

허용하는 요청은 그대로 두고 좌석 수 계산 함수를 단순화해 달라고 AI에게 요청했다고 가정해 보겠습니다. 아래 패치와 약해진 테스트는 이 튜토리얼을 위해 작성했으며 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 발생 0, 음수, 소수 좌석 예약 금지
4 RangeError 발생 요청 전체를 처리할 수 없음
'2', NaN, Infinity RangeError 발생 수량은 숫자형 안전한 정수여야 함

남은 좌석이 없으면 한 좌석 요청도 오류가 됩니다. 정확히 남은 수량을 예약할 때 0은 정상 결과이므로 오류 신호로도 쓸 수는 없습니다.

실제 검토에서는 요구된 동작, 기존 호출자, 기준 리비전의 테스트로 규칙을 확인하세요. 기존 검증문도 잘못될 수 있지만, 수정하려면 어떤 동작을 바꾸는지 설명해야 합니다. 여기서는 거부 동작을 유지하라는 요구가 명확합니다.

두 파일에서 삭제된 줄을 읽으세요

제안된 구현은 검증을 값의 범위 제한으로 바꿉니다.

 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”로 바꾸면 테스트 보고서에서 이 변화가 덜 드러납니다.

0과 소수 입력도 예외 검증을 잃습니다. 테스트는 여전히 다섯 개이며 일반적인 성공 테스트 두 개는 바뀌지 않았습니다. 테스트 개수만으로 어떤 동작을 검증하는지 알 수는 없습니다.

실제 패치에서는 구현과 테스트 변경을 함께 읽으세요.

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

import를 따라가 호출자가 어떤 헬퍼를 사용하는지 확인하세요. 호출 이후에 원래 입력을 저장하는지, 응답을 만드는지, 특정 오류를 잡는지 살펴보세요. 이름으로 참조를 찾기 어렵다면 언어 서버를 사용하고 래퍼도 확인하세요. 코드 읽기 가이드에서 이 방법들을 함께 사용하는 법을 설명합니다.

오류 검사를 유지하고 저장은 따로 관찰하세요

기존 거부 테스트 세 개를 복원하는 것만으로도 이 패치를 잡아낼 수 있습니다. 거부가 중요한 이유를 보여 주는 호출자 테스트를 추가하세요.

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로 테스트를 등록하고 결과에 맞는 종료 코드를 반환합니다. 각각 실행하세요. 두 번째와 다섯 번째 명령은 의도적으로 실패합니다. &&로 연결하면 첫 예상 실패에서 멈추므로 사용하지 마세요.

macOS arm64의 Node.js v22.22.1에서 관찰한 결과입니다.

모드 구현 / 검증문 통과 실패 종료 코드
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을 선택합니다. 잘못된 결과가 나왔음을 확인해 통과하는 재현 테스트와는 다릅니다. 여기서는 검증 테스트가 올바른 동작을 요구하며 제안된 구현은 실제로 실패합니다. 명령에는 OS 전용 셸 기능이 없지만, 이 실행은 macOS에서 했으며 Linux나 Windows 테스트 매트릭스는 아닙니다.

자신의 프로젝트에서 AI 코드 변경을 검증하려면 관련 기준 검증문을 유지해 제안 코드에 실행하고, diff가 암시하는 가장 작은 호출자 사례를 추가하세요. 새 테스트에서 기존 구현과 제안 구현이 모두 통과하면 회귀를 다룬다고 판단하기 전에 입력과 검증문을 살펴보세요.

실패를 검토 결정으로 연결하세요

검토 의견은 구체적으로 적을 수 있습니다.

거부를 범위 제한으로 바꾸면 remainingSeats(3, 4)가 0을 반환합니다. 변경되지 않은 createBooking은 이어서 { seats: 4, remaining: 0 }을 저장합니다. 수정된 세 테스트는 이전에 RangeError를 요구했지만 새 검증문은 음수가 아닌지만 확인해 이 동작을 허용합니다. 거부 검사를 유지하고 초과 예약이 절대 save를 호출하지 않는지 검증하세요. 기존 구현은 검증 테스트 15개를 모두 통과하고 이 패치는 10개에 실패합니다.

명시된 작업에서는 기존 검증을 유지하고 오류 검증문을 복원하세요. 일부 수량 처리가 의도된 새 요구라면 수락된 수량을 호출자에게 어떻게 전달하고 사용자가 무엇을 확인하는지 먼저 정한 뒤 구현, 호출자, 테스트를 함께 바꾸세요. 헬퍼의 지역 수량만 조용히 바꾸면 이런 결정은 해결되지 않습니다.

이 예시의 범위는 동기 검증과 동기 저장 함수 호출입니다. 유효한 available 수량을 가정하며 동시 예약, 데이터베이스 원자성, 재시도, 권한 검사는 다루지 않습니다. 그런 항목은 실제 통합 지점에서 테스트해야 합니다.

Yenpo는 구현, 테스트, 호출자를 읽고 커밋된 버전을 비교하는 데 도움이 됩니다. 현재 시스템 요구 사항은 설치 가이드를 확인하세요. Node 예시와 검토 방법은 Yenpo 없이도 사용할 수 있습니다. Git 비교에서 선택한 리비전을 확인한 뒤 다음과 같이 질문하세요.

이 패치에서 제거된 허용 규칙과 함께 바뀐 검증문을 찾아 주세요. 그 오류에 의존하는 변경되지 않은 호출자를 따라가세요. 각 우려마다 구체적인 입력과 기대 결과, 소스 참조를 제시하세요. 회귀와 기준 버전부터 있던 동작을 구분하세요.