Claude Code에게 “이 기능 구현해줘”라고 한 문장만 던지면 동작하는 코드는 나온다. 처음에는 그럴듯해 보이지만 엣지 케이스 한두 개만 추가해도, 유효하지 않은 입력이 한 번만 들어와도 쉽게 무너진다. CodeCraft 유형의 설계 문제는 코드를 치기 전에 다섯 단계로 먼저 분해해야 한다. 이 글은 Claude Code CodeCraft 문제 해결 5단계 루틴과, 그중 첫 단계인 Explore에서 반드시 짚어야 하는 입력·출력·제약·변경 가능성 분석을 정리한다
CodeCraft 5단계 루틴은 어떻게 구성되는가
CodeCraft 문제는 어떤 주제가 나오든 동일한 다섯 단계로 풀어낼 수 있다. 단계마다 산출물이 달라지고, 앞 단계의 결과가 다음 단계의 입력이 된다
Explore → Brainstorm → Plan → Implement → Verify 요구·제약 설계 옵션 구조화 구현 검증
각 단계가 다루는 범위는 다음과 같다
| 단계 | 목적 | 산출물 |
|---|---|---|
| Explore | 요구사항·제약·입출력 정의 | 분석 노트, 모호함 제거 |
| Brainstorm | 설계 옵션 비교 | 옵션별 trade-off |
| Plan | 선택한 설계 구조화 | 도메인 객체와 책임 분리 |
| Implement | 구조에 맞춘 코드 작성 | 테스트 가능한 구조 |
| Verify | 요구사항 충족 증명 | 해피 패스·엣지·예외 검증 |
Verify는 단순히 “코드가 돈다”가 아니다. 요구사항을 만족하는지 증명하는 단계다. 해피 패스, 엣지 케이스, 유효하지 않은 입력, 성능, 확장성까지 분리해서 확인한다
왜 바로 코딩하면 무너지는가
DoorDash의 driver payout calculator를 예로 들어보자. 드라이버가 수행한 배달 기록을 받아 정산 금액을 계산하는 기능이다. 누군가는 다음과 같이 한 줄짜리 프롬프트로 Claude Code에 작업을 맡긴다
Build a service that calculates payout for each dasher.
Claude Code는 즉시 Driver, DeliveryRecord, PayoutCalculator 같은 클래스를 만든다. 코드가 컴파일되고 테스트도 통과한다. 그러나 이 코드는 결정되지 않은 것이 너무 많은 위에 서 있다.
같은 드라이버에게 여러 배달 기록이 들어오면 어떻게 합산하는가. 금액은 float인가 decimal인가. 음수 거리나 다른 드라이버 소속의 기록이 섞여 들어오면 어떻게 거를 것인가. 보너스와 프로모션은 지역·시간대별로 어떻게 달라지는가. 이런 질문에 답하지 않은 채 만들어진 코드는 요구사항이 한 칸만 움직여도 무너진다.
문제는 프롬프트가 짧아서가 아니다. 문제의 입력·출력·제약·변경 가능성을 사람이 먼저 분해하지 않았기 때문이다. 모호한 요구사항을 그대로 넘기면 Claude Code는 모호한 가정 위에 코드를 쌓는다
Explore의 출발점은 기능 요구사항 분석이다
기능 요구사항은 “시스템이 반드시 해야 하는 행동”이다. 추상적으로 적으면 의미가 없다. driver payout calculator를 다시 보면, “payout을 계산한다” 정도로는 부족하다. 행동을 더 작은 단위로 쪼개야 한다. driver payout calculator가 실제로 해야 하는 행동은 다음과 같이 분해된다.
- 배달 기록(delivery record)을 입력으로 받는다
- 각 배달 기록에 대한 payout을 계산한다
- 같은 드라이버에 속한 기록만 합산한다
- 드라이버별로 총 payout을 반환한다
- 잘못된 거리, 음수 금액, 다른 드라이버 소속 기록 등 잘못된 레코드를 처리한다
이 다섯 줄을 프롬프트에 함께 넣어주면 Claude Code의 산출물이 달라진다. 입력 처리, 계산, 합산, 검증이라는 책임이 자연스럽게 분리된 구조로 나온다. 기능 요구사항을 찾을 때 던지는 질문은 다섯 개로 압축된다
- 사용자는 이 기능으로 무엇을 하려고 하는가
- 시스템이 반드시 해야 하는 행동은 무엇인가
- 반드시 지원해야 하는 흐름(happy path)은 무엇인가
- 실패하거나 예외가 되는 상황은 무엇인가
- 지금 구현할 범위와 나중에 확장될 범위는 어디서 나뉘는가
입력은 시스템 외부에서 들어오는 모든 것이다
모든 시스템은 입력 → 로직 → 출력의 구조를 갖는다. 입력 분석은 “무엇이 시스템에 들어오고, 잘못된 입력에 어떻게 대응할 것인가”를 정리하는 단계다. 발견하지 못한 가정은 나중에 버그가 된다. 입력을 분석할 때 점검하는 항목은 다음과 같다.
- 입력 타입과 데이터 구조
- 필수 필드와 선택 필드의 구분
- 데이터 출처(API, DB, 메시지 큐 등)
- 유효성 조건(범위, 형식, 비즈니스 규칙)
- 중복 가능성과 멱등성(idempotency) 요구
- 이미 정렬되어 들어오는지, 직접 정렬해야 하는지
- null·빈 컬렉션의 처리 방식
driver payout calculator의 입력은 배달 기록 리스트다. 한 건의 배달 기록 안에는 드라이버 ID, 기본 요금, 이동 거리, 팁, 지역 정보가 들어 있다. 거리는 음수가 될 수 있는가, 팁은 비어 있을 수 있는가, 지역 정보가 누락된 레코드는 어떻게 다룰 것인가. 이 질문 하나하나가 검증 규칙으로 코드에 박힌다
출력은 시스템이 외부로 내보내는 형태와 약속이다
출력 분석은 결과를 어떤 형태로 돌려줄지 정하는 단계다. 입력과 마찬가지로 명확하지 않으면 테스트가 흔들린다. 출력을 정의할 때 결정해야 하는 항목은 다음과 같다.
- 반환 타입(단일 숫자, 리스트, DTO 등)
- 정렬 기준
- 금액 단위와 통화 표기
- 라운딩 규칙(반올림·내림·은행원 반올림 등)
- 에러를 반환하는 방식(예외, 결과 객체, 상태 코드)
- 빈 입력에 대한 출력 형태
driver payout calculator라면 “드라이버별 총 payout의 리스트”가 출력일 수 있다. 금액은 float가 아닌 decimal 또는 cent 단위 integer로 다루는 편이 정확성에서 유리하다. 빈 입력이 들어오면 빈 리스트인지, 예외인지도 미리 정한다
제약 분석이 설계 방향을 바꾼다
제약은 설계의 마지막을 결정한다. 같은 문제도 제약이 무엇이냐에 따라 완전히 다른 구조로 풀린다
| 제약 유형 | 대표 예시 | 설계에 미치는 영향 |
|---|---|---|
| 정확성 | 금액 계산, 회계 | float 금지, decimal/integer 사용 |
| 성능 | 뉴스피드 랭킹 | 사전계산·캐시·인덱스 도입 |
| 일관성 | 결제 재시도 | idempotency 키, 멱등 연산 |
| 보안·권한 | 사용자 데이터 접근 | 인가 계층 분리, 감사 로그 |
| 확장성 | 트래픽 급증 | 비동기·샤딩·큐잉 |
| 테스트 가능성 | 모든 도메인 | 의존성 주입, 순수 함수화 |
driver payout calculator에서는 정확성이 가장 앞선다. float로 누적하면 소수점 오차가 쌓여 정산 분쟁이 생길 수 있기 때문에, 처음부터 decimal 또는 cent 단위 integer로 가져간다. 뉴스피드 랭킹이라면 성능과 최신성이 앞서고, 결제 재시도라면 같은 결제가 두 번 실행되지 않도록 보장하는 멱등성이 앞선다
변경 가능성이 디자인 패턴을 부른다
변경 가능성이 높은 부분은 미리 분리 후보로 표시해두는 것이 핵심이다. 분리할지 말지는 그 다음 결정이다. 변경 가능성이 높은 자리는 보통 다음과 같다.
- 정책(payout 규칙, 할인 정책, 권한 정책)
- 계산 공식(지역·시간대별로 갈리는 공식)
- 데이터 출처(DB → 외부 API로 바뀔 가능성)
- 출력 포맷(JSON, CSV, PDF 등 다중 포맷)
- 외부 호출(결제 게이트웨이, 알림 채널 등)
driver payout calculator의 payout 규칙이 항상 하나라면 단순 함수 하나로 충분하다. 그러나 지역별 보너스, 시간대별 프로모션, 이벤트성 할증이 추가되는 순간 단순 if-else는 빠르게 무너진다. 이 시점에 Strategy 패턴이나 정책 객체로 분리한다
| 접근 | 단순 구현 | 패턴 기반 구현 |
|---|---|---|
| 초기 비용 | 낮다 | 약간 더 든다 |
| 규칙 추가 | if-else 분기 증가 | 새 정책 객체 추가 |
| 테스트 | 분기마다 시나리오 늘어남 | 정책 단위로 격리 가능 |
| 적합한 상황 | 규칙이 거의 안 바뀜 | 규칙이 자주 바뀌거나 늘어남 |
처음부터 모든 곳에 패턴을 끼우면 과설계다. 변경 가능성이 확인된 자리에만 패턴을 넣는다는 기준이 안전하다. 디자인 패턴을 모르더라도, “여기는 자주 바뀔 자리”라는 표시만 남겨두면 Claude Code에게 적절한 분리 방식을 추천받을 수 있다
요구사항 분석 템플릿으로 Claude Code에 넘긴다
위 다섯 항목을 매번 다시 찾아 적기는 번거롭다. CodeCraft 문제를 받았을 때 그대로 채울 수 있는 분석 템플릿을 미리 갖춰두면 Harness Engineering의 입력으로 바로 쓸 수 있다
# 요구사항 분석 ## 기능 요구사항 - 시스템이 반드시 해야 하는 행동: - Happy path: - 예외 상황: ## 입력 - 입력 타입: - 필수 필드: - 선택 필드: - 데이터 출처: - 유효성 조건: - 중복 가능성: - 정렬 여부: - null / empty 처리: ## 출력 - 반환 타입: - 정렬 기준: - 단위·라운딩 규칙: - 에러 반환 방식: - 빈 입력 처리: ## 제약사항 - 정확성: - 성능: - 일관성: - 보안·권한: - 확장성: - 테스트 가능성: ## 변경 가능성 (분리 후보) - 정책: - 계산 공식: - 데이터 출처: - 출력 포맷: - 외부 호출:
이 템플릿을 채운 결과를 Claude Code 프롬프트의 컨텍스트로 넣어주면 산출물의 질이 달라진다. “각 dasher의 payout을 계산하는 서비스를 만들어줘”가 아니라, 입력·출력·제약·변경 가능성이 명시된 요구사항을 함께 전달하는 방식이다. Claude Code는 같은 모델이라도 분석된 입력 위에서는 책임이 분리된 구조를 만든다
Brainstorm·Plan·Implement·Verify는 분석 위에 얹는다
분석이 끝나면 나머지 네 단계는 빠르게 이어진다. Brainstorm에서는 설계 옵션 두세 가지를 나란히 두고 장단점을 비교한다. 하나의 정답을 찾는 단계가 아니라, 현재 요구사항과 제약에 맞는 합리적인 선택을 가려내는 단계다. Plan에서는 선택한 설계를 도메인 객체와 책임 단위로 쪼갠다. driver payout calculator라면 DeliveryRecord는 데이터를 표현하고, PayoutPolicy는 규칙을 캡슐화하고, PayoutCalculator는 계산을 조율하는 식이다. 변경 가능성이 높다고 표시한 자리는 인터페이스로 분리한다. Implement에서는 언어, 클래스, 메서드, 예외 처리, 데이터 타입을 결정해 코드로 옮긴다. 테스트 가능한 구조를 유지하는 것이 가장 중요한 기준이다. Verify에서는 해피 패스부터 엣지 케이스, 유효하지 않은 입력, 성능, 확장성까지 분리해서 확인한다. “동작한다”가 아니라 “요구사항을 만족한다”를 증명하는 자리다
정리
CodeCraft 문제는 코드를 치기 전에 다섯 단계로 분해한다. 그중에서도 Explore의 입력·출력·제약·변경 가능성 분석이 산출물의 품질을 결정한다. Claude Code에 한 문장 프롬프트로 일을 시키는 대신, 이 네 가지가 채워진 분석 노트를 함께 넘기는 것이 Harness Engineering의 출발점이다
출처 – 인프런 [Claude Code Harness Engineering 클로드코드 심화 CLI 하네스 엔지니어링 실무]