본문으로 건너뛰기
JSL107's Tech Note
돌아가기

문서 드리프트를 넘어 생성기의 정확성 검증하기

6분 분량이 글 고치기
목차 · 6

CI는 통과했지만 문서는 비어 있었다

한 프로젝트에서 코드에 있는 에이전트와 환경변수 목록을 추출해 각각 카탈로그 문서로 만들어요. 개발자가 문서를 직접 갱신할 필요는 없어요. 문서 검사 명령을 실행하면 코드에서 문서를 다시 생성해 저장소의 문서와 비교하고, 차이가 있으면 CI를 실패시켜요.

전형적인 documentation-as-code 구조예요. 실제로 환경변수 107개가 빠짐없이 카탈로그에 들어 있었고 CI도 정상적으로 통과했어요.

하지만 생성된 문서를 직접 읽어 보니 문제가 드러났어요. 상당수 항목이 비어 있었고, 일부 파싱 실패는 다음과 같은 폴백에 가려져 있었어요.

modelByRole[catalogItem.role] ?? '?'

생성기가 모델 정보를 찾지 못해도 작업은 실패하지 않았어요. 문서에 ?만 기록한 채 계속 진행했죠. 자동화는 멈추지 않았지만 생성 결과의 품질이 왜 낮아졌는지는 아무도 알 수 없었어요.

드리프트 검사는 생성기의 정확성을 증명하지 않는다

기존 문서 검사 명령이 검증하는 명제는 단순했어요.

현재 파일의 내용과 생성기를 다시 실행한 결과가 같은가?

이 검사는 코드와 생성 문서가 어긋났을 때는 효과가 있지만, 생성기 자체가 잘못됐는지는 판단하지 못해요.

문서 동기화 명령과 문서 검사 명령이 같은 파서를 공유했기 때문이에요. 파서가 코드를 잘못 읽으면 생성 명령과 검사 명령도 똑같이 잘못된 결과를 만들어요. 그 결과를 저장소에 한 번 반영하고 나면 이후 비교에서는 계속 일치한다고 나와요.

흐름을 정리하면 다음과 같아요.

코드
└─ 잘못된 파서
├─ 문서 동기화 → 잘못된 문서 저장
└─ 문서 검사   → 같은 잘못된 결과 생성

CI 통과

드리프트 검사는 생성 결과가 일관적인지만 확인했을 뿐, 생성기가 정확한지는 검증하지 않았어요. 생성기와 검사기가 같은 오류를 공유하면 서로를 독립적으로 검증할 수 없어요.

정규식으로 코드를 추론하던 경로를 제거했다

문제의 핵심은 TypeScript 코드를 문자열로 읽고 정규식으로 구조를 추정하던 로직이었어요.

정규식은 특정한 코드 형태를 전제로 작동해요. 의미가 같은 코드라도 줄바꿈이나 타입 표기, 객체 선언 방식이 달라지면 매칭 결과도 달라질 수 있어요. TypeScript 컴파일러는 이미 에이전트 역할과 모델 정보가 어떻게 연결되는지 알고 있었으므로, 생성기가 그 관계를 문자열에서 다시 복원할 이유는 없었어요.

이번 변경에서는 에이전트와 모델의 관계를 소스 텍스트에서 정규식으로 찾던 경로를 없애고, 문서 생성기가 타입이 보장된 데이터 구조를 직접 사용하도록 바꿨어요.

핵심은 파서를 더 정교하게 만드는 게 아니라, 처음부터 파싱할 필요가 없는 정보는 파싱하지 않는 데 있어요.

기존 구조에서는 코드 표현이 바뀌면 다음과 같은 문제가 생길 수 있었어요.

TypeScript 코드 변경
→ 정규식 매칭 실패
→ 모델 정보 누락
→ '?' 또는 빈 값으로 대체
→ 문서 생성 성공

타입 기반 구조에서는 관계가 잘못되거나 빠지면 TypeScript 검사가 먼저 문제를 드러내요.

타입이 보장된 정의
→ 문서 생성기가 직접 참조
→ 잘못된 키 또는 누락을 컴파일 단계에서 감지

이제 문서 생성 로직은 TypeScript의 의미를 문자열에서 재구성하지 않고 코드가 이미 제공하는 타입 정보를 그대로 사용해요.

