우리가 클로드(Claude)로 코드를 배포하며 얻은 교훈들 ②
본문은 요즘IT와 번역가 Jane Heo가 함께 디완크 토머(Diwank Tomer)의 글 <Field Notes From Shipping Real Code With Claude>을 번역한 글입니다. 필자인 디완크 토머는 인공지능 분야에서 혁신적인 시스템과 도구를 구축하는 창업자이자 AI 엔지니어입니다. 현재 ‘Julep AI’의 공동 창업자로 지속적인 메모리를 갖춘 AI 에이전트 인프라 구축하고 있습니다. 이 글은 AI 도구를 개발 프로세스에 효과적으로 통합하고자 하는 개발자들에게 실질적인 가이드라인을 제공하며, AI와의 협업을 통해 생산성과 코드 품질을 동시에 향상시키는 방법을 탐색하는 데 유용한 정보를 제공합니다.
필자에게 허락을 받고 번역했으며, 글에 포함된 링크는 원문에 따라 표시했습니다. 글에 포함된 각주(*표시)는 ‘번역자주’입니다. (원문에는 필자의 오디오 파일이 포함되어 있으니 필요하신 분들은 참고 바랍니다.)
*우리가 클로드(Claude)로 코드를 배포하며 얻은 교훈들 ①을 먼저 읽고 오시면 좋습니다.
신성한 원칙: 테스트는 사람이 작성해야 한다
이제 AI 기반 개발에서 가장 중요한 원칙으로 들어갑니다. 이건 너무나도 중요하기 때문에, 여러 방식으로 반복해서 말할 겁니다. 여러분의 기억 속에 완전히 새겨질 때까지요.
절대 AI에게 테스트를 맡기지 마세요.
테스트는 단순히 다른 코드가 잘 작동하는지 확인하는 코드가 아닙니다. 테스트는 실행 가능한 사양(specification)입니다. 당신의 실제 의도, 엣지 케이스, 문제 도메인에 대한 이해가 테스트 코드에 담깁니다. 속도와 안정성을 모두 갖춘 팀이 진짜 실력자입니다. 둘 중 하나만 선택하는 시대는 끝났습니다. 그리고 이 두 가지를 동시에 달성하는 방법이 바로 테스트입니다.

왜 이게 그렇게 중요한지 예를 들어 설명해 보겠습니다. 클로드(Claude)에게 속도 제한기(rate limiter)를 구현해달라고 요청했다고 가정해 봅시다.
class RateLimiter:
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.requests = defaultdict(list)
def is_allowed(self, user_id: str) -> bool:
now = time.time()
user_requests = self.requests[user_id]
# 오래된 요청 제거
self.requests[user_id] = [
req_time for req_time in user_requests
if now - req_time < self.window_seconds
]
if len(self.requests[user_id]) < self.max_requests:
self.requests[user_id].append(now)
return True
return False
겉보기에 괜찮아 보이죠? 클로드(Claude)는 여기에 친절하게 테스트도 생성해 줬습니다.
def test_rate_limiter():
limiter = RateLimiter(max_requests=3, window_seconds=60)
assert limiter.is_allowed("user1") == True
assert limiter.is_allowed("user1") == True
assert limiter.is_allowed("user1") == True
assert limiter.is_allowed("user1") == False # 제한 도달
하지만 클로드(Claude)의 테스트가 놓친 것이 있습니다. 그리고 이것은 비즈니스 요구사항을 실제로 이해하는 사람만이 확인할 수 있는 부분입니다. 클로드(Claude)의 구현에는 메모리 누수(memory leak)가 존재합니다. 한 번 API를 호출하고 다시는 돌아오지 않는 사용자라도, 그 사용자의 데이터는 메모리에 영원히 남아 있게 됩니다. 클로드(Claude)가 생성한 테스트는 정상적인 흐름(happy path)만 검증했을 뿐, 이처럼 중요한 프로덕션 수준의 문제는 완전히 놓쳤습니다.

