Claude Code 수정 지시에 '완료 조건'을 작성하면 무엇이 달라지는가
요약
Claude Code 사용 시 수정 지시에 '완료 조건'을 명시하면 테스트 및 검증 과정에서 큰 차이를 보였습니다. 구현 자체는 간단한 요청만으로도 유사했으나, 완료 조건을 통해 모델이 도달해야 할 구체적인 목표를 제시함으로써 결과물의 완성도를 높일 수 있음을 확인했습니다.
핵심 포인트
- 수정 지시에 '완료 조건'을 명시하면 테스트 및 검증 과정에서 차이가 발생합니다.
- 공통 규칙은 `CLAUDE.md`에, 특정 수정의 도달점은 개별 지시문에 작성하는 것이 효과적입니다.
- 구체적인 완료 조건을 제시할수록 모델이 목표를 달성하기 위한 행동 범위가 명확해집니다.
지난 글에서는 Claude Code가 수정한 후 스스로 빌드와 테스트를 실행하도록, CLAUDE.md의 권한(permission) 및 hooks 설정을 했습니다. 다만 한 가지 궁금했던 점이 있었습니다. CLAUDE.md의 실행 절차 섹션에 'lint는 수정 마지막에 단 1회'라고 적었음에도 불구하고, 실제로 실행하는 경우와 그렇지 않은 경우가 있었습니다.
그래서 이번에는 프로젝트 전체의 규칙이 아닌, 1회 단위의 수정 지시에 완료 조건을 작성해 보았습니다. 동일한 기능 추가를 '간단한 요청'과 '완료 조건을 작성한 요청'으로 각각 3번씩 부탁해 보고, 나온 차이점을 비교했습니다.
먼저 결과를 말씀드리겠습니다. 구현 자체는 간단한 요청만으로도 거의 같은 결과물이 나왔습니다. 차이가 난 부분은 테스트와 검증이었습니다.
두 파일의 역할 분리
지난번의 CLAUDE.md와 이번 지시문 모두에 '완료 조건'을 작성하지만, 그 내용이 다릅니다.
| 위치 | 작성할 내용 | 예시 |
|---|---|
| CLAUDE.md | 모든 수정에 공통되는 조건 | 빌드와 테스트가 통과함, 기존 테스트를 제거하여 실패하게 만들기 |
| 수정 지시문 | 해당 수정만의 도달점 | 어떤 계층에 무엇을 추가할지, 어떤 동작을 테스트할지, 순서|
수정 건별로의 도달점을 CLAUDE.md에 모두 적어두는 것은 현실적이지 않습니다. 그 수정을 통해 무엇이 완료되어야 하는지는 요청하는 측에서 지시문으로 전달합니다. 공통적인 조건이라 하더라도, 해당 수정에서 반드시 통과해야 할 것은 지시문에 다시 작성할 수 있습니다. 이번 B에서도 빌드와 테스트 실행을 지시문에 적었습니다.
시도한 수정 및 조건
재료는 지난번과 동일하게 자사 Android 앱 MyntPack입니다. 사진 이동 기간 선택지에 '이달의 것'을 추가합니다. 이 수정은 다음 4개 계층에 걸쳐 이루어집니다.
-
데이터:
Period에 '이달'을 나타내는ThisMonth를 추가 - 프리셋: 선택지 종류PeriodPresets.Kind에THIS_MONTH를 추가하고 목록에 배치 - 화면: 기간 선택 화면의labelOf에 표시 이름 분기를 추가하고 문구를strings.xml에 배치 - 테스트 조건은 갖추었습니다. -
코드는 Google Play에서 공개 중인 v1.2.2와 동일한 커밋(
e161664)을 사용하고, 매번git reset --hard및git clean -fd app으로 초기화한 후 시작했습니다 (Git 관리 외의 빌드 출력물app/build는 회를 거쳐 남아있음) -CLAUDE.md는 MyntPack에 원래 있던 18줄 그대로 유지하고, 지난 글에서 추가했던 '완료 조건' 섹션은 넣지 않고, 변경하는 것은 지시문만으로 했습니다 - 권한은 지난 글과 동일하게settings.json을--settings로 전달했습니다 (새로 clone한 디렉토리이므로). 아울러--setting-sources project,local를 추가하여 로컬 사용자 설정은 읽히지 않도록 했습니다. 다만, 원래의CLAUDE.md가:app:assembleDebug를 명시하고 있기 때문에,allow에Bash(./gradlew :app:assembleDebug *)를 추가했습니다 - hooks 설정도 같은 것을 넣었지만, 로그에는 hook이 작동했다는 기록이 없었습니다 (작동하지 않은 이유는 분리하지 않았습니다) -
비대화 모드(
claude -p)로 실행하고 파일 편집은 자동으로 승인했습니다 - Claude Code 2.1.281, 모델은 Claude Opus 5.5 (claude-opus-5-5[1m]). 2026-09-26에 실행 - A1→A2→A3→B1→B2→B3 순서로 연속 실행하고, 각 회차마다 이쪽의 확인도 거쳤습니다. 빌드 캐시는 회를 거쳐 적용되고 있기 때문에, 소요 시간은 A와 B의 속도를 비교하는 데 사용할 수 없습니다.
간단한 요청(A)은 다음과 같습니다.
기간 선택지에 '이달의 것'을 추가해 주세요.
완료 조건을 작성한 요청(B)은 다음과 같습니다. A의 결과를 보기 전에 작성하여 저장해 두었습니다.
기간 선택지에 '이달의 것'을 추가해 주세요.
## 완료 조건 (모두 충족된 후에 '완료'라고 보고할 것)
1. 데이터: `Period`에, 당월 1일 00:00부터 당월 말일 23:59:59.999까지를 나타내는 `ThisMonth`가 존재
...
측정 항목도, 실행하기 전에 결정해 두었습니다. 구체적으로는, 수정된 파일, 4개의 레이어가 모두 갖춰졌는지 여부, 순서, 문구 배치 위치, 추가한 테스트 내용, 세션 중에 실행한 Gradle 태스크, 보고서 작성 방식입니다. 세션 후에는 저희가 spotlessCheck detekt :app:lintDebug :app:testDebugUnitTest를 실행했습니다.
결과: 구현은 한 글자도 동일했다
본체의 4개 파일(Period.kt, PeriodPresets.kt, PeriodSelectScreen.kt, strings.xml)은 A의 경우 3번, B의 경우 3번으로 각각 동일했습니다. A와 B 사이에서는 월말 계산 방식이 한 줄만 다릅니다(A는 '다음 달 1일 전날', B는 '오늘 날짜를 월의 일수로 대체'이며, 결과는 같습니다). 그 외에 A 중 1회에서 PeriodPresets.kt의 주석을 1줄 수정했습니다. 기능적으로는 모두 동일한 구현입니다. 테스트 파일에는 차이가 있으며, B는 3번 모두 PeriodPresetsTest.kt를 새로 작성했습니다.
| A (한 글자) 3회 | B (완료 조건 있음) 3회 | |
|---|---|---|
| 4개 레이어 추가 | 모두 갖춰짐 | 모두 갖춰짐 |
| 순서 ('전체' 다음, '지난 달 것' 전) | 모두 이 위치 | 모두 이 위치 |
문구를 strings.xml에 배치 | 3번 모두 | 3번 모두 |
| 세션 후 체크 | 3번 모두 성공 | 3번 모두 성공 |
| 소요 시간 | 55~87초 | 59~100초 |
한 글자 요청만으로 화면 측의 분기가 누락되지 않은 이유는 코드 자체에 있습니다. labelOf는 when (preset.kind)을 식으로 반환하며, else가 없습니다. 따라서 THIS_MONTH를 추가하면, 화면 측에서 구현하지 않는 한 컴파일이 통과되지 않습니다. 문구를 strings.xml에 배치한 것은 MyntPack의 CLAUDE.md에 'UI 문구는 app/src/main/res/values/strings.xml에 통합(하드코딩 금지)'이라고 적혀 있고, 기존 선택지의 문구도 거기에 있기 때문이라고 생각합니다(어느 것이 효과가 있었는지는 분리하여 확인하지 않았습니다). 순서의 경우, 지시가 없었음에도 3번 모두 같은 위치가 선택되었습니다. A의 3번째에서는 보고서에 '순서는 제 판단입니다'라고 적혀 있었습니다.
이번 A의 3회에서는 컴파일러와 CLAUDE.md가 제약하는 사항은 완료 조건을 작성하지 않아도 지켜졌습니다.
차이가 나타난 것은 테스트와 검증
차이가 나타난 부분은 컴파일러도, CLAUDE.md도 관여하지 않은 부분이었습니다.
| A (한 글자) 3회 | B (완료 조건 있음) 3회 | |
|---|---|---|
| 추가한 테스트 | 건별 | 3~5건 |
순서 테스트 (PeriodPresetsTest) | 3번 모두 없음 | 3번 모두 신규 작성 |
| 윤년의 2월 | 3번 모두 없음 | 3번 모두 있음 |
| 세션 중 lint | 3번 모두 실행 안 함 | 3번 모두 마지막에 1회 |
| 보고서의 '확인하지 못한 것' | 2회는 실제 기기에서 미확인이라고 본문에서 언급함 | 3번 모두 절을 나누어 나열 |
덧붙여, B의 보고서는 3번 중 2번이 영어로 돌아왔습니다(지시문은 일본어였습니다).
A의 테스트는 모두 '이번 달 안은 포함하고, 전후 월은 포함하지 않는다'를 확인하는 1건만었습니다. 그중 1회에서는 말일을 5/31 00:00으로 검증했습니다.
// A의 2번째가 추가한 테스트 (발췌)
assertTrue(period.matches(millis(LocalDate.of(2026, 5, 31)), now))
assertFalse(period.matches(millis(LocalDate.of(2026, 4, 30)), now))
이로는 범위가 월말의 0시에서 끝나버리는 구현이라도 테스트가 통과해 버립니다. 말일의 마지막 1밀리초가 포함되는지는 확인하지 않았습니다.
반면 B는 조건에 적은 경계를 그대로 테스트로 삼았습니다. 2번째 보고서에는 다음과 같은 설명이 있었습니다.
경계 값은, 테스트 대상과 동일한 dayEnd
사용하지 않고 '다음 달 1일 0시 - 1밀리초'로 별도로 계산하고 있습니다. 같은 계산을 사용하면, 구현이 잘못되어도 테스트가 통과해 버리기 때문입니다.
기대값을 구현과 동일한 함수로 계산해버리면, 그 함수가 틀려도 테스트는 통과해 버립니다. 이는 조건에 쓰여 있지 않았습니다. 다만 B의 3번 모두가 그런 것은 아니었고, 3번째는 테스트에서도 dayEnd를 사용하고 있었습니다.
lint에 대해서는 지난 기사의 결과와 함께 살펴보겠습니다. 지난번에는 CLAUDE.md의 실행 절차 부분에 적었을 때, 실행되는 경우와 그렇지 않은 경우가 있었습니다. 이번 A는 CLAUDE.md에 lint 지시가 없어 3번 모두 실행되지 않았습니다. 반면, 지시문의 완료 조건에 적은 B에서는 3번 모두 마지막에 한 번씩 실행되었습니다. 다만 B는 완료 조건 외에도 진행 방식이나 보고서 작성 방법까지 동시에 지정하고 있기 때문에, 어느 부분이 영향을 미쳤는지 분리하기 어렵습니다. CLAUDE.md의 '완료 조건' 부분에 쓰는 방법도 시도해 보지 않았습니다. 검증 횟수가 적어 확실한 것은 말할 수 없지만, 반드시 통과시키고 싶은 검증은 지시문의 완료 조건에 작성하는 것을 권장합니다.
사양서가 없을 때의 동작
MyntPack의 CLAUDE.md에서는 구현 전에 docs/local/의 사양서를 읽도록 지시하고 있습니다. 이 디렉터리는 Git 관리 밖에 있기 때문에, clone한 리포지토리에는 없습니다.
A의 3번 모두에서 사양서가 없다는 것을 인지하고, '이번 달 것'이 현재 개발 범위에 포함되는지 확인해 달라고 보고 마지막에 적었습니다. 작업 도중에 멈춰서 물어보는 것이 아니라, 구현을 마친 후에 알려주고 있습니다. B에서는 조건에 '읽지 않고 진행해도 좋다'고 쓰었기 때문에, 확인해 달라는 요청은 나오지 않았습니다 (3번째는 사양서와 대조하지 않은 것을 '확인하지 못한 것'으로 언급했습니다).
한마디의 요청이라도 판단하기 어려웠던 내용이 보고에 남는 것은 도움이 됩니다. 다만, 확인이 필요한 사항이 보고 끝부분으로 밀려나기 때문에, 건너뛰면 알아차리기 어렵습니다.
계획을 먼저 내보내게 하기
B의 지시문에서는 구현 전에 계획만 출력하게 하는 경우도 시도했습니다. --permission-mode plan으로 계획을 출력하게 하고, 그 세션을 --resume으로 재개하여 '계획대로 진행해 주세요'라고 부탁합니다.
claude -p "$(cat prompt-B.txt)" --permission-mode plan --output-format stream-json --verbose
claude -p "계획대로 진행해 주세요." --resume <세션ID> --permission-mode acceptEdits
계획에는 변경할 5개의 파일과, 코드를 읽고 파악한 내용이 적혀 있었습니다.
Period로 분기하는 것은 Period.kt 안의 한 곳뿐입니다. Kind로 분기하는 것은 PeriodSelectScreen.kt의 labelOf() 뿐입니다. 둘 다 추가가 빠질 만한 다른 곳은 없습니다.
구현에 들어가기 전에, 여기를 읽고 확인합니다. 건드릴 파일이 예상대로인지, 놓치고 있는 분기가 없는지, 테스트로 무엇을 확인할 것인지. 이번 계획에는, 완료 조건에 쓰지 않았던 '현재가 월초 정각/월말 정각일 때도 당월이 되는 것'이라는 테스트도 포함되어 있었습니다. 구현 후의 차이점에서는 이 경우가 가장 많은 테스트를 거쳐 6건이었습니다.
계획 단계에서 오류를 발견하면, 차이점을 읽는 수고나 되돌리는 수고가 들지 않습니다. 계획에는 파일 목록과 테스트 방침이 나란히 적혀 있어, 차이점보다 짧은 시간에 읽을 수 있습니다.
보고서를 맹신하지 않기
B의 보고서는 확인한 것과 확인하지 않은 것이 구분되어 있어서 읽기 쉽지만, 쓰여진 내용이 모두 정확하다고 할 수는 없습니다. 2번째 보고서에는 다음 문장이 있었습니다.
편집할 때마다 움직이는 훅(hook)이 먼저 빌드했기 때문이라고 생각하지만, 훅의 내용은 보지 못했습니다.
실제로는 이 실험 로그에 hook이 동작한 기록이 없습니다. 애초에 지난 기사의 hook은 ktlint로 포맷만 하는 것이고, 빌드는 하지 않습니다. 컴파일과 lint 분석이 UP-TO-DATE가 된 것은 직전 회(B의 1회차)에서 같은 본체 코드를 빌드한 출력이 app/build
남아있었기 때문입니다. 두 번의 본체 코드 차이점이 한 글자도 같다는 것은 저희가 확인했습니다. Claude Code는 '훅 내부 내용은 보지 않는다'고 추측임을 명시하고 있으므로, 추측과 사실을 구분하여 읽으면 곤란할 일은 없습니다. 보고서의 '확인한 것' 부분 역시 그 근거가 적혀 있는지 확인하면서 읽습니다.
요약하자면
- 구현 누락은 컴파일러(포괄적인
when)와CLAUDE.md의 규칙으로 막을 수 있는 부분이 크다. 이번에는 한마디 요청만으로도 기능적으로 같은 구현을 얻었다. - 테스트 내용과, 그 수정으로 반드시 통과시켜야 하는 검증은, 이번에는 지시문에 완료 조건으로 작성한 3번에서 갖추어졌다.
- 차이가 난 부분은, 지시문 중 테스트 내용・검증・보고서 작성 방식에 해당하는 부분이었다. 구현의 도달점에 해당하는 부분은 한마디 요청과 결과가 변하지 않았다. 다만 지시문을 한데 모아 주었기 때문에, 어느 줄이 효과를 발휘했는지는 분리하여 확인하지 못했다.
- 계획을 먼저 내보내게 하면, 건드릴 파일과 테스트 방침을 구현 전에 확인할 수 있다.
3번씩 비교한 것이라 통계적인 주장은 할 수 없습니다. 그럼에도 불구하고, 완료 조건을 작성하는 수고는 몇 줄입니다. 테스트를 다시 읽어보고 '월말의 마지막 1밀리초가 빠져있다'고 되돌려 받는 것보다, 미리 적어두는 편이 더 빠릅니다.
다음에 읽을 것
다음 기사에서는 도착한 차이분으로 무엇을 확인할지, 실제 리뷰에서 발견된 오류와 함께 다룰 예정입니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기