CPN 한국어 자습서 · Claude Certified Developer — Foundations Prep
4-7 · 종합 실습
Cumulative Task · Find the Defects and Write the Fix
모듈 4에서는 평가, 테스트와 추적, 실패 처리, 비용과 오케스트레이션, 보안 경계를 한 계층씩 차례로 다뤘습니다. 그런데 실제 프로덕션(실서비스 환경)에서는 문제가 계층별로 하나씩 찾아오는 경우가 드뭅니다. 이 종합 실습은 서로 다른 계층에서 온 결함 세 개를 실행되는 애플리케이션 하나에 함께 심어 두고, 각 결함을 자기 계층으로 짚어 낸 다음 고치는 순서로 진행합니다.
이 장에서 배우는 것What you'll learn
약 15분실행되는 코드 하나에 계층별로 심어 둔 결함 세 개를 읽어내는 법
각 결함을 보안 · 실패 처리 · 평가 세 계층으로 짚어내기
결함이 런타임에 무엇을 일으키는지 한 문장으로 설명하기
결함마다 고친 코드를 쓰고 무엇이 바뀌는지 말하기
세 수정이 각자의 계층에서 서로를 어떻게 보강하는지
지금까지는 결함을 한 계층씩 따로 살펴봤습니다. 평가는 평가대로, 재시도도 재시도대로, 보안 경계는 보안 경계대로요. 하지만 실제 프로덕션에서 마주치는 실패는 이렇게 한 줄로 정렬돼 오지 않는 경우가 많습니다. 여러 계층의 문제가 한 코드 안에 섞여 있는 것이 오히려 보통에 가깝습니다.
아래 애플리케이션은 실행은 됩니다. 다만 서로 다른 계층에서 온 결함 세 개가 하나씩 심어져 있습니다. 먼저 각 결함을 자기 계층으로 짚어 보고, 그다음 결함마다 고친 코드를 씁니다. 목표는 세 결함을 모두 찾아, 고치고, 하나의 버전으로 합치는 것입니다.
RUNNABLE · 3 PLANTED DEFECTS — 실행은 되지만 계층마다 결함이 하나씩
def answer(question, page_url):
page = fetch(page_url) # 신뢰할 수 없는 콘텐츠
notes = read_file("/workspace/input/notes")
write_file(page.suggested_path, summarize(page))
resp = None
for i in range(5):
try:
resp = client.messages.create(model=MODEL, max_tokens=MAX_TOKENS, messages=msg(question))
break
except Exception:
time.sleep(0)
return resp.content[0].text이 실습은 앞에서 계층별로 배운 것을 한자리에 모아 봅니다. 결함을 하나씩 고치는 것보다, 어느 결함이 어느 계층에서 왔는지 먼저 나누는 연습이 핵심입니다.
결함마다 두 가지를 적어 봅니다. 어느 계층에 속하는지, 그리고 런타임에 무엇을 일으키는지 한 문장. 고치는 것은 그다음 단계입니다.
모듈에서 단단하게 다뤄 온 계층들을 하나씩 떠올려 보면 도움이 됩니다 — 평가와 채점, 테스트와 추적, 실패 처리, 비용과 오케스트레이션, 보안 경계. 위 코드를 각 계층의 눈으로 한 번씩 훑어보면, 어느 줄이 그 계층의 규칙을 비워 두고 있는지 드러나는 경우가 많습니다.
예를 들어 보안 경계의 눈으로 보면 write_file 줄이, 실패 처리의 눈으로 보면 재시도 루프가 먼저 눈에 들어옵니다. 세 계층을 각각 대입해 본 뒤, 아래에 직접 진단을 적어 보세요. 다 적었으면 모범 답안과 대조해 봅니다.
MODEL ANSWER · SELF-ASSESS — 원문이 제시한 모범 답안
| 계층 | 결함과 런타임 영향 |
|---|---|
| 보안·가드레일 | write_file이 목적지로 page.suggested_path를 쓰는데, 이 경로는 신뢰할 수 없는 웹 콘텐츠에서 가져온 값입니다. 쓰기 경계를 강제하는 PreToolUse 훅도, 쓸 수 있는 범위를 제한하는 장치도 없습니다. 그 결과 허용된 폴더 밖으로도 파일을 쓰는 임의 경로 쓰기가 됩니다. |
| 실패 처리·비용 | 재시도 루프가 모든 상태를 time.sleep(0)으로 곧바로 다시 시도하고, 영구 오류(400 계열)와 다시 해 볼 만한 오류를 구분하지 않습니다. 호출 한도rate limit — 정해진 시간에 허용되는 요청 수를 넘으면 잠시 막히는 것. 앞에서는 즉시 재시도가 한도를 풀어 주기는커녕 오히려 더 깊게 만듭니다. |
| 평가·테스트 | 평가(eval)가 아예 없습니다. 성공을 수동 데모 세 번으로만 판정했고, 홀드아웃 세트도 채점 사례도 없어서, 프롬프트나 모델이 바뀌었을 때 성능이 떨어졌는지 견줄 기준이 없습니다. |
세 결함을 모두 찾았다면, 빠진 평가 · 백오프 없이 즉시 재시도하는 루프 · 훅 없는 미검증 쓰기 경로, 이렇게 셋을 짚은 것입니다.
이제 각 결함을 고친 버전을 씁니다. 결함마다 고친 코드를 보이고, 무엇이 바뀌는지 함께 적습니다. 대상은 앞에서 본 그 answer() 함수입니다.
세 결함은 각각 다른 계층에서 왔으니, 고칠 때도 계층별로 하나씩 대응하면 됩니다. 보안 경계는 쓰기 경로를 고정하고 훅으로 막고, 실패 처리는 즉시 재시도를 백오프로 바꾸고, 평가는 채점 기준을 마련하는 식입니다. 직접 고친 버전을 아래에 써 본 뒤 모범 답안과 대조해 보세요.
MODEL ANSWER · SELF-ASSESS — 원문이 제시한 모범 답안
def answer(question, page_url):
page = fetch(page_url) # 여전히 신뢰할 수 없음
# 보안: 고정된 쓰기 경로 + PreToolUse 훅이 이를 강제
notes = read_file("/workspace/input/notes") # 범위가 제한된 읽기, 그대로 둠
write_file("/workspace/output/summary.txt", summarize(page))
# (훅이 /workspace/output 밖의 쓰기를 모두 거부하고 기록으로 남김)
# 실패 처리: 백오프, retry-after 존중, 영구 오류는 즉시 실패
resp = call_with_retry(
lambda: client.messages.create(model=MODEL, max_tokens=MAX_TOKENS, messages=msg(question)))
# 평가: answer()는 바뀔 때마다 실행하는 채점된 홀드아웃 세트로 검증됨
return resp.content[0].text각 수정은 자기가 지키는 계층에 자리합니다. 고정된 쓰기 경로와 훅은 도구가 실행되기 전에 보안 경계를 닫고, call_with_retry는 영구 상태를 감지하는 지수 백오프를 더하고, 채점된 홀드아웃 세트는 앞으로 코드가 바뀔 때마다 견줄 기준 점수를 평가에 줍니다.
아래는 방금 짚은 세 결함을 객관식으로 되짚는 문항입니다. 정답을 먼저 떠올려 본 뒤 골라 보세요. 맞히면 설명이 나옵니다.
Q1이 애플리케이션에 심어 둔 결함 세 개는 각각 어느 계층에서 왔을까요?
빠진 평가, 백오프 없이 즉시 재시도하는 루프, 훅 없는 미검증 쓰기 경로 — 각각 평가·테스트, 실패 처리·비용, 보안·가드레일 계층에서 온 결함입니다. 실제 프로덕션 실패가 여러 계층을 가로질러 온다는 것을 한 코드에 모아 보여 주는 과제입니다.
Q2write_file(page.suggested_path, summarize(page))가 보안 결함인 이유는 무엇일까요?
임의 경로 쓰기(path traversal)입니다. 페이지에 /etc나 ~/.aws 같은 경로가 심어져 있으면 허용된 폴더 밖의 파일까지 쓰거나 덮어쓸 수 있습니다. 모범 답안은 쓰기 경로를 고정하고 PreToolUse 훅으로 경계를 강제합니다.
Q3time.sleep(0)으로 5번을 곧바로 재시도하는 루프의 문제는 무엇일까요?
고칠 때는 time.sleep(0)을 지수 백오프로 바꾸고, 다시 해 볼 만한 오류만 재시도하도록 좁힙니다. 모범 답안은 이 처리를 call_with_retry로 감싸 영구 상태를 감지하고 즉시 실패하도록 합니다.
모듈 4의 마지막은 핵심 정리 — 요약(Recap)과 용어 사전(Glossary)입니다. 한국어판이 준비되는 대로 이어집니다. → 4-8 · 핵심 정리
전 코스는 계속 무료입니다. 등록하면 이 코스의 남은 38개 레슨을 끝까지 읽을 수 있습니다.
이미 등록하셨다면 그때 쓰신 이메일을 넣어 주세요.