Devin.KR

핵심 흐름 설명과 최종 검토

85분 안팎

학습 목표

생성부터 완료까지 코드 흐름·외부 의존성·실패 조건을 자기 말로 설명하는 검토서를 작성합니다.

개념

코드를 설명하면 검토 질문이 드러납니다

앱이 실행되는 화면만 보여 주면 빈 제목이나 잘못된 완료 ID에서 어떤 동작을 하는지 설명하기 어렵습니다. 최종 검토서는 요청 한 건이 어디에서 검사되고 어디에서 저장되는지 자신의 문장으로 따라갑니다. 코드를 읽고 책임이 바뀌는 경계를 표시하면 AI 설명의 오류와 빠진 예외 경로를 발견할 수 있습니다. 설명할 수 없는 분기는 검토 완료 대신 질문으로 남깁니다. 이번 레슨은 새 기능 구현보다 현재 앱의 성공과 실패 흐름을 확인하여 읽는 사람에게 전달하는 작업입니다.

생성 요청의 책임 경계를 읽습니다

TaskController.create는 JSON 요청의 title을 받아 TaskService.add에 넘깁니다. 정상 반환은 201과 Task입니다. TaskService는 먼저 TitlePolicy.normalize를 호출하고 통과한 제목으로 번호와 done=false를 만듭니다. TaskRepository.save는 그 항목을 메모리에 저장합니다. Controller는 HTTP 응답, 서비스는 생성 규칙, 정책은 제목 검증, 저장소는 보관 책임을 맡습니다. 각 클래스의 역할을 모두 생성 처리라고 적기보다 실제 호출 순서와 전달하는 값으로 설명하면 어디를 수정해야 할지 보입니다.

빈 제목 실패를 저장 경계까지 따라갑니다

공백만 있는 문자열은 정리 뒤 빈 값이 되어 EMPTY_TITLE 예외가 발생합니다. 이 호출은 Task 생성과 save보다 앞이므로 번호와 목록이 유지됩니다. Controller의 예외 처리기가 400과 error 필드를 반환합니다. 서비스의 apply는 참조 모델과 연결하기 위해 ok 포장을 사용하지만 HTTP 응답에는 그 포장이 없습니다. 두 출력 모양을 같은 것으로 설명하지 않습니다. HttpContractTest.blankPreservesNextId는 실패 후 빈 조회와 다음 생성 번호를 함께 확인하므로 오류 응답 코드만 보는 검사보다 계약에 가깝습니다.

문자열 길이의 단위를 근거로 설명합니다

Java String.length는 UTF-16 코드 단위 수이므로 이모지 길이를 명세의 코드 포인트 수와 다르게 셀 수 있습니다. TitlePolicy는 codePointCount로 정리 뒤 40까지 허용합니다. 구현의 공백 판정은 앞 단계에서 고정한 Python strip 계약을 옮긴 것입니다. 최종 문서에는 화면상 글자 수 제한이라고 적지 않습니다. 40 이모지 허용과 41 코드 포인트 거절 사례를 찾아 이 설명이 테스트와 맞는지 대조합니다. 길이 제한은 구현의 상수를 보고 추측하기보다 spec.md의 입력 계약을 먼저 읽고 확인합니다.

조회 순서는 자료구조와 요구를 연결합니다

TaskRepository는 TreeMap에 정수 ID를 키로 저장하고 values를 목록으로 반환합니다. 현재 ID는 1부터 성공 생성마다 증가하므로 번호 오름차순이 생성 순서와 맞습니다. 제목 A가 Z보다 먼저 정렬되어야 한다는 규칙은 없습니다. Z,A 입력은 두 정렬 기준을 구분하는 반례입니다. ReproductionTest는 이전 제목 정렬 fixture를 검출하고 수정한 저장소에서 [1,2]를 확인합니다. 변경 검토는 자료구조 교체가 순서 요구에 맞는지, 생성·완료와 관련 없는 수정이 들어오지 않았는지 함께 읽습니다.

완료 요청은 경로와 서비스 계약을 구분합니다

HTTP 경로의 id는 문자열이며 Controller가 BigInteger로 읽습니다. 숫자로 읽을 수 없으면 INVALID_ID입니다. 서비스는 정수 종류를 받아 양수인지 검사하고 저장소에 없는 양수는 NOT_FOUND로 돌려줍니다. 문자열 ID를 서비스가 거절한다는 순수 함수 계약과 HTTP의 문자열 경로 처리는 충돌하지 않습니다. 경계를 넘는 변환이 있기 때문입니다. 매우 큰 양수는 404이며 정수 크기 초과를 400으로 오해하지 않습니다. CompletionHttpTest의 hugePositiveIsMissing을 읽으면 이 결정의 근거가 보입니다.

반복 완료와 다른 항목 보존을 확인합니다

Task.completed는 id와 title을 유지하고 done을 true로 만든 새 항목을 반환합니다. 이미 완료한 ID를 다시 호출해도 같은 내용의 성공 결과입니다. 저장소의 같은 키를 갱신하므로 다른 ID 항목을 추가하거나 지우지 않습니다. 두 항목을 만든 뒤 첫 번째를 두 번 완료하는 검사는 반복 응답과 전체 목록을 모두 봅니다. 검토서에는 멱등성이라는 용어만 쓰지 말고 입력을 반복해도 목록의 다른 항목과 번호가 유지된다는 구체적인 동작을 적습니다. 동시성 안전은 이 단일 요청 검사로 주장하지 않습니다.