남겨야 하는 파서에는 계약을 추가했다

그렇다고 모든 텍스트 파싱을 없앨 수는 없었어요. 환경변수 설명처럼 주석에만 있는 정보는 런타임 타입이나 객체에서 바로 가져올 수 없어서 여전히 주석을 파싱해야 했어요.

중요한 건 파서가 있느냐가 아니라, 실패했을 때 어떻게 동작하느냐였어요.

기존 구현은 예상한 주석 형식을 만나지 못해도 빈 값이나 기본값을 반환할 수 있었어요. 생성 과정은 성공한 채 손상된 결과만 문서에 남았으니, 파싱 실패를 정상 데이터처럼 취급한 셈이에요.

이번 변경에서는 남아 있는 주석 파서에 계약 검증을 추가했어요. 입력이 파서가 기대하는 구조를 충족하지 않으면 빈 설명을 만든 채 넘어가지 않고 즉시 실패해요.

주석 입력
→ 계약에 맞게 해석됨
→ 문서 생성 계속
→ 계약을 만족하지 않음
→ 즉시 오류
→ 문서 동기화 / 문서 검사 실패

이 계약 테스트가 보장하는 것은 특정 문서 문자열이 아니라, 파서가 반드시 지켜야 할 전제예요.

이 계약 덕분에 주석 형식이 바뀌거나 파서가 회귀하면 문서 내용이 조용히 비어 가는 대신 CI가 문제를 드러내요.

CI에서 검증할 계약을 분리했다

이번 사례에서 문서 자동화에 필요한 검증은 하나가 아니었어요.

첫 번째는 드리프트 계약이에요.

현재 생성 결과 == 저장소에 반영된 문서

코드는 바꿨지만 문서를 갱신하지 않은 경우를 잡아내요.

두 번째는 파서 계약이에요.

입력 소스가 기대한 구조로 해석됨

생성기가 입력을 제대로 이해하지 못한 경우를 잡아내요.

세 번째는 타입 계약이에요.

문서 생성에 필요한 코드 관계가 타입 수준에서 유효함

소스 코드의 의미를 문자열로 추측하면서 생기는 오류를 줄여요.

세 검증은 서로를 대신할 수 없어요. 드리프트 검사만으로는 같은 파서를 공유해 생기는 오류를 발견할 수 없고, 타입 검사만으로는 주석처럼 타입 시스템 밖에 있는 정보를 검증할 수 없어요.

변경한 뒤에도 환경변수 카탈로그에는 기존 107개 항목이 그대로 남았어요. 기존 자동화가 누락을 막는 기능은 유지하면서 생성기 내부의 실패를 감지할 조건을 추가한 거예요.

이제 코드와 문서가 다르면 드리프트 검사에서 실패하고, 생성기가 코드를 제대로 해석하지 못하면 파서 계약에서 실패해요. 에이전트와 모델의 관계처럼 타입으로 표현할 수 있는 정보는 정규식 파서에 맡기지 않아요. 남은 파서의 범위는 줄었고, 그 파서도 실패를 숨기지 않도록 조건을 갖췄어요.

documentation-as-code의 정본은 생성 파일이 아니다

자동 생성 문서를 운영하다 보면 생성된 Markdown을 정본으로 여기기 쉬워요. 하지만 실제 신뢰 경계는 그보다 앞에 있어요.

문서와 코드가 동기화돼 있어도 생성기가 잘못됐다면 문서 역시 일관되게 잘못될 수 있어요. documentation-as-code에서는 결과 파일만 검증해서는 안 되는 이유예요.

이번 변경의 핵심은 더 복잡한 파서를 만드는 데 있지 않았어요. 불필요한 파서는 없애고, 남은 파서는 잘못됐을 때 반드시 실패하도록 만들었어요.

문서 자동화의 신뢰성은 “항상 같은 문서를 만드는가”만으로 판단할 수 없어요. “생성기가 입력을 이해하지 못했을 때 확실히 멈추는가”까지 검증해야 해요.


이 글 고치기
이 글 공유하기:

이전 글
침묵하거나 무한 재시도하는 자동화를 관찰 가능한 시스템으로 바꾸기