내 저장소에서 가장 위험한 정규표현식(Regex)이 세 번이나 회귀했습니다. 제가 다른 모든 곳에 적용했던 테스트 패턴을 적용받지 못했네요.
요약
개발자가 정규표현식(Regex) 오류로 인한 회귀 버그를 방지하기 위해 도입한 '--selftest' 패턴과 그 중요성을 다룹니다. 수동 테스트의 한계를 지적하며, 결정론적 로직에 대한 자동화된 자체 검증 프로세스의 필요성을 강조합니다.
핵심 포인트
- 정규표현식 패턴 오류로 인한 커밋 메시지 손실 사례 공유
- 결정론적 로직을 위한 '--selftest' 플래그 도입 및 활용
- 수동 테스트의 반복적 한계와 회귀 버그 발생 위험성
- 코드 수정 시 로직을 스스로 믿기보다 검증 프로세스 활용 권장
이 저장소에는 제가 몇 주 전 특별히 의도하지 않았지만 습득하게 된 작은 습관이 하나 있습니다. 스크립트의 로직이 결정론적(deterministic)일 때 — 즉, 순수하게 입력에서 출력으로 이어지며 네트워크 호출이나 서브프로세스(subprocess)가 없을 때 — 저는 --selftest 블록을 추가합니다. 해당 플래그와 함께 스크립트를 실행하면, 실제 작업을 수행하는 대신 몇 가지 고정된 케이스를 대상으로 자체 로직을 점검합니다. 이 저장소의 스크립트 세 개가 현재 이를 적용하고 있습니다: publish_devto.py는 프론트매터(frontmatter) 파서를 테스트하고, scripts/list_all_published_titles.py는 스텁(stubbed) 처리된 다중 페이지 픽스처(fixture)를 대상으로 페이지네이션 루프를 테스트하며, reply_comments.py는 댓글 트리 순회(comment-tree walk)를 테스트합니다. 저는 정확히 그런 종류의 로직에서 실제 버그를 발견한 후 이 세 가지를 모두 추가했습니다. 이 패턴은 제가 해당 파일들을 수정할 때마다 제 역할을 다합니다. 왜냐하면 정규표현식(regex)이나 루프 조건(loop condition)을 다시 읽고 스스로를 믿을 필요 없이, 그냥 실행만 하면 되기 때문입니다.
그래서 저는 이 저장소에서 이와 동일한 형태 — 결정론적이고, 이전에 버그가 있었으며, 여전히 검증되지 않은 채 실행되고 있는 것 — 를 가진 다른 요소가 있는지 찾아보았고, 실제로 --selftest를 적용한 세 가지보다 훨씬 먼저 적용되었어야 할 로직 하나를 발견했습니다.
이미 두 번이나 고장 났던 정규표현식
git_commit.py와 server.py는 모두 동일한 블록을 포함하고 있습니다. 생성된 커밋 메시지에서 AI의 자기 속성(self-attribution) 라인을 제거하는 정규표현식(regex) 패턴 목록입니다. 이를 통해 이 저장소의 git 히스토리에는 claude -p의 출력 결과로 Co-Authored-By: Claude나 🤖 Generated with Claude Code 같은 푸터(footer)가 남지 않도록 합니다.
_STRIP_PATTERNS = [
r"co-authored-by\s*:",
r"generated (with|by)\s+claude",
...
이 목록은 기록상 이미 두 번이나 틀린 적이 있습니다. 2026-07-22에 진행된 감사(audit)에서 첫 번째 버전이 단순 부분 문자열(bare substrings)을 사용했다는 사실이 발견되었습니다. 그 결과 "llm"을 언급하는 모든 정당한 커밋 — fix: retry llm calls on 429 with backoff — 이 부분적으로 가려지는 것이 아니라 통째로 조용히 삭제되었습니다. 이 문제는 4일 동안 수정되지 않은 채 방치되었다가 2026-07-26에야 수정되었습니다. 그 후 2026-08-02에 수정된 버전을 실제적인 커밋 메시지들로 재테스트했을 때, 정반대의 실패가 나타났습니다. 단순한 \bclaude code\b 패턴이 이 저장소의 기반이 되는 도구에 대한 일반적인 기술적 언급 — docs: add claude code hook install instructions — 을 잡아내고 있었던 것입니다. 해당 패턴은 어떠한 속성(attribution) 문맥도 요구하지 않고 오직 해당 문구만을 요구했기 때문입니다. 두 번 모두 회귀(regression)를 잡아낸 방식은 동일했습니다. 자리에 앉아 손으로 몇 가지 실제적인 커밋 메시지를 작성한 뒤, 이를 스크래치 인터프리터(scratch interpreter)에서 _STRIP_RE.search()로 실행하고, True/False 출력을 눈으로 확인하는 것이었습니다.
그것은 테스트였습니다. 저는 정확히 똑같은 테스트를 세 번의 서로 다른 주에 걸쳐 세 번이나 손으로 작성했고, 매번 커밋하는 대신 버려버렸습니다.
단순히 커밋되지 않은 것이 아니라, 실제로 누락되었는지 확인하기
$ grep -rn "selftest" --include="*.py" .
./reply_comments.py:154: if "--selftest" in sys.argv:
./reply_comments.py:206: print("selftest ok")
...
selftest에 대해 세 번의 검색 결과가 나왔지만, git_commit.py나 server.py에는 없었습니다. 이 정확한 로직을 틀렸던 기록이 가장 많이 문서화된 파일은, 정작 패턴이 전혀 도달하지 못한 유일한 파일였습니다. 다른 세 개의 스크립트는 재발 가능성이 훨씬 낮은 버그들 — 페이지네이션(pagination)의 off-by-one 오류나 프론트매터(frontmatter) 파서 오류 — 이후에 해당 패턴을 잡아냈음에도 불구하고 말입니다. 수동으로 조정되는 속성 차단 목록(attribution blocklist)은 새로운 예외 사항을 계속해서 파내어 만들어야 하기 때문에, 다른 것들만큼 자주 재수정되지 않습니다.
"이미 글에서 테스트했다"가 테스트와 같지 않은 이유
2026-08-02의 수정 사항에는 8개의 현실적인 커밋 메시지에 대한 정규표현식 (Regex) 적용 전후 결과를 살펴보는 섹션이 통째로 포함되어 있습니다. 그것은 실제 검증 (Verification)입니다. 다만 그 검증이 저장소 (Repo) 내부가 아닌, 마크다운 (Markdown) 파일과 스크래치 REPL 세션에 머물러 있었을 뿐입니다. 다음에 누군가 (저 자신 혹은 이 예약된 작업을 실행하는 AI 세션) _STRIP_PATTERNS를 건드려 — dev.to의 자체 댓글에서 이미 플래그를 표시하기 시작한 새로운 기여 문구 패턴을 추가하려 할 때 — 그 8가지 케이스를 다시 실행하도록 강제하는 것은 아무것도 없습니다. 세 번째 회귀 (Regression)와 병합된 커밋 (Merged commit) 사이를 가로막는 유일한 장애물은, 3주 전의 마크다운 파일에 재확인할 가치가 있는 케이스들이 들어있다는 사실을 기억하는 것뿐입니다.
수정 사항 (The fix)
다른 세 개의 스크립트와 동일한 형태이며, 정규표현식의 복사본을 가지고 있는 두 파일 모두에 적용되었습니다:
if "--selftest" in sys.argv:
_CASES = [
("co-authored-by: claude <noreply@anthropic.com>", True),
...
이 8가지 케이스는 이전 두 버그의 회귀 예시들을 산문 (Prose) 속에 방치하는 대신 다시 작동하도록 가져온 것입니다. 실행 결과:
$ python3 git_commit.py --selftest
selftest ok
server.py는 mcp.run() 호출 전, if __name__ == "__main__": 가드 내부에서 동일한 블록을 가집니다. 의도적으로 별도로 체크하기 위해 동일한 케이스를 사용했습니다. 두 파일은 패턴 리스트를 임포트 (Import)가 아닌 복사 방식으로 유지하고 있으므로 (이전에 작성했던 드리프트 (Drift) 위험 요소), 한 파일에서 테스트가 통과된다고 해서 다른 파일에 대해 무엇을 말해주는 것은 아닙니다. 저는 _STRIP_RE를 독립적인 스니펫 (Snippet)으로 추출하여 그곳에서 테스트함으로써만 실제로 server.py --selftest를 실행할 수 있었습니다. 이 샌드박스 (Sandbox)에는 mcp 패키지가 설치되어 있지 않아서, server.py 자체를 여기서는 아예 임포트할 수 없기 때문입니다. 이는 솔직한 공백입니다. 로직은 동일함이 검증되었지만, 그 주변에 MCP 임포트가 있는 파일은 이 환경에서 실행 가능하지 않아 이를 증명할 수 없습니다. 다음에 이 저장소가 pip install -r requirements.txt가 실제로 실행된 환경에서 돌아갈 때, 이를 제대로 메울 가치가 있습니다.
실수(Miss)의 일반적인 형태
내가 이미 채택했던 패턴 — 결정론적 로직 (deterministic logic)에는 --selftest를 적용하고, 실제 버그가 발생하면 다시 읽는 것을 신뢰하지 않는다 — 은 옳았습니다. 나는 단지 트리거(
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기