외부 의존성과 검사의 경계를 표시합니다

Spring MVC는 경로 매핑과 예외 응답을 맡고 Jackson은 JSON 변환에 참여합니다. Maven Wrapper는 빌드 도구 실행을 준비하고 pom은 의존성을 선언합니다. 하네스의 Python은 앱 자체의 HTTP 처리와 별개로 권한과 상태를 검사합니다. 실제 모델 호출은 fixture로 대체합니다. 따라서 검토서에서 모든 처리를 AI SDK가 수행한다고 쓰면 현재 구조와 다릅니다. 직접 읽은 파일, 실행으로 확인한 조건, 의존성 이름을 분리하여 설명합니다. 버전은 전달한 pom을 기준으로 기록합니다.

최종 diff의 비교 기준을 선택합니다

앞 모듈 solution과 이번 제출물을 비교합니다. candidate.patch는 앞 단계의 제공 자료이며 이번 변경 목록이 아닙니다. 이번 변경은 clean_run.py와 복사 계약 검사, 연결 검사 및 evidence 문서 추가로 제한됩니다. Java 앱·기존 테스트·정책 해시는 그대로여야 합니다. scope.json은 새 문서 경로를 추가하기 위해 보완된 전달 범위 장부입니다. 범위를 넓힌 이유를 검토서에 적고 실행 권한 정책은 그대로 유지합니다. 문서 경로 허용과 모델의 도구 권한 확장은 다른 변경입니다.

검토 질문과 승인 상태를 분리합니다

자동 검사가 초록색이어도 실제 사람의 최종 승인 여부를 추론할 수 없습니다. final-review.md 예시는 질문과 근거를 준비하는 자료입니다. 학습자는 코드 줄과 테스트를 읽고 채택 이유, 거절한 제안, 남은 질문을 작성합니다. AI를 사용하지 않았다면 고정 fixture만 썼다고 밝히고 요청 기록을 만들어내지 않습니다. 동료에게 생성 성공, 빈 제목 실패, 중복 완료 세 경로를 설명한 뒤 동료가 재현할 수 있는지 확인합니다. AI 이용을 정직하게 기록하는 더 넓은 원칙은 더 읽기로 연결합니다.

따라하기

입력 순서의 반례를 만듭니다

명세가 요구하는 ID 순서와 제목 순서를 구별합니다.

items=[(1,'Z'),(2,'A')]
print('ID order:', sorted(items,key=lambda x:x[0]))
print('title order:', sorted(items,key=lambda x:x[1]))

실행 결과

ID order: [(1, 'Z'), (2, 'A')]
title order: [(2, 'A'), (1, 'Z')]

길이 단위를 구별합니다

Python으로 같은 문자열의 코드 포인트와 UTF-16 코드 단위 수를 계산합니다. Java의 실제 정책은 TitlePolicy와 해당 테스트를 읽어 대조합니다.

title='😀'*40
print('code points:',len(title))
print('UTF-16 units:',len(title.encode('utf-16-le'))//2)

실행 결과

code points: 40
UTF-16 units: 80

설명 경로를 점검합니다

검토서 작성 전에 요청이 넘는 책임 경계 목록을 만듭니다. 이 출력은 코드 실행 추적이 아닌 문서 작성용 목록입니다.

flows={'create':['TaskController.create','TaskService.add','TitlePolicy.normalize','TaskRepository.save'], 'complete':['TaskController.complete','TaskService.complete','Task.completed','TaskRepository.save']}
for name,path in flows.items():
    print(name+': '+' → '.join(path))

실행 결과

create: TaskController.create → TaskService.add → TitlePolicy.normalize → TaskRepository.save
complete: TaskController.complete → TaskService.complete → Task.completed → TaskRepository.save

앱의 실패 분기를 직접 읽습니다

solution ZIP 루트에서 Controller 예외 매핑과 실제 코드 포인트 길이 조건을 추출합니다. 출력한 코드가 언제 실행되는지 생성·완료 경로를 직접 따라가 flow.md에 적습니다.

python3 - <<'PY'
from pathlib import Path
for name in ['TaskController.java','TitlePolicy.java']:
    for line in Path('src/main/java/lab',name).read_text().splitlines():
        if 'ResponseEntity.status(' in line or 'codePointCount(' in line:
            print(name+': '+line.strip())
PY

실행 결과

TaskController.java: return ResponseEntity.status(201).body(service.add(request.title()));
TaskController.java: return ResponseEntity.status("NOT_FOUND".equals(error.getMessage()) ? 404 : 400).body(Map.of("error", error.getMessage()));
TitlePolicy.java: if (clean.codePointCount(0, clean.length()) > 40) throw new IllegalArgumentException("TITLE_TOO_LONG");

확인 문제

실습

evidence/flow.md와 final-review.md를 작성합니다. 정상 생성·빈 제목 실패·반복 완료의 호출 흐름, 입력 변환, 상태 보존, HTTP 응답과 외부 의존성을 설명합니다. 앞 모듈 solution과 이번 제출의 차이를 읽고 AI 도움·직접 판단·미확인 경계를 적습니다. 제출 기준은 세 경로의 실제 메서드, 최소 두 테스트와 연결, 범위 밖 변경 확인, 읽지 못한 부분을 질문으로 표시하는 것입니다.

더 읽기

면접 질문

  • AI가 만든 코드가 실행될 때 추가로 확인할 내용을 설명해 주시면 됩니다.