+ Covers every requirement with practical checks.
- Some checks overlap with the design-change section.
Goes past listing pitfalls to attaching a concrete check that would have caught each one.
| Category | Development › Coding |
|---|---|
| Tags | ChecklistReviewingDeveloper |
List the common mistakes in this area, each with the check that catches it. For each mistake: - What the mistake is, concretely. Not "poor error handling" but the specific shape of it. - **The check** — a test, a command, a grep, a review question, or an assertion. It must be something I can actually run or ask, not "be careful". - What the check outputs when the mistake is present versus absent. - How expensive the check is, so I know what to run every time versus once before release. Then: 1. Order by (how often it happens) × (how expensive it is when it ships). 2. Mark the checks that can be automated, and roughly how. 3. For the problems I already hit: which check would have caught each one. *If none would have, say so* — that is the more useful answer than inventing a check after the fact. 4. Mistakes with no cheap check. Those need a design change instead, not vigilance. Rules: - *A mistake without a check does not go in the list.* Awareness is not a control. - Fit the checks to my stated context. Generic advice fails here. - Five to eight entries. A long checklist gets skipped.
A list of things to be careful about leaves nothing behind. Attaching "the check that would have caught this" turns the list into a runnable checklist.
ChatGPT has the best balance of accuracy, structure, and length. Gemini is concrete but overlong and relies on questionable Toss-specific assumptions; [C] is missing.
+ Covers every requirement with practical checks.
- Some checks overlap with the design-change section.
+ Provides concrete commands, tests, and outputs.
- Assumes Toss auth and timing rules; far too long.
| Criterion | ChatGPT | Gemini | Leader |
|---|---|---|---|
| Instruction following | 9 | 8 | ChatGPT +13% |
| Accuracy | 8 | 5 | ChatGPT +60% |
| Specificity | 9 | 9 | Tie |
| Structure | 9 | 8 | ChatGPT +13% |
| Right length | 8 | 5 | ChatGPT +60% |
Scored 1–10 by gpt-5.6-sol with model names hidden (2026-09-24). This is an AI review, not a measurement.
We gave three models the same input and copied their answers unedited. Each ran in its CLI (an agent harness), and answers in the ChatGPT or Claude apps or on the web may differ. Outputs are in Korean.
Area: Node.js/Express로 만든 결제 API 서버, 외부 PG(토스페이먼츠) 연동 My context: TypeScript, PostgreSQL, 팀 3명, 2주마다 배포. 프리랜서 출신 개발자라 결제 도메인 실무 경험은 이번이 처음 Problems I have hit: PG 웹훅을 중복으로 두 번 수신해서 결제 완료 처리가 두 번 일어난 적 있음. 환불 금액을 원 단위 정수가 아니라 float으로 계산해서 소수점 오차가 발생한 적 있음
아래 순서는 이 스택에서의 대략적인 발생 빈도 × 출시 피해 기준입니다. 비용은 “한 번 실행하는 비용”입니다.
| 순위 | 구체적인 실수 | 검출 체크 | 실수 있음 / 없음의 출력 | 비용·자동화 |
|---|---|---|---|---|
| 1 | 같은 PG 웹훅을 두 번 처리해 결제 완료 이벤트, 포인트 지급, 주문 상태 변경을 두 번 실행 | 동일한 웹훅 payload를 동시에 2회 전송한 통합 테스트. DB에는 webhook_event_id 또는 PG 거래 ID에 UNIQUE 제약을 둠. 결제 상태 변경도 WHERE status = 'READY'처럼 조건부 갱신 |
있음: 처리 행 또는 부수효과가 2개, 잔액/포인트가 2배. 없음: 한 번만 상태 변경되고 두 번째 요청은 200/already processed |
매 배포마다. 자동화 쉬움. CI 통합 테스트 + PostgreSQL 제약 |
| 2 | 금액을 number, float, parseFloat로 계산하거나 원 단위가 아닌 소수 단위로 저장 |
금액 타입을 정수로 제한하고 다음 테스트를 실행: 10_000 - 3_333 또는 0.1 + 0.2가 결제/환불 금액으로 통과하지 않는지 검사. 코드 검사로 amount 주변의 parseFloat, toFixed, 산술 연산도 검색 |
있음: 9999.999999..., 반올림 차이, 정수 검증 실패. 없음: 모든 금액이 Number.isSafeInteger()를 통과하고 DB 저장값도 정수 |
매 커밋. 단위 테스트·ESLint·grep 자동화 가능. DB 컬럼은 BIGINT 권장 |
| 3 | 클라이언트가 보낸 금액이나 paymentKey만 믿고 결제 성공 처리 |
금액을 서버 주문 금액과 PG 조회 결과 양쪽에 대조하는 변조 테스트. 요청의 amount=1000을 amount=1로 바꿔도 서버가 성공 처리하지 않는지 검사 |
있음: 변조된 금액으로 결제 완료. 없음: 409/검증 오류 또는 PG 실제 금액과 불일치로 거부 |
매 배포마다. 자동화 쉬움. API 통합 테스트 |
| 4 | 결제 상태를 아무 순서로나 바꿀 수 있음. 예: CANCELED → PAID, REFUNDED → PAID, 이미 전액 환불된 결제를 다시 환불 |
상태 전이 표를 코드로 만들고 모든 금지 전이를 테스트. DB 업데이트는 허용된 현재 상태를 WHERE 조건에 포함 |
있음: 금지 전이가 성공하거나 영향 행 수가 1. 없음: 409 또는 영향 행 0, 최종 상태 불변 |
매 커밋. 자동화 쉬움. 상태 머신 단위 테스트 + DB 제약/조건부 UPDATE |
| 5 | DB 커밋 전에 웹훅에 성공 응답하거나, DB 반영 후 외부 부수효과가 실패해 상태와 실제 결과가 어긋남 | DB commit → 응답 순서를 테스트하고, 중간 실패를 주입해 재시도 결과를 확인. 결제 완료 후 포인트/영수증/주문 반영은 outbox 레코드가 생성되는지 검사 |
있음: 실패했는데 200, 또는 재시도 때 중복 부수효과/누락 발생. 없음: 커밋 전 오류는 재시도되고, 커밋 후 작업은 outbox 재처리로 완료 |
릴리스 전 + 주요 변경 시. 실패 주입 테스트는 중간 비용. 진짜 해결책은 트랜잭션 경계와 outbox |
| 6 | 웹훅 본문에 있는 status=PAID만 믿고 위조·재전송 요청을 처리 |
Toss Payments가 제공하는 공식 인증/검증 방식에 맞춰, 위조된 본문과 실제 PG 조회 결과가 다른 테스트를 실행. 인증 수단이 별도 헤더가 아니라면 서버에서 PG 결제 조회 API로 상태·금액·주문을 재검증 | 있음: 인증되지 않은 본문만으로 결제 완료. 없음: 인증 실패 또는 PG 실제 상태 불일치로 거부 | 릴리스 전 + 인증 코드 변경 시. 자동화 가능. 중요한 점은 존재하지 않는 Toss 헤더를 임의로 가정하지 않는 것 |
| 7 | 환불 요청 재시도 때 같은 환불을 두 번 호출하거나, 부분 환불 합계가 결제액을 초과 | 같은 refundRequestId로 동시 요청 2개를 보내고, sum(refund_amount) <= paid_amount를 DB/서비스 양쪽에서 검사. PG 환불 API 호출에도 idempotency key를 사용 |
있음: 환불 API 2회 호출, 환불 합계 초과, 잔액 음수. 없음: 환불 기록 1개 또는 두 번째 요청이 동일 결과로 안전하게 종료 | 매 배포마다. 자동화 가능. DB 유니크 제약 + 동시성 통합 테스트 |
금액 타입은 특히 다음처럼 고정하는 편이 안전합니다.
amount_krw BIGINT NOT NULL CHECK (amount_krw >= 0)
TypeScript에서도 number를 쓰더라도 경계에서 반드시 정수 검사를 두고, 계산 결과에 대해 Number.isSafeInteger(amount)를 검사해야 합니다. 금액을 소수로 표현해야 하는 요구가 실제로 생기면 decimal.js 같은 명시적 decimal 타입을 사용하고, 현재처럼 원 단위 결제라면 정수 원 단위가 더 단순합니다.
이미 겪은 문제와 대응 체크는 다음과 같습니다.
PG 웹훅 중복 수신으로 완료 처리가 두 번 됨
→ 1번 체크가 직접 잡습니다. 특히 “동시 2회 전송”과 UNIQUE 제약 양쪽이 필요합니다. 애플리케이션의 if (alreadyProcessed)만으로는 두 요청이 동시에 통과할 수 있습니다.
환불 금액 float 계산으로 소수점 오차 발생
→ 2번 체크가 직접 잡습니다. BIGINT 금액 컬럼, Number.isSafeInteger() 테스트, float 관련 금지 lint 규칙을 함께 두는 것이 좋습니다.
저렴한 체크만으로는 잡기 어려운 문제도 있습니다.
이것들은 “주의해서 코딩하기”로 해결할 문제가 아닙니다. 결제 상태 변경은 DB 트랜잭션으로 처리하고, 포인트·알림·영수증 같은 후속 작업은 outbox 테이블에 기록한 뒤 별도 worker가 재시도하도록 설계를 바꾸는 것이 통제 수단입니다.
| 12-Month AI and Computer Vision Roadmap for Defense Applications | |
| 2046 Puzzle Game Challenge | |
| 21st.dev component prompt | |
| 3D FACTORY | |
| 3D FPS Game |