Claude 코드에서 금액 반올림 오류 발생: 11줄의 CLAUDE.md로 해결
요약
Claude Code에서 금액 반올림 오류가 발생하는 문제를 11줄의 CLAUDE.md 파일 추가만으로 해결한 사례를 공유합니다. 이 경험은 코딩 에이전트의 성능 논쟁에 대한 저자의 답변이며, 구조적 문제보다는 레포지토리 자체의 컨텍스트와 관례 준수가 중요함을 강조합니다.
핵심 포인트
- Claude Code에서 반올림 오류가 발생했으나, 11줄 수정으로 완벽히 해결됨.
- 코딩 에이전트의 실패는 모델 자체보다 레포지토리 구조나 컨텍스트 문제일 가능성이 높음.
- 에이전트에게 명확한 관례와 규칙을 제공하는 것이 중요함.
- 단순한 기능 추가도 정확한 반올림 처리가 필수적임.
1,005센트짜리 청구서의 50%를 환불해야 합니다. 제가 에이전트에게 준 청구 코드는 반 올림(rounds half up)을 하므로 정답은 503입니다. Claude Code에서 Sonnet 5.5는 5회 중 2회에서 502를 반환했습니다. Haiku 5.5는 5회 중 3회에서 502를 반환했습니다. 이 실행들 각각은 round_money로 반올림했다는 깔끔한 요약으로 끝났습니다. 심지어 Haiku 실행 중 두 번은 파일 자체의 헬퍼가 반 올림을 한다고 언급했지만, 다른 값을 선택했습니다.
그래서 저는 리포에 11줄짜리 CLAUDE.md를 추가했고, 다른 것은 아무것도 변경하지 않았습니다. 두 모델 모두 10번 중 10번으로 반올림을 정확하게 처리했습니다.
이 결과가 이번 주에 돌고 있는 '왜 코딩 에이전트는 이렇게 멍청한가'라는 논쟁에 대한 저의 답변 대부분입니다.
Lynch는 하네스(harness)에 대해 옳다
Michael Lynch의 Why Are Coding Agents So Dumb?는 10월 9일에 올라왔고, Hacker News와 Lobsters에서 긴 스레드를 형성했습니다(제가 마지막으로 확인했을 때 100점, 99개 댓글). 그의 불만 대부분은 모델 주변에 감싸는 소프트웨어인 하네스(harness), 즉 구조적인 문제에 관한 것입니다. OpenCode는 1,500줄짜리 기능을 10개의 서브태스크로 분할한 다음 하나씩 실행했습니다. 에이전트들은 더 저렴한 것으로 할 수 있는 검색에도 느리고 비싼 모델을 계속 사용합니다. 한 에이전트는 그가 자리를 떠나서 git 브랜치 이름을 무엇으로 지을지 물어보는 것까지 2분이 지나도록 작업을 중단했고, 다음 날 아침에도 작업은 여전히 시작되지 않은 상태였습니다. 파일 접근
하지만 댓글 스레드는 다른 곳으로 흘러갔다. 그곳에서 '멍청하다(dumb)'는 것은 모델이 '동의한 후 타입 지정되지 않은 문자열 덩어리(untyped string map ball of mud)를 작성하는 것'을 의미했거나 (throwaway63467 on HN), 코드를 생성했지만 '지독하게 복잡하다(diabolically over complicated)'는 것이었거나 (pipes, also on HN), 아니면 '단순히 컨텍스트가 더 긴 자동 완성 기능일 뿐이다'라는 것을 의미했다 (Pomax on Lobsters). 나는 그 부분에 동의하지 않는다. 그러한 실패 사례 중 상당수는 레포지토리 자체의 문제다.
1라운드: 지저분한 레포지토리는 아무것도 망가뜨리지 않았다
나는 가상의 청구 서비스(billing service)를 만들었고, Claude Code 2.1.296에게 매번 동일한 프롬프트를 주었다:
claude -p 'Add a function `refund_invoice(invoice_id, percent)` to the billing code. ...
Follow the existing conventions of this codebase. Do not ask me questions; make
reasonable decisions and finish the task.' \
...
각 실행마다 /tmp에 새 복사본이 생성되었다. 그 후 숨겨진 채점기가 여섯 가지 사항을 확인했다: 반올림(half-up rounding) 처리된 정수 센트, 감사 로그 항목(audit-log entry), 올바른 상태 상수(status constants), 그리고 알 수 없는 ID 및 미지급 청구서, 과도한 환불에 대한 BillingError.
지저분한 버전은 테스트나 문서가 없는 1,094줄짜리 billing.py 하나였다. 관례는 모두 암묵적이었다: _rc()라는 헬퍼를 사용하여 반올림하고, persist()를 통해 작성하며 (이는 감사 기능을 수행함), 절대로 _put()을 사용하지 않으며 (이것은 그렇지 않음), STATUS_* 상수를 사용하는 것이었다. 나는 또한 함정들을 남겨두었다: 부동 소수점(float) 수학을 수행하고 `
진짜 차이점은 이겁니다: 깨끗한 저장소(clean repo)에서는 Haiku가 5회 중 5회 단위 테스트를 작성했습니다. CLAUDE.md에는 새 함수는 테스트를 가져야 한다고 명시되어 있었고, 복사할 수 있는 tests/ 폴더도 있었습니다. 반면 지저분한 저장소(messy repo)에서는 단 한 번도 작성하지 못했습니다. 프롬프트가 테스트를 요구한 적은 없었습니다.
라운드 2: 저장소에 하나의 질문에 대한 두 가지 답을 제시하다
그래서 저는 모든 코드가 몇 년 이상 된 코드베이스에서 흔히 볼 수 있는 것을 추가했습니다. 즉, 그것을 수행하는 두 번째 방법입니다. 지저분했던 파일을 593줄로 줄이고 utils.py를 만들어 round_money() (은행가 반올림 방식, docstring "센트 단위의 금액을 센트 단위의 정수로 반올림합니다.")와 감사 로그(audit log)를 건너뛰는 save() 함수를 만들었습니다. 그런 다음 void_invoice, apply_discount 및 두 개의 새로운 함수들을 이들로 옮겼습니다. 이제 저장소는 "돈을 어떻게 반올림할까요"라는 질문에 대해 두 가지 답을 제시했으며, 어느 쪽이 폐기된 것인지는 아무 말도 하지 않았습니다.
| 저장소 | 모델 | 올바른 반올림 | 감사 여부 | 6가지 모든 검사 | 평균 비용/실행 |
|---|---|---|---|---|---|
| 라운드 1: 하나의 큰 파일, 문서 없음 | Haiku 5.5 | 5/5 | 5/5 | 5/5 | $0.0099 |
| ... |
비용은 Claude Code가 JSON 출력에서 보고한 것입니다. 모든 실행은 17초에서 32초 사이에 완료되었습니다.
문서화(doc)가 없었기 때문에 반올림은 동전 던지기와 같았습니다: 10번 중 5번이 맞았습니다. Sonnet은 실행당 약 14배 더 비싸고, Haiku보다 한 번 더 정확하게 반올림을 했으며, 여섯 가지 모든 검사를 동일한 빈도(5회 중 2회)로 통과했습니다. 어떤 도우미가 폐기되었는지는 오직 누군가의 머릿속에만 존재하는 결정이며, 더 똑똑한 모델은 그것을 읽어낼 수 없습니다.
요약이 최악의 부분이었습니다
실패한 Sonnet 실행 중 하나는 해당 함수가 " pay_invoice를 따릅니다: BillingError를 발생시키고, round_money로 센트를 반올림하며, persist(..., action="refund")를 통해 저장합니다."라고 작성했습니다. 하지만 pay_invoice는 아무것도 반올림하지 않습니다. 집 전체가 반올림되는 유일한 함수는 create_invoice이며, 여기에는 _rc()가 있습니다. 또 다른 실패한 실행은 1000센트짜리 청구서의 30%를 환불하여 작업을 확인했는데, 이는 어떤 반올림 방식으로 계산해도 정확히 300이 나오기 때문에 검사가 버그를 포착할 수 없었습니다.
신입사원도 그렇게 실수할 수 있습니다. 차이점은 신입사원이 Slack에 질문하면 누군가 "utils.py는 절대 사용하지 마세요, 그 리팩토링은 폐기됐어요"라고 답하고 문제가 해결된다는 것입니다. 에이전트는 그런 행동을 할 수 없습니다 (그리고 제 프롬프트도 그렇게 하라고 지시했습니다). 그래서 추측하고, 자신감 있는 요약문을 작성한 뒤 넘어갑니다. 이 모든 우회 과정은 약 20초가 걸리며 PR(Pull Request) 하나를 남깁니다.
두 라운드 모두에서 실행된 모든 테스트는 감사된 경로(persist() 또는 클린 리포의 save_invoice())를 거쳤으며, 두 번째 라운드에서는 어느 것도 save()에 접근하지 않았습니다. 한 가지 패턴이 명확하게 지배적이었던 곳에서 에이전트들은 그 패턴을 따랐고, 코드가 스스로와 의견이 달랐던 바로 그 지점에서 실패했습니다.
제가 대신 언급할 주의사항 (Caveats)
제가 작성한 장난감 리포지토리에서 셀당 5번씩 실행했고, 제가 작성한 스크립트로 평가했습니다. 효과가 있다는 것을 보여주기에는 충분하지만, 얼마나 큰지는 말하기엔 너무 작습니다. 환불 검사만 실패한 세 번의 테스트는 오류를 발생시키는 대신 남은 잔액으로 환불 금액을 제한했습니다. 제 프롬프트에서 제가 원하는 바를 명시하지 않았기 때문에 저에게 책임이 있습니다. 2/5와 3/5 반올림 숫자가 중요한 부분입니다.
이번 주 실제 리포지토리에서 할 일
- 코드가 알려줄 수 없는 것만 적습니다. 아키텍처 에세이는 건너뜁니다. 이들은 제가 두 번째 라운드 파일에서 작업한 내용들입니다:
- 금액은 항상 정수 센트(integer cents)여야 합니다. 절대 부동 소수점(floats)을 사용하지 마세요. `_rc()`로 반올림합니다 (반 올림, 금융 요구 사항).
- 인보이스는 반드시 `persist()`를 통해서만 작성하세요. 이것이 감사 로그를 기록하고, 재무팀은 여기서 조정합니다.
- `utils.py` (`round_money`, `save`)는 폐기된 리팩토링의 결과입니다: 은행가 반올림(banker's rounding)
...
Claude Code는 CLAUDE.md를 읽습니다. 대부분의 다른 에이전트들은 AGENTS.md를 찾습니다. 심볼릭 링크(symlink)가 둘 다를 커버합니다.
-
중복된 헬퍼 함수를 찾으세요.
grep -rn "def .*round\|def .*save\|def .*format_money" --include=*.py와 같은 명령어는 값싼 첫 번째 검사입니다. 제 round-two 레포지토리에서는 곧바로utils.py를 가리켰습니다. 사용하지 않는 함수는 삭제하거나 파일 상단에 사용 중단(deprecated)으로 표시하세요. -
몇 초 만에 실행되는 테스트 명령어를 배포하고 문서에 추가하세요. 이 줄과 "새로운 함수에는 테스트가 포함된다"라는 문구, 그리고
tests/폴더 덕분에 round one에서 Haiku는 모든 클린 레포지토리 실행 시마다 테스트를 작성했습니다. -
에이전트가 자체적으로 수행하는 건전성 검사(sanity check)를 믿지 마세요. 어떤 엣지 케이스(edge case)를 테스트했는지 물어보세요. 제 경우, 버그가 보이지 않는 금액을 테스트했습니다.
하네스(harnesses)는 개선될 필요가 있습니다. 하지만 그 스레드에서 "멍청하다"고 불리는 것의 상당 부분은 에이전트가 스스로 모순되는 코드베이스를 읽고 답변 중 하나를 선택하는 것입니다. 귀하의 팀은 수년 전에 그러한 모순을 우회하는 방법을 배웠습니다. 에이전트는 첫 시도에 그 모순에 부딪히고, 여러분은 diff에서 그것을 발견하게 됩니다.
귀하의 코드베이스에서 코딩 에이전트가 저지른 가장 멍청한 행동은 무엇이며, 솔직히 말해서 누구의 잘못이었나요?
출처
- Michael Lynch, Why Are Coding Agents So Dumb?
- Hacker News 토론
- Lobsters 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기