
설계 문서는 쓰는 날이 가장 정확하고 그 뒤로 매일 틀려간다. 코드는 바뀌는데 문서는 안 바뀌기 때문이다. 이 글은 개인 프로젝트의 설계 문서를 "읽는 문서"에서 "실패하는 테스트"로 바꾼 방법을 다룬다. 규격을 YAML 한 파일에 두고, 코드가 그 규격에서 벗어나면 명령 하나가 exit 1을 내게 했다. 문서를 성실하게 갱신하겠다는 다짐보다, 갱신하지 않으면 커밋할 수 없는 구조가 오래간다.
문서와 코드가 어긋나면 어긋난 쪽을 알 수 없다
자동화 파이프라인이 새벽에 조용히 실패했을 때 가장 먼저 여는 것은 설계 문서다. 어느 단계가 무엇을 받아 무엇을 내는지, 어떤 규칙이 발행을 막는지. 그런데 문서에 적힌 검수 규칙이 아홉 개이고 코드에는 열두 개라면, 문서를 읽어서는 원인을 못 찾는다. 코드를 읽어야 하고, 그러면 문서는 애초에 왜 있었나.
흔한 대응은 두 가지다. 문서를 열심히 갱신하기로 다짐하거나, 문서를 포기하고 코드가 곧 문서라고 선언하거나. 첫째는 오래 못 간다. 둘째는 "왜 이렇게 만들었나"가 코드에 남지 않는다. Michael Nygard가 아키텍처 결정 기록(ADR)을 제안하며 든 이유가 정확히 이것이다. "프로젝트의 생애에서 가장 추적하기 어려운 것 중 하나는 어떤 결정의 동기다."
규격을 YAML 로 옮기고 검사기를 붙였다
접근은 단순하다. 문서 가운데 사실관계 표에 해당하는 부분, 즉 CLI 명령 목록, 파일 스키마, 셀렉터 이름, 검수 규칙 ID 같은 것을 docs/spec.yaml 한 파일로 모은다. 산문은 그대로 마크다운에 둔다. 그리고 bgp spec-check라는 명령이 코드의 실물과 YAML을 대조한다.
# docs/spec.yaml (일부)
gate:
rules:
- id: title_present
severity: error
summary: 제목이 비어 있으면 안 된다.
- id: body_length
severity: error
summary: 본문이 min_chars 미만이면 실패.
cadence:
hard_cap_per_day: 3
# bgp/spec.py (일부)
def check_spec() -> list[str]:
spec = load_spec()
problems = []
problems += _diff("검수 규칙",
[r["id"] for r in spec["gate"]["rules"]],
gate.RULE_IDS)
for key, value in cadence.DEFAULTS.items():
if spec["cadence"].get(key) != value:
problems.append(f"cadence.{key}: 문서={spec['cadence'].get(key)} 코드={value}")
return problems
코드 쪽은 규칙 ID를 리스트 상수로, 기본값을 딕셔너리 상수로 노출하기만 하면 된다. 검사기는 양쪽 집합의 차이를 출력한다. "문서에는 있는데 코드에 없음", "코드에는 있는데 문서에 없음".
이게 실제로 잡는지 확인하려고 코드에만 규칙 하나를 몰래 추가해 봤다.
불일치 검수 규칙: 코드에는 있는데 spec.yaml 에 없음 → ['ghost_rule']
불일치 발행 빈도: cadence.hard_cap_per_day 가 spec.yaml=3 / 코드=9 로 다름
spec-check 실패 — 코드 또는 docs/spec.yaml 을 맞추세요. exit=1
문서의 표는 YAML 에서 생성하고, 낡으면 그것도 실패다
사람이 읽는 마크다운 문서에는 같은 정보가 표로 들어가야 한다. 그 표를 손으로 쓰면 다시 어긋난다. 그래서 문서 안에 <!-- spec:begin:gate --> 와 <!-- spec:end:gate --> 표식을 두고, bgp spec-docs가 그 사이를 YAML로부터 다시 쓴다.
여기서 한 걸음 더 갔다. spec-check는 "문서의 자동 생성 구간이 지금 YAML로 만들 결과와 같은가"도 검사한다. 다르면 실패다. 즉 YAML을 고치고 spec-docs를 안 돌리면 커밋이 막힌다. 문서 갱신을 잊을 수가 없다.

검사 없는 규격은 규격이 아니다
이 방식의 비용은 분명하다. 규격 항목을 하나 늘릴 때마다 대응하는 검사 코드도 늘려야 한다. 검사 없이 YAML에만 적힌 항목은 곧바로 낡는다. 그래서 규칙을 하나 정했다. 검사 없는 규격은 추가하지 않는다. 검사를 쓰기 귀찮은 항목이라면 그 항목은 규격이 아니라 산문으로 문서에 남긴다.
반론도 있다. 개인 프로젝트에 이 정도 장치는 과하다는 것이다. 처음 이틀 동안은 나도 그렇게 느꼈다. 생각이 바뀐 것은 셋째 날, 다른 세션이 올린 커밋과 내 커밋이 충돌했을 때다. 양쪽이 spec.yaml의 다른 부분을 늘렸고, 병합 후 spec-check가 즉시 어느 문서 표가 낡았는지 짚어줬다. 사람 둘이 아니라 세션 둘이 번갈아 작업하는 프로젝트에서, 문서가 코드를 따라오게 만드는 장치는 사치가 아니라 전제 조건이었다.
문서를 잘 쓰겠다는 다짐은 사람에게 거는 것이다. 문서가 틀리면 빌드가 깨지게 하는 것은 구조에 거는 것이다. 오래가는 쪽은 언제나 후자다.
참고 자료
- Documenting Architecture Decisions — Michael Nygard — ADR 의 형식과 "결정의 동기가 사라진다"는 문제 제기
- Docs as Code — Write the Docs — 문서를 코드와 같은 도구로 다루는 관행
- Playwright Python — Auto-waiting — 이 프로젝트에서 규격화한 셀렉터 계약이 전제하는 동작
'IT' 카테고리의 다른 글
| AI 에이전트 보안, 권한을 어디까지 줄 것인가 (0) | 2026.09.16 |
|---|---|
| Playwright 고정 대기 대신 상태 대기를 써야 하는 이유 (0) | 2026.09.15 |
| 오픈소스 라이선스 변경, 기업이 자꾸 바꾸는 이유와 개발자의 대응 (0) | 2026.09.15 |
| Playwright intercepts pointer events 해결 (0) | 2026.09.14 |
| AI 반도체 거품 논쟁, 매출 1조 달러와 캡엑스 6천억 달러 사이 (0) | 2026.09.14 |