라우트와 컴포넌트에서 실제 앱 동작을 추출해 코드에서 Claude Code 기능 스펙을 만들고, 그 결과 리빙 스펙과 갭 리스트를 작성하는 방법을 배우세요.

사람들이 앱이 무엇을 하는지 다르게 기억하는 건 당연합니다. 지원팀은 최근의 화난 티켓을 기억하고, 영업은 데모 경로를 기억하고, 엔지니어는 기능이 의도된 바를 기억합니다. 세 사람에게 물으면 세 가지 확신에 찬 답을 얻지만, 그 중 어느 것도 현재 빌드와 일치하지 않을 수 있습니다.
시간이 지나면 코드만이 최신 상태를 유지하는 유일한 출처가 됩니다. 문서는 흐트러지고, 티켓은 닫히며, 빠른 수정들이 쌓입니다. 라우트에 새로운 검증 규칙이 생기고, UI 토글이 기본값을 바꾸고, 핸들러가 다른 오류를 반환하기 시작합니다. 누구도 스펙을 업데이트하지 않습니다—옵션처럼 느껴지기 때문이고, 각 변경이 문서화하기엔 너무 사소하게 느껴집니다.
이로 인해 예측 가능한 문제가 생깁니다. 팀은 존재하는 엣지 케이스를 알지 못한 채 변경을 배포해 문제가 발생합니다. QA는 행복 경로만 테스트하고 핸들러에 숨어 있는 규칙을 놓칩니다. 새 팀원은 실제 제약을 이해하지 못하고 UI에서 동작을 복사해 옵니다. 이해관계자들은 합의된 동작을 가리키지 못한 채 의견을 논쟁합니다.
좋은 결과는 완벽한 문서가 아닙니다. 공유된 명확성입니다. 누구나 추측 없이 “X를 하면 무엇이 일어나나?”와 “시스템이 무엇을 보장하나?”에 답할 수 있어야 합니다. 그러면 놀라움이 줄고, 리뷰 주기가 짧아지며, “잠깐, 이미 그랬어” 같은 순간이 줄어듭니다. 팀이 같은 사실을 보고 있기 때문입니다.
스펙이 코드와 일치하면 변경을 계획하기 안전해집니다. 무엇이 안정적인지, 우연한지, 누락된 것인지를 배포 전에 파악할 수 있습니다.
리빙 스펙은 앱이 오늘 실제로 무엇을 하는지에 대한 짧고 편집 가능한 설명입니다. 한 번 작성하고 끝내는 문서가 아닙니다. 동작이 바뀔 때마다 함께 바뀌어 팀이 신뢰할 수 있어야 합니다.
코드에서 작성한 기능 스펙(예: Claude Code 같은 도구 사용)을 말할 때 목표는 단순합니다: 라우트, 핸들러, 화면에서 실제 동작을 읽고 그것을 평이한 언어로 적는 것.
유용한 리빙 스펙은 사용자가 볼 수 있는 것과 시스템이 약속하는 것에 초점을 맞춥니다. 포함해야 할 항목:
스펙에 포함하지 않아야 할 것은 코드가 어떻게 조직되어 있는지입니다. 파일명이나 리팩터 계획을 적기 시작하면 구현 세부로 빠지는 것입니다. 피해야 할 것:
갭 리스트는 별개입니다. 작성 중에 발견한 불일치와 불명확한 점을 작은 목록으로 남겨둡니다.
예: 한 라우트는 10MB 초과 파일을 거부하는데 UI는 25MB라고 말하면, 팀이 어느 규칙을 실제로 따를지 결정할 때까지 그건 갭입니다.
작게 시작하세요. 앱 전체를 문서화하려 하면 신뢰받지 못하는 메모 더미만 생깁니다. 한 문장으로 설명 가능한 한 덩어리(예: “팀원 초대”, “결제”, “비밀번호 재설정”)를 선택하세요. 좋은 범위는 단일 기능 영역, 한 모듈, 또는 진입점부터 결과까지의 하나의 사용자 여정입니다.
진실이 어디에 있는지에 따라 진입점을 선택하세요:
코드를 읽기 전에 몇 가지 입력을 모아두면 불일치가 빨리 눈에 들어옵니다: 기존 API 문서, 오래된 제품 노트, 지원 티켓, 사람들이 불평하는 “알려진 고통점”. 이들은 코드를 무시하지는 않지만 오류 상태, 엣지 케이스, 권한 같은 누락된 상태를 발견하는 데 도움이 됩니다.
스펙 형식은 지루하고 일관되게 유지하세요. 모든 스펙이 같은 방식으로 읽힐 때 팀의 정렬이 빨라집니다.
이 구조를 반복해서 사용하면 기능 스펙이 읽기 쉽고 비교하기 쉬우며 업데이트하기도 수월해집니다.
서버 진입점부터 시작하세요. 라우트와 핸들러는 "앱이 무엇을 하는지"를 구체적으로 보여줍니다: 누가 호출할 수 있는지, 무엇을 보내야 하는지, 무엇을 받는지, 시스템에서 무엇이 변경되는지.
범위에 해당하는 라우트를 목록화하고 각 라우트를 사용자 의도와 매핑하세요. "POST /api/orders" 같은 기술적 표기를 쓰지 말고 "주문하기" 또는 "초안 저장"처럼 적으세요. 만약 평이한 말로 의도를 이름 붙일 수 없다면, 그 자체가 이미 스펙 갭입니다.
각 핸들러를 읽으면서 입력과 검증 규칙을 사용자에게 보이는 요구사항으로 캡처하세요. 필수 필드, 허용 형식, 실제 오류를 유발하는 규칙을 포함합니다. 예: "이메일은 유효해야 한다", "수량은 최소 1", "시작일은 과거일 수 없다".
인증과 역할 검사도 같은 방식으로 적으세요. "middleware: requireAdmin" 대신 이렇게 문서화하세요: "관리자만 모든 주문을 취소할 수 있다. 일반 사용자는 본인 주문을 10분 이내에만 취소할 수 있다." 코드가 소유권, 기능 플래그, 테넌트 경계를 검사하면 그것도 포함하세요.
그다음 출력과 결과를 적으세요. 성공 시 무엇을 반환하는가(생성된 ID, 업데이트된 객체)? 흔한 실패는 어떤 형태인가(401 로그인 필요, 403 권한 없음, 404 없음, 409 충돌, 422 검증 오류)?
마지막으로 부수 효과를 기록하세요. 레코드 생성/업데이트, 이메일/알림 발송, 이벤트 발행, 백그라운드 잡 큐잉, 다른 흐름을 트리거하는 모든 것들은 동작의 일부입니다. 이런 세부는 나중에 스펙에 의존할 때 놀라움을 예방합니다.
라우트는 시스템이 무엇을 할 수 있는지를 알려줍니다. 컴포넌트는 사용자가 실제로 경험하는 것을 알려줍니다. UI를 계약의 일부로 취급하세요: 무엇이 보이는지, 무엇이 차단되는지, 문제가 생기면 무엇이 발생하는지.
기능의 진입 화면을 먼저 찾으세요. 페이지 컴포넌트, 레이아웃 래퍼, 그리고 페칭, 권한, 내비게이션을 제어하는 몇몇 "결정" 컴포넌트를 찾아보세요. 실제 동작은 보통 그곳에 있습니다.
컴포넌트를 읽으면서 사용자가 느낄 수 있는 규칙들을 캡처하세요: 액션이 비활성화되는 조건, 필수 단계, 조건부 필드, 로딩 상태, 오류 표시 방식(인라인 필드 오류 vs 토스트, 자동 재시도, "다시 시도" 버튼). 또한 stale 데이터 먼저 표시, 낙관적 업데이트, "마지막 저장" 타임스탬프 같은 상태와 캐싱 동작도 기록하세요.
사용자에게 보이지 않게 흐름을 바꾸는 숨겨진 흐름을 주시하세요. 기능 플래그, 실험 버킷, 관리자 전용 게이트를 찾아보세요. 로그인하지 않은 사용자를 로그인으로 보내는 조용한 리다이렉트나 접근 권한 없는 사용자를 업그레이드 화면으로 보내는 흐름도 기록하세요.
구체적 예: "이메일 변경" 화면에서 Save 버튼은 이메일이 유효할 때까지 비활성화되어 있고, 요청 중에는 스피너가 표시되며, 성공하면 확인 배너가 뜨고 백엔드 검증 오류는 입력 아래에 렌더링된다는 것을 문서화하세요. 코드에 newEmailFlow 같은 플래그가 보이면 두 가지 변형과 차이점을 함께 적으세요.
각 UI 흐름을 짧은 단계로 쓰되(사용자가 하는 일, UI가 반응하는 방식), 조건과 오류는 영향을 받는 단계 옆에 적으세요. 이렇게 하면 스펙이 읽기 쉬워지고 갭을 찾기 쉬워집니다.
라우트와 컴포넌트에서 얻은 원시 노트는 유용하지만 토론하기엔 어렵습니다. 관찰한 것을 PM, 디자이너, QA, 엔지니어 모두 읽고 합의할 수 있는 스펙으로 다시 작성하세요.
실용적 패턴은 라우트나 화면별로 하나의 사용자 스토리를 쓰는 것입니다. 작고 구체적으로 유지하세요. 예: "로그인한 사용자는 비밀번호를 재설정하여 접근을 복구할 수 있다." 코드가 역할별로 다른 동작을 보이면(관리자 vs 사용자), 각 역할을 별도의 스토리로 분리하세요.
그다음 수용 기준(acceptance criteria)을 실제 코드 경로를 반영하도록 작성하세요. 핸들러가 토큰이 없을 때 401을 반환한다면 그것도 기준입니다. UI가 필드가 유효할 때까지 제출을 비활성화한다면 그것도 기준입니다.
특히 놀라움을 주는 데이터 규칙(한도, 정렬, 유니크, 필수 필드)은 평이한 언어로 포함하세요. "사용자 이름은 저장 시 유일해야 한다(저장 시 확인)"는 "유니크 인덱스"보다 명확합니다.
엣지 케이스는 종종 문서를 유용하게 만드는 차이입니다. 빈 상태, null 값, 재시도, 타임아웃, API 호출 실패 시 사용자가 보는 것을 명확히 적으세요.
모르는 점이 나오면 추측하지 말고 표시하세요:
이 마커들은 팀에게 빠른 질문으로 바뀌며 은밀한 가정 대신 명확한 결정을 이끌어냅니다.
갭 리스트는 두 번째 Jira가 아닙니다. 코드와 의도된 동작이 일치하지 않거나 아무도 "정상"이 무엇인지 설명할 수 없는 곳을 근거 기반으로 기록한 짧은 목록입니다. 잘하면 합의를 위한 도구가 되고, 계획 싸움이 되지 않습니다.
갭으로 간주할 항목에 엄격하세요:
갭을 기록할 때는 근거를 유지하기 위해 세 부분을 포함하세요:
증거가 있으면 목록이 의견의 나열이 되는 것을 막습니다. 예: "POST /checkout/apply-coupon가 만료된 쿠폰을 수락하지만 CouponBanner.tsx는 UI에서 차단함. 영향: 수익과 사용자 혼란. 유형: 버그 또는 결정 필요(의도 확인)."
짧게 유지하세요. 첫 패스에서 10개 항목 같은 상한을 정하세요. 40개 이슈를 찾으면 패턴(검증 불일치, 권한 검사, 빈 상태)으로 그룹화하고 상위 사례만 남기세요.
갭 리스트에 날짜나 일정 계획을 넣지 마세요. 소유권이 필요하면 가벼운 수준으로: 결정을 내려야 할 사람(제품)이나 동작을 검증할 사람(엔지니어)을 적고 실제 계획은 백로그로 옮기세요.
작고 사용자가 많이 쓰는 범위를 고르세요: 프로모 코드와 배송 옵션이 있는 체크아웃. 목표는 제품 전체를 재작성하는 것이 아니라 앱이 오늘 무엇을 하는지 캡처하는 것입니다.
백엔드 라우트부터 시작하세요. 규칙이 거기서 먼저 나오는 경우가 많습니다. POST /checkout/apply-promo, GET /checkout/shipping-options, POST /checkout/confirm 같은 라우트를 발견할 수 있습니다.
그 핸들러들에서 관찰한 동작을 평이한 문장으로 적으세요:
그다음 UI 컴포넌트를 확인하세요. PromoCodeInput은 성공 응답 후에만 총액이 갱신되고 오류는 입력 아래에 렌더링될 수 있습니다. ShippingOptions 컴포넌트는 처음 로드 시 가장 저렴한 옵션을 자동 선택하고 사용자가 변경하면 전체 가격 상세를 갱신할 수 있습니다.
이제 읽기 쉬운 스펙과 작은 갭 리스트가 생깁니다. 예: 프로모 라우트와 UI의 오류 메시지가 다르다("Invalid code" vs "Not eligible")거나 세금 반올림 규칙(라인별 vs 주문 총액)이 명확하지 않은 경우가 그 예입니다.
계획 단계에서는 현실에 먼저 합의한 뒤 무엇을 바꿀지 결정합니다. 의견을 논쟁하는 대신 문서화된 동작을 검토하고 한 불일치부터 고치며 나머지는 가치가 있을 때까지 "현재 동작으로 알려진 상태"로 둡니다.
스펙은 팀이 현실과 일치한다고 합의할 때만 도움이 됩니다. 엔지니어 한 명과 제품 담당 한 명과 함께 짧게 읽어보세요. 20–30분이면 충분합니다: 사용자가 무엇을 할 수 있고 시스템이 어떻게 반응하는지에 초점을 맞춥니다.
읽는 동안 문장을 예/아니오 질문으로 바꾸세요. "사용자가 이 라우트에 접근하면 세션이 없을 때 항상 403을 반환하나요?" "이 빈 상태가 의도된 것인가요?" 이렇게 하면 의도된 동작과 우연히 들어온 동작을 분리할 수 있습니다.
편집하기 전에 용어를 합의하세요. UI에 보이는 단어(버튼 레이블, 페이지 제목, 오류 메시지)를 사용하고 내부 이름은 엔지니어가 코드를 찾는 데 도움이 될 때만 추가하세요. 이렇게 하면 제품에서 "Workspace"라 부르는 것을 스펙에서 "Org"라고 잘못 표기하는 불일치를 방지합니다.
유지 관리를 위해 소유권과 주기를 명확히 하세요:
Koder.ai 같은 도구를 사용하면 스냅샷과 롤백으로 스펙 업데이트 전후를 비교할 수 있어 큰 리팩터 후에 유용합니다.
스펙에 대한 신뢰를 잃는 가장 빠른 방법은 원하는 제품을 기술하는 것입니다. 한 가지 규칙을 엄격히 지키세요: 모든 문장은 코드나 실제 화면에서 가리킬 수 있는 근거로 뒷받침되어야 합니다.
또 다른 함정은 코드의 구조를 그대로 문서에 옮기는 것입니다. "Controller -> Service -> Repository"처럼 읽히는 문서는 스펙이 아니라 폴더 맵입니다. 사용자 관점으로 쓰세요: 무엇이 동작을 트리거하는가, 사용자가 무엇을 보는가, 무엇이 저장되는가, 오류는 어떻게 보이는가.
권한과 역할은 나중에 무시되기 쉬워 맨 끝에 가면 모든 것이 깨집니다. 초기부터 접근 규칙을 추가하세요. 어떤 역할이 조회, 생성, 수정, 삭제, 내보내기, 승인할 수 있는지, 그리고 규칙이 UI에서만 강제되는지, API에서도 강제되는지 명시하세요.
비해피 경로(non-happy paths)를 건너뛰지 마세요. 실제 동작은 재시도, 부분 실패, 만료 같은 시간 기반 규칙에 숨어 있습니다. 이것들을 1급 시민처럼 다루세요.
갭을 쉽게 드러내는 빠른 방법:
마지막으로 갭 리스트를 움직이게 하세요. 각 갭은 "unknown/needs decision", "bug/fix", "missing feature/plan" 같은 라벨 중 하나로 분류되어야 합니다. 라벨이 없으면 목록이 멈추고 스펙은 더 이상 리빙하지 않습니다.
명확성, 범위, 실행 가능성을 위해 빠르게 검토하세요. 작성자가 아니라도 기능이 오늘 무엇을 하는지, 무엇이 불명확한지 이해할 수 있어야 합니다.
새 팀원 입장에서 읽어보세요. 한 분으로 요약할 수 있으면 거의 완성입니다. "어디서 시작하나?" 또는 "행복 경로가 뭐야?"라는 질문이 계속 나오면 도입부를 다듬으세요.
확인 항목:
각 갭은 구체적이고 테스트 가능해야 합니다. "오류 처리 불분명" 대신:\n"결제 공급자가 402를 반환하면 UI가 일반 토스트를 보여준다; 원하는 메시지와 재시도 동작을 확인하라."와 같이 적으세요. 한 가지 다음 행동(제품에 질문, 테스트 추가, 로그 조사)을 달고 답변할 사람을 적으세요.
하나의 기능 영역을 고르고 60분으로 시간 상한을 두세요. 로그인, 체크아웃, 검색, 관리자 화면처럼 작지만 실용적인 것을 고르세요. 범위를 한 문장으로 적고 포함되는 것과 제외되는 것을 명시하세요.
워크플로를 한 번 끝까지 실행하세요: 핵심 라우트/핸들러를 훑고 주요 UI 흐름을 추적한 뒤 관찰 가능한 동작(입력, 출력, 검증, 오류 상태)을 적으세요. 막히면 질문을 갭으로 기록하고 계속 진행하세요.
완료하면 팀이 코멘트할 수 있는 곳에 스펙을 공유하고 하나의 규칙을 정하세요: 배포된 동작 변경은 같은 배포 주기 내에 스펙도 업데이트해야 한다(다섯 줄이라도).
갭은 백로그와 분리하세요. "알 수 없음 행동", "일관성 없음", "테스트 누락"으로 그룹화하고 매주 간단히 검토해 무엇이 지금 중요한지 결정하세요.
초안 작성과 반복이 느리다면 Koder.ai 같은 채팅 기반 빌더가 첫 버전을 빠르게 만드는 데 도움이 될 수 있습니다. 기능을 설명하고 주요 스니펫이나 라우트 이름을 붙여넣고 대화로 문구를 다듬은 뒤 필요할 때 출처를 내보내세요. 핵심은 속도와 공유된 명확성이지 더 큰 프로세스가 아닙니다.
한 조각씩 작게 시작하세요(예: “비밀번호 재설정” 또는 “팀원 초대”). 먼저 routes/handlers를 읽어 규칙과 결과를 캡처하고, 그다음 UI 흐름을 읽어 사용자가 실제로 보는 것(비활성 상태, 오류, 리디렉트)을 기록하세요. 일관된 템플릿으로 정리하고 모르는 부분은 별도의 갭 리스트로 남기세요.
기본 원칙: 현재 코드 동작을 진실의 출처로 보고 문서화하세요.
동작이 우연히 생긴 것 같거나 일관성이 없어 보이면, 사양에서 바로 '수정'하려 하지 말고 증거와 함께 갭으로 표시하세요(어디에서 그런지, 실제로 어떻게 작동하는지). 그다음 팀에서 코드나 스펙 중 어느 쪽을 바꿀지 결정하세요.
단순하고 반복 가능한 형식을 유지하세요. 실용적인 템플릿 예시:
이 형식을 반복하면 스펙이 읽기 쉽고 불일치가 눈에 띕니다.
사용자 관점의 요구사항으로 적으세요. 코드 노트처럼 쓰지 마세요.
예:
어떤 조건이 오류를 유발하는지와 사용자가 오류를 경험할 때 어떻게 보이는지도 적으세요.
관찰 가능한 것에 집중하세요:
부수 효과는 다른 기능과 지원/운영 기대치에 영향을 주기 때문에 중요합니다.
UI가 차단하는 것과 API가 허용하는 것(또는 그 반대)이 있으면 그것을 갭으로 기록하세요. 기록할 내용:
팀이 하나의 규칙에 합의할 때까지 갭으로 남겨 두세요.
갭 리스트는 작고 증거 기반이어야 합니다. 각 항목에 포함할 것:
스케줄링이나 두 번째 백로그로 만들지 마세요.
명시적으로 문서화하세요:
이런 경우들이 보통 놀라움과 버그의 원천입니다.
짧게 유지하세요: 엔지니어 한 명과 프로덕트 한 명과 20–30분 동안 읽어보면 됩니다.
문장을 예/아니오 확인으로 바꾸세요(예: “권한 없을 때 항상 403을 반환하나요?”). UI에 표시된 단어(버튼 레이블, 페이지 제목, 오류 메시지)를 어휘로 합의하면 모든 사람이 같은 의미로 말합니다.
코드와 가까운 곳에 스펙을 두고, 배포 과정의 일부로 업데이트하세요.
권장 기본값:
목표는 큰 재작성보다 작고 잦은 수정입니다.