이것이 바로 왜 테스트는 사람이 직접 작성해야 하는지를 보여주는 결정적인 이유입니다. 우리는 컨텍스트, 프로덕션 환경, 그리고 중요한 엣지 케이스를 이해합니다. 줄렙(Julep)에서는 이 원칙이 절대적입니다.
## 테스트 규율 (Testing Discipline)
| 항목 | AI가 해도 되는 것 | AI가 절대 하면 안 되는 것 |
|-------|---------------------|------------------------------|
| 구현(Implementation) | 비즈니스 로직 생성 | 테스트 파일 수정
| 테스트 기획(Test Planning) | 테스트 시나리오 제안 | 테스트 코드 작성
| 디버깅(Debugging) | 테스트 실패 분석 | 테스트 기댓값 변경
AI가 테스트 파일을 수정하는 순간, 해당 PR은 무조건 반려됩니다. 예외는 없습니다.
테스트는 사양(specification)입니다. 테스트는 안전망입니다. 테스트는 지금까지 고친 모든 버그, 발견한 모든 엣지 케이스의 지혜가 담긴 기록입니다. 그 테스트들을 반드시, 철저하게 지키십시오.
물에 빠지지 않고 확장하기: 토큰 경제학과 컨텍스트 관리
AI 보조 개발에서 가장 직관에 반하는 교훈 중 하나는, 토큰을 아끼기 위해 컨텍스트를 아끼는 것이 결국 더 많은 비용을 초래한다는 점입니다. 이는 마치 기름값을 아끼려고 자동차 연료탱크를 반만 채우는 것과 같습니다. 결국 주유소를 더 자주 가게 됩니다.
토큰 예산은 중요합니다. 핵심에 집중된 프롬프트를 제공하고, diff(변경사항)의 길이를 줄이며, 목적을 미리 요약해서 대용량 파일로 인한 부하를 피해야 합니다. 그러나 여기서 “집중”이란 “최소한”이 아니라 “관련 있고 완전함”을 의미합니다.
다음은 컨텍스트가 부족한 프롬프트가 낳는 헛된 절약의 사례입니다.
컨텍스트가 부족한 프롬프트 시도:
“사용자 엔드포인트에 캐싱을 추가해 줘”
클로드(Claude)의 응답: 캐싱을 구현함… 하지만:
- 인메모리 캐시 사용 (서버 여러 대에서는 작동하지 않음)
- 캐시 무효화 전략 없음
- 메트릭 또는 모니터링 없음
- 캐시 스탬피드(Cache stampede)에 대한 고려 없음
결과: 수정 작업 3회, 사용된 토큰 4배
적절한 컨텍스트가 포함된 프롬프트:
“GET /users/{id} 엔드포인트에 Redis 캐싱을 추가해 줘.”
컨텍스트:
- 이 엔드포인트는 분당 5만 건의 요청을 처리함
- 로드 밸런서 뒤에 12개의 API 서버가 있음
- 사용자 데이터는 자주 변경되지 않음 (하루에 몇 번 수준)
- 이미 Redis가 cache.redis.internal:6379에 있음
- 표준 캐시 키 패턴은 "user:v1:{id}"
- 캐시 히트/미스 메트릭 포함 (Prometheus 사용 중)
- 캐시 어사이드(Cache-aside) 패턴, TTL은 1시간
- 캐시 스탬피드는 확률 기반 조기 만료 방식으로 처리
참고 자료: docs/patterns/caching.md(캐싱 가이드 문서)
교훈: 컨텍스트를 앞에 충분히 제공하면 반복적인 수정 사이클을 피할 수 있습니다. 토큰을 좋은 도구에 투자한다고 생각하세요. 초기 비용은 결국 몇 배로 보상됩니다.
실제로 저는 모든 프로젝트가 클로드(Claude)에게 코드베이스 변경 내용을 정기적으로 검토하게 하고, 그 내용을 CLAUDE.md에 추가하게 할 것을 추천합니다.
새로운 세션과 멘탈 모델
여기 또 하나의 직관에 반하는 실천법이 있습니다. 각기 다른 작업에는 항상 새로운 클로드(Claude) 세션을 사용하라는 것입니다. 하나의 긴 대화를 계속 이어가는 것이 매력적으로 느껴질 수 있지만, 그렇게 하면 컨텍스트 오염이 발생합니다.
이렇게 생각해 보세요. 생닭을 자른 도마로 바로 채소를 썰지는 않겠죠. 마찬가지로 프론트엔드 스타일링을 논의한 클로드(Claude) 세션을 그대로 사용해서, 데이터베이스 마이그레이션 작업을 요청해서는 안 됩니다. 이런 식으로 컨텍스트가 미묘하게 섞여버립니다.
우리의 규칙은 간단합니다. 작업 하나에 세션 하나. 작업이 끝나면 새로운 세션을 시작하세요. 이렇게 하면 클로드(Claude)의 “정신 모델”이 깔끔하고 집중된 상태로 유지됩니다.
사례 연구: 프로덕션 환경에서 구조화된 오류 처리 방식 적용하기
이번에는 우리가 줄렙(Julep)에서 실제로 수행한 리팩토링 사례를 소개하며, 프로덕션 규모의 바이브 코딩(vibe-coding)이 어떤 식으로 이루어지는지 보여드리겠습니다. 우리 팀은 기존의 임시방편 오류 처리 방식을 500개 이상의 엔드포인트에 걸쳐 구조화된 오류 계층(hierarchy)으로 전면 교체해야 했습니다.
사람이 결정한 부분(왜 그렇게 했는가)
우선 오류 분류 체계(error taxonomy)를 정의해야 했습니다. 이건 전적으로 아키텍처 설계 작업이며—Claude가 대신 결정해 줄 수 없는 부분입니다. 왜냐하면 이는 우리 비즈니스, 사용자, 운영 요구 사항에 대한 깊은 이해를 필요로 하기 때문입니다.
# SPEC.md – 오류 계층 설계 (사람이 작성)
## 오류 처리 철학
-클라이언트 오류(4xx)는 행동 가능한 피드백을 반드시 포함해야 함
-시스템 오류(5xx)는 디버깅을 위한 트레이스 ID를 반드시 포함해야 함
-모든 오류는 JSON 직렬화 가능해야 함
-오류 코드는 안정적이어야 함 (클라이언트가 해당 코드에 의존함)
## 오류 계층 구조
BaseError
├── ClientError (4xx)
│ ├── ValidationError
│ │ ├── SchemaValidationError - 요청이 스키마와 맞지 않음
│ │ ├── BusinessRuleError - 스키마는 유효하지만 비즈니스 논리에 위배
│ │ └── RateLimitError - 요청 과다
│ └── AuthError
│ ├── AuthenticationError - 당신은 누구인가?
│ └── AuthorizationError - 그 작업은 할 수 없음
└── SystemError (5xx)
├── DatabaseError - 연결 실패, 타임아웃, 데드락
├── ExternalServiceError - API 또는 웹훅 실패
└── InfrastructureError - 디스크 용량 부족, OOM 등
## 오류 응답 포맷
{
"error": {
"code": "VALIDATION_FAILED", // 클라이언트를 위한 안정적인 오류 코드
"message": "Email already exists", // 사람이 읽을 수 있는 메시지
"details": { ... }, // 구조화된 추가 정보
"trace_id": "abc-123-def" // 디버깅용 트레이스 ID
}
}
AI의 실행(어떻게 구현했는가)
사양(SPEC.md)이 명확해지자, 이제 반복적이고 기계적인 리팩토링 작업은 클로드(Claude)에게 맡겼습니다.
### 클로드(Claude)에게 준 프롬프트:
우리의 오류 처리 로직을 SPEC.md에 맞게 리팩토링해 줘.
현재 상태:
raise ValueError("Invalid email")
return {"error": "Something went wrong"}, 500
목표 상태:
SPEC.md에 정의된 오류 계층 구조 사용
적절한 오류 코드 포함
모든 5xx 오류에 trace_id 추가
auth 모듈부터 시작해 줘. 구현에 들어가기 전에 먼저 계획을 보여줘.
Claude의 계획은 탄탄했습니다.
- common/errors.py에 에러 계층 구조를 생성
- 에러 응답 포매터를 생성
- 각 모듈을 체계적으로 업데이트
- 에러 처리 미들웨어를 추가
Claude는 500개 이상의 에러 지점을 찾아 업데이트하는 지루한 작업을 수행하는 동안, 우리는 리뷰에만 집중할 수 있었습니다.
# 이전 (Claude가 찾은 패턴):
if not user:
raise Exception("User not found")
# 이후 (Claude의 리팩토링):
if not user:
raise AuthenticationError(
message="User not found",
code="USER_NOT_FOUND",
details={"identifier": email}
)
철저히 작성된 CLAUDE.md 파일, 세심한 문서, 정기적으로 업데이트된 앵커 코멘트, 명확한 지침과 결합된 결과:
- 소요 시간: 2일 대신 4시간
- 적용 범위: 500개 이상의 모든 에러 지점 업데이트
AI 시대의 리더십과 문화
시니어 엔지니어로서 당신의 역할이 근본적으로 변화했습니다. 이제 단순히 코드를 작성하는 것을 넘어, 지식을 선별하고 경계를 설정하며, 사람과 AI 시스템 모두에게 효과적으로 협업하는 방법을 가르치는 역할을 수행하게 되었습니다.
린(Lean) 관리 및 지속적 배포(Continuous Delivery) 관행은 소프트웨어 전달 성능을 향상시키고, 이는 조직의 전체적인 성과 개선으로 이어집니다. 여기에는 AI 협업을 어떻게 관리하느냐도 포함됩니다.
새로운 온보딩 체크리스트
새로운 개발자가 우리 팀에 합류하면, 사람을 위한 트랙과 AI와 협업을 위한 트랙, 두 가지 온보딩 루트를 제공합니다. 다음은 통합 체크리스트입니다.
1주 차: 기초
- 팀의 CLAUDE.md 파일 읽기 (먼저 루트, 그다음 서비스별)
- 개발 환경 설정
- 첫 번째 PR 생성(사람이 작성, AI 사용 금지)
2주 차: AI 협업 가이드
- 팀 템플릿을 사용해 클로드(Claude) 설정
- AI 도움으로 “토이 문제” 해결
- 프롬프트 패턴 연습
- 첫 번째 AI 지원 PR 생성(감독하에)
3주 차: 독립 작업
- 첫 번째 주요 AI 지원 기능 배포
- 다른 개발자가 만든 AI 결과물에 대한 테스트 작성
- 한 번의 코드 리뷰 세션 주도
투명성 문화를 구축하기
필수적인 문화적 변화 중 하나는 AI 지원 사실을 공개하는 것을 일상화하는 것이죠. 우리는 AI 사용을 숨기려는 것이 아니라, 책임감 있게 사용하려는 것입니다. AI 작업이 포함된 모든 커밋 메시지에는 태그를 붙입니다.
# feat/fix/docs: <설명> [AI]?
#
# [AI] - AI 지원 비중이 많음 (>50% 생성)
# [AI-minor] - AI 지원 비중이 적음 (<50% 생성)
# [AI-review]- 코드 리뷰 용도로만 AI 사용
#
# 예시:
# feat: 사용자 서비스에 Redis 캐싱 추가 [AI]
#
# AI가 캐시 구현과 Redis 클라이언트 설정을 생성했습니다.
# 저는 캐시 키 구조를 설계하고 모든 테스트를 작성했습니다.
# 캐시 무효화 로직이 제대로 작동하는지 수동으로 검증했습니다.
이러한 투명성은 여러 목적을 제공합니다.
- 리뷰어가 추가로 주의해야 할 부분을 알 수 있습니다.
- 향후 디버거가 코드의 출처를 이해할 수 있습니다.
- 도구 사용에 대해 부끄러움을 느끼는 사람이 없습니다.
개발자들이 두려움이나 부끄러움 없이 AI를 효과적으로 활용할 수 있는 환경을 조성하는 것은 고성능 문화를 구축하는 일부입니다.
Claude가 절대 건드리면 안 되는 것들
경계를 명확히 합니다. 이 사항은 제안이 아니라 명령입니다. 이를 어기면 큰 문제가 발생합니다.
절대 건드리지 말아야 할 신성한 리스트
Test Files
# 이곳은 신성한 영역입니다
# AI는 결코 통과할 수 없습니다
def test_critical_business_logic():
"""이 테스트는 1천만 달러 가치의 도메인 지식을 담고 있습니다"""
pass
테스트는 인간의 이해를 담고 있는 안전망이자 명세입니다. 클로드(Claude)가 테스트를 작성하면, 단지 코드가 "작성된 대로" 동작하는지만 확인할 뿐, "코드가 의도한 대로 동작하는지"는 검증하지 못합니다.
Database Migrations
-- migrations/2024_01_15_restructure_users.sql
-- AI가 이걸 만지지 못하게 하세요
-- 한 번 잘못 건드리면 = 데이터 손실 = 경력 손실
ALTER TABLE users ADD COLUMN subscription_tier VARCHAR(20);
UPDATE users SET subscription_tier = 'free' WHERE subscription_tier IS NULL;
ALTER TABLE users ALTER COLUMN subscription_tier SET NOT NULL;
마이그레이션은 운영 환경에서 되돌릴 수 없습니다. 데이터 패턴, 배포 타이밍, 롤백 전략 등을 이해하지 못하면 AI는 절대 안전하게 다룰 수 없습니다.
Security-Critical Code
# auth/jwt_validator.py
# 보안 검토된 코드 – 오직 사람 눈만
def validate_token(token: str) -> Optional[UserClaims]:
# 이 모든 코드는 보안팀의 승인을 거쳤습니다
# 변경 시 반드시 보안팀 승인이 필요합니다
# AI 제안은 여기서 매우 위험합니다
API Contracts Without Versioning
# openapi.yaml
# 이 파일을 깨뜨리면 모든 클라이언트가 깨집니다
# AI는 모바일 앱 릴리스 주기를 이해하지 못합니다
paths:
/api/v1/users/{id}:
get:
responses:
200:
schema:
$ref: '#/definitions/UserResponse' # v2까지는 동결
Configuration and Secrets
# config/production.py
DATABASE_URL = os.environ["DATABASE_URL"] # 절대 하드코딩 금지
STRIPE_SECRET_KEY = os.environ["STRIPE_SECRET_KEY"] # 당연히 환경 변수에서 불러오기
FEATURE_FLAGS = {
"new_pricing": False, # 제품팀 결정 필요
}
AI 오류의 계층 구조
모든 AI 실수가 동일하지는 않으며, 다음과 같이 분류합니다.
1단계: 성가시지만 무해함
- 잘못된 코드 포맷(린터가 잡아줄 것)
- 지나치게 장황한 코드(나중에 리팩터링)
- 최적화가 덜 된 알고리즘(프로파일링으로 개선 가능)
2단계: 수정 비용이 많이 드는 실수
- 내부 API 깨뜨리기(조율 필요)
- 기존 패턴 변경(팀 혼선 초래)
- 불필요한 의존성 추가(프로젝트 비대화)
3단계: 경력에 치명적
- 테스트를 통과시키기 위해 테스트 파일 수정
- API 계약 깨뜨리기
- 비밀 정보나 개인정보 누출
- 데이터 마이그레이션 손상
가드레일은 실수의 심각도에 비례해야 합니다. 1단계 실수는 주니어 교육에 활용하고, 3단계 실수는 LinkedIn 프로필 업데이트를 고민하게 만들어야 합니다.
개발의 미래: 앞으로의 방향
2025년에 이 글을 쓰고 있는 지금, 우리는 AI 지원 개발의 어색한 사춘기에 있습니다. 이 도구들은 강력하지만 서툴러서, 마치 급성장기를 맞은 10대와 같습니다. 그러나 그 궤적은 분명하며 점점 더 가속화되고 있습니다.
좋은 문서는 DevOps 역량을 성공적으로 구현하기 위한 토대입니다. 우수한 성과를 내는 팀은 문서를 코드로 다루며, 테스트 스위트만큼이나 엄격하게 CLAUDE.md 파일을 관리할 것입니다.
예상 도래 순서(대략적인 순서):
- 요청 없이도 개선을 제안하는 선제적 AI
- 팀의 패턴과 선호도를 학습하는 AI
- 세션과 프로젝트 전반에 걸친 지속적 메모리
- 파일 단위를 넘어 전체 코드베이스를 이해하는 AI
그러나 기능이 확장된다 해도 기본은 변함없습니다. 방향을 설정하는 것은 인간이고, 지렛대를 제공하는 것은 AI입니다. 우리는 도구의 사용자일 뿐이며, 이것이 우리가 지금까지 만든 가장 강력한 도구들입니다.
결론: 지금 여기서, 오늘 시작하세요
여기까지 왔다면, 설렘과 두려움이 교차하고 있을 겁니다. 그 반응이 맞습니다. AI 지원 개발은 강력하지만, 규율과 의도를 가지고 접근해야 합니다.
액션 플랜
오늘:
- 현재 프로젝트에 대한 CLAUDE.md 파일 생성
- 까다로운 코드 3곳에 앵커 코멘트 직접 추가
- 적절한 경계 안에서 AI 지원 기능 하나 시도
이번 주:
- 팀과 함께 AI 커밋 메시지 규칙 수립
- 주니어 개발자와 AI 지원 코딩 세션 진행
- AI가 생성한 코드 한 부분에 대한 테스트 작성
이번 달:
- AI 도입 전후 배포 빈도 측정
- 자주 쓰이는 작업에 대한 프롬프트 패턴 라이브러리 제작
- AI 지원 개발에 대한 팀 회고 진행
가장 중요한 것은 시작입니다. 작게, 신중하게 시작하되 반드시 시작하세요. 이 워크플로우를 마스터한 개발자가 반드시 더 똑똑하거나 재능 있는 것은 아닙니다. 그저 더 일찍 시작해서 더 많은 실수로부터 배웠을 뿐입니다. 소프트웨어 전달 성능은 조직 성과를 예측합니다. 속도와 품질이 성공을 결정짓는 이 업계에서 AI 지원은 선택이 아니라 경쟁 필수 요소입니다. 단, 제대로 활용할 때만 그렇습니다.
‘바이브 코딩(vibe-coding)’이라는 이름이 장난스러워 보여도, 이건 진지한 접근법입니다. 인간의 역량을 대체하는 것이 아니라 증폭하는 새로운 사고방식입니다. 이를 익히면 상상 이상으로 빠르고 뛰어난 소프트웨어를 배포할 수 있습니다. 무시하면, 당신이 여전히 보일러플레이트를 치고 있는 동안 경쟁자는 벌써 앞서 나갈 것입니다.
도구는 이미 준비되어 있고, 패턴은 검증되었습니다. 이제 남은 질문은 “당신이 오케스트라를 지휘할 것인가, 아니면 여전히 모든 악기를 직접 연주할 것인가”입니다.
시작할 준비 되셨나요? 기억하세요. 완벽함은 배포의 적입니다. 작은 프로젝트 하나로 시작해 경계를 정하고, 반복하며 나아가세요. 개발의 미래는 이미 도래했지만, 아직 고르게 분포되어 있지 않습니다. 여러분도 그 분포의 일부가 되어보세요.
<참고>
<추천 도서>
- Peter Senge – The Fifth Discipline (2010)
- “Beyond the 70 %: Maximising the Human 30 % of AI-Assisted Coding” (2025년 3월 13일) – Addy Osmani
- Mark Richards & Neal Ford – Fundamentals of Software Architecture, 2판 (2025)
- Nicole Forsgren, Jez Humble, Gene Kim – Accelerate: The Science of Lean Software and DevOps
<원문>
Field Notes From Shipping Real Code With Claude
©위 번역글의 원 저작권은 Diwank Tomer에게 있으며, 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다