AI 코딩 신뢰의 누락된 절반: AI가 생성한 코드 검증하기
요약
AI의 빠른 코드 생성 속도가 엔지니어의 검증 속도를 앞지르면서 발생하는 '검증 부채(validation debt)' 문제를 다룹니다. AI가 작성한 코드가 작동하더라도 아키텍처 부합성이나 유지보수성을 보장하지 못해 발생하는 기술적 위험을 경고합니다.
핵심 포인트
- AI 코딩은 구현 속도를 높이지만 검증 루프를 망가뜨릴 수 있음
- 검증되지 않은 빠른 생성은 '검증 부채'라는 새로운 형태의 기술 부채를 유발함
- 단순히 작동하는 코드를 넘어 아키텍처와 유지보수성을 고려한 검증이 필수적임
- AI 생성 코드의 리뷰와 디버깅 난이도가 급격히 상승함
AI 코드 생성은 확장되었습니다. 하지만 검증은 그렇지 못했으며, 그 격차는 기술 부채(technical debt)가 되고 있습니다.
애플리케이션은 작동했습니다.
그것이 위험한 부분이었습니다.
47일 동안, 1,384개의 커밋, Obsidian 내 약 250개의 사양(spec) 파일, 470개 이상의 테스트 파일, 그리고 추정치 1억 개 이상의 생성된 출력 토큰(output tokens)을 거친 끝에, 나는 작동하는 시스템을 갖게 되었습니다. 기능들은 존재했습니다. 테스트는 실행되었습니다. 풀 리퀘스트(pull requests)는 머지(merged)되었습니다.
그럼에도 나는 여전히 코드베이스를 통째로 버리고 싶었습니다.
AI가 코드를 작성할 수 없어서가 아닙니다. 오히려 그 반대였습니다. AI는 너무 많이, 너무 빠르게, 그리고 너무나도 자신만만하게 코드를 작성할 수 있었습니다.
약 반년 동안, 나는 현재 많은 엔지니어링 팀이 직면하고 있다고 생각되는 한 가지 질문을 테스트해 왔습니다:
통제력을 잃지 않으면서 복잡한 코드베이스에서 자율적인 AI 코딩 실행을 어디까지 밀어붙일 수 있는가?
나의 첫 번째 답변은 냉혹했습니다:
매우 멀리까지 가능하지만, 신뢰할 수는 없다.
어느 시점부터 구현(implementation)은 더 이상 병목 현상이 아니었습니다. AI는 내가 의미 있게 검증할 수 있는 속도보다 더 빠르게 코드를 작성하고 있었습니다. 그리고 그것은 새로운 종류의 부채를 만들어냈습니다. 전통적인 의미의 기술 부채(technical debt)가 아닙니다. 내가 지금 **검증 부채 (validation debt)**라고 생각하는 것에 더 가깝습니다.
증명되는 속도보다 더 빠르게 생성되는 모든 코드 라인은 의무(obligation)가 됩니다.
그리고 그 의무는 되돌아옵니다.
리뷰할 때. 디버깅(debugging)할 때. 다음 리팩터링(refactor)을 할 때. 또는 다음과 같은 사실을 깨닫는 순간에 말입니다: 애플리케이션은 작동하지만, 그 누구도 더 이상 코드베이스를 건드리고 싶어 하지 않는다는 사실을 말이죠.
AI 코딩은 나의 구현 루프를 개선하지 않았습니다. 나의 검증 루프를 망가뜨렸습니다.
동료들이 작성한 코드를 리뷰할 때, 나는 결코 그것을 고립된 상태로 읽지 않습니다.
나는 그들이 대략 어떻게 일하는지 압니다. 나는 코드베이스를 압니다. 우리가 어떤 패턴을 선호하는지, 어떤 지름길이 허용되는지, 어디에서 누군가가 아마도 합리적인 결정을 내렸을지, 그리고 어디를 더 면밀히 살펴봐야 할지를 압니다.
AI와 함께하면서, 그것이 변했습니다.
AI는 종종 작동하는 솔루션을 찾아냈습니다. 하지만 그것들은 숙련된 팀이 해당 코드베이스(codebase)에서 구축했을 법한 솔루션처럼 보이지 않았습니다. 때로는 영리했고, 때로는 불필요하게 복잡했습니다. 때로는 국소적으로는 맞지만 아키텍처(architecture) 측면에서는 틀리기도 했습니다.
모든 리뷰가 제로(zero) 상태에서 시작되었습니다.
저는 단순히 코드가 올바른 결과를 생성하는지 확인하는 것만으로는 부족했습니다. 왜 그런 방식으로 구축되었는지 이해했는지, 아키텍처에 부합하는지, 테스트가 실제로 무언가를 증명하는지, 수정 사항이 단순히 증상만을 완화하는 것인지, 새로운 헬퍼(helper) 함수가 3주 후에 문제가 되지는 않을지까지 확인해야 했습니다.
AI는 저의 구현 속도를 추월했습니다.
처음에는 그것이 혁신처럼 느껴졌습니다.
그러다 통제력을 잃어가는 것처럼 느껴지기 시작했습니다.
결국, 저는 형식적으로는 "작동"하지만 실질적으로는 더 이상 유지보수할 수 없는 시스템을 마주하게 되었습니다. 병합된 25개의 풀 리퀘스트(pull requests) 중에서 애플리케이션은 실행되었지만, 코드가 너무 엉망이라 디버깅은커녕 다시 보고 싶지도 않을 정도였습니다.
문제는 AI가 나쁜 코드를 작성한다는 것이 아니었습니다.
문제는 제가 AI에게 그만큼 빠른 검증(verification) 없이 작업을 허용했다는 점이었습니다.
AI 때문에 잠시 잊고 있었던 오래된 개발 원칙
결국, 저는 모든 개발자가 고생하며 배우게 되는 한 가지를 기억해 냈습니다.
검증 없는 구현은 없다.
이것은 추상적인 원칙이 아닙니다. 소프트웨어 개발의 핵심 루프(core loop)입니다.
무언가를 만듭니다.
그것을 확인합니다.
조정합니다.
다시 확인합니다.
이것이 신뢰가 쌓이는 방식입니다.
이전에 수백 번 작성해 본 코드의 경우, 이 루프는 거의 보이지 않을 정도로 자연스러워집니다. 무엇이 작동하는지 알고, 실패 모드(failure modes)를 알고, 타이핑하는 동안 많은 오류를 잡아냅니다.
AI와 함께할 때는 다른 일이 일어납니다.
우리는 구현(implementation)은 넘겨주지만, 검증(verification)은 스스로가 계속 맡습니다.
이것은 비대칭적인 워크플로(workflow)를 만듭니다.
AI 속도의 구현. 인간 속도의 검증.
이것은 확장(scale)될 수 없습니다.
이러한 깨달음은 새로운 것이 아닙니다. 비슷한 시기에 저는 주요 AI 연구소(AI labs)와 엔지니어링 리더들 사이에서 동일한 아이디어가 반복해서 등장하는 것을 목격했습니다. Anthropic은 자사의 Claude Code 모범 사례(best practices)에서 이를 직설적으로 표현합니다: “검증할 수 없다면, 배포하지 마십시오 (If you can’t verify it, don’t ship it).” 그리고 Google에서 Chrome의 개발자 경험(Developer Experience)을 이끄는 Addy Osmani는 이를 개인적인 차원으로 확장합니다: “당신의 업무는 제대로 작동함을 증명한 코드를 전달하는 것입니다 (Your job is to deliver code you have proven to work).”
진정한 통찰은 '더 나은 프롬프트(prompt)가 필요하다'는 것이 아니었습니다.
진정한 통찰은 다음과 같았습니다:
AI가 구현하는 것이라면, AI 또한 그것을 증명해야 한다.
긴 실행이 끝난 후에 하는 것이 아닙니다. 사후에 작성하는 요약본도 아닙니다. “이것을 검증했습니다”라고 말하는 친절한 산문 형태도 아닙니다.
실행 과정 중에 이루어져야 합니다. 단계별로. 수용 기준(acceptance criterion) 하나하나마다. 그리고 코드를 작성한 것과 동일한 에이전트(agent)로부터 나오지 않은 증거와 함께 말입니다.
그 시점부터, 저는 더 나은 프롬프트를 찾는 것을 멈췄습니다.
대신 검증이 선택 사항이 아닌 워크플로(workflow)를 찾기 시작했습니다.
프롬프트만으로는 부족한 이유
저의 첫 번째 접근 방식은 당연하게도 더 나은 명세(spec)를 작성하는 것이었습니다.
Obsidian에서 저는 수용 기준(acceptance criteria)을 명세 파일에 직접 포함시켰습니다. 모든 작업에는 체크리스트가 부여되었습니다. 프로젝트 관리와 유사하지만, 코드에 훨씬 더 밀접하게 연결된 방식이었습니다.
그것은 즉각적인 도움이 되었습니다.
처음으로 각 작업별로 실제로 무엇이 충족되어야 하는지를 확인할 수 있었습니다. AI는 더 구조적인 방식으로 작동했습니다. 스스로를 더 자주 수정했습니다. 리뷰(review)는 더 쉬워졌고, 커밋(commit)은 더 깔끔해졌습니다. 프롬프트의 수는 줄어들었습니다.
하지만 핵심적인 문제는 남아 있었습니다.
프롬프트에 포함된 지시사항은 보장이 아니기 때문입니다.
스킬(skill)은 다음과 같이 말할 수 있습니다: “모든 기준을 검증해 주세요.”
하지만 에이전트는 하나를 건너뛸 수 있습니다. 긴 실행 과정 중에 흐름을 놓칠 수 있습니다. 무언가를 확인했다고 주장할 수 있습니다. 실제로는 기준을 증명하지 못하는 테스트를 가리킬 수도 있습니다. 긴 세션이 끝난 후, 듣기에는 좋지만 신뢰할 수 있는 흔적을 남기지 않는 그럴싸한 요약을 만들어낼 수도 있습니다.
이것이 규칙(rule)과 경계(boundary)의 차이입니다.
규칙은 이렇게 말합니다:
제발 이런 방식으로 해주세요.
경계는 이렇게 말합니다:
이것이 완료될 때까지 앞으로 나아갈 수 없습니다.
AI 코딩에서 그 차이는 매우 중요합니다.
만약 검증(Verification)이 프롬프트(Prompt) 안에만 존재한다면, 그것은 에이전트(Agent)의 행동 일부가 됩니다.
만약 검증이 시스템 내에 존재한다면, 그것은 인프라(Infrastructure)의 일부가 됩니다.
검증은 데이터 모델(Data Model)로 이동해야 합니다
제 워크플로우의 다음 버전은 단순한 가정에 기반했습니다:
증거(Evidence)가 첨부되지 않는 한, 수락 기준(Acceptance Criterion)이 done 상태로 이동하는 것을 허용해서는 안 된다는 것입니다.
단순한 댓글로서가 아닙니다.
마크다운(Markdown)의 장식으로서도 아닙니다.
"괜찮아 보임(looks good)"과 같은 식이어도 안 됩니다.
반드시 구조화된 상태(Structured State)로서 존재해야 합니다.
이는 명세(Spec) 자체에 데이터 모델이 필요함을 의미했습니다. 기준(Criterion)은 상태(Status)를 가집니다. 기준은 증거(Evidence)를 가집니다. 기준은 특정 단계(Phase)에 속합니다. 그리고 이러한 규칙을 위반하는 모든 쓰기 작업(Write Operation)은 거부됩니다.
이로써 검증은 에이전트에게 보내는 요청에서 시스템에 의해 강제되는 조건(Condition)으로 변화했습니다.
역할 분리(Role Separation) 또한 그만큼 중요했습니다.
코드를 작성하는 에이전트가 코드가 증명되었는지 여부를 결정하는 유일한 주체가 되어서는 안 됩니다. 따라서 독립적인 인스턴스(Instance)가 각 기준을 평가해야 했습니다:
- 무엇이 요구되었는가?
- 무엇이 변경되었는가?
- 어떤 테스트나 체크가 이를 증명하는가?
- 증거가 충분한가?
- 프로젝트 규칙을 위반했는가?
- 이 단계가 다시 루프(Loop)로 돌아가야 하는가?
이것은 마지막에 수행하는 전형적인 리뷰(Review)가 아닙니다.
가장 작고 유용한 경계인 수락 기준(Acceptance Criterion) 단계에서 이루어지는 검증입니다.
왜 파일 기반(File-based)으로 만들었는가
저는 데이터베이스(Database), 프로젝트 관리 도구(Project Management Tools), 외부 저장소(External Stores) 등 다양한 접근 방식을 시도했습니다.
결국, 가장 빠르고 견고한 모델은 놀라울 정도로 단순했습니다:
코드베이스(Codebase) 내부의 직접적인 파일들.
파일이 화려하기 때문이 아닙니다. 전혀 그렇지 않습니다. 하지만 로컬에서 실행되는 AI 에이전트들에게 파일은 거의 완벽합니다.
파일은 빠릅니다.
버전 관리(Versionable)가 가능합니다.
코드가 존재하는 곳에 함께 존재합니다.
추가적인 인프라 없이 작동합니다.
사람이 읽을 수 있습니다.
그리고 AI 프로세스 자체를 저장소(Repository)의 일부로 만듭니다.
마지막 포인트가 중요했습니다.
AI가 코드베이스 내부에서 더 깊게 작동하게 된다면, 코드만이 버전 관리 (Version Control)의 대상이 되어서는 안 됩니다. 코드를 도출해낸 프로세스 또한 버전 관리되어야 합니다.
테스트 (Tests), 게이트 (Gates), 수락 기준 (Acceptance Criteria), 커밋 규칙 (Commit Rules), 검증 로직 (Validation Logic) — 이 중 그 어느 것도 단 한 사람의 머릿속이나 채팅 기록 속에만 머물러서는 안 됩니다.
이것들은 프로젝트의 일부가 되어야 합니다.
그로부터 탄생한 워크플로우
이러한 실험들은 결국 제가 현재 anchored라고 부르는 시스템이 되었습니다.
anchored는 제가 **명세 검증 코딩 (Spec-Validated Coding)**이라고 부르는 워크플로우를 위한 Claude Code 플러그인입니다.
아이디어는 간단합니다:
명세 (Spec)가 무엇이 중요한지를 정의합니다. 시스템은 그 증거를 강제합니다.
모든 작업은 동일한 네 가지 단계를 거칩니다:
1. plan (계획)
작업은 단계별로 나뉩니다.
단순히 대략적인 계획이 아니라, 테스트 가능한 수락 기준 (Acceptance Criteria)과 함께 나뉩니다. 중요한 점은 각 기준이 나중에 누군가 — 또는 무언가가 — 실제로 충족되었는지 검증할 수 있는 방식으로 작성되어야 한다는 것입니다.
기준이 테스트 가능하지 않다면, 그것은 좋은 기준이 아닙니다.
2. refine (정제)
계획을 실제 코드베이스 및 프로젝트 규칙과 대조하여 확인합니다.
이 단계는 제가 처음에 과소평가했던 단계 중 하나입니다. 많은 문제는 구현 중에 시작되지 않습니다. 그보다 더 일찍 시작됩니다. 불분명한 요구사항, 누락된 엣지 케이스 (Edge Cases), 기존 아키텍처에 대한 잘못된 가정, 또는 너무 느슨한 수락 기준 등이 그것입니다.
refine은 코드 한 줄이 작성되기 전에 계획이 더 구체화되도록 강제합니다.
제가 다시 구축하는 과정에서, 이 단계는 문제가 코드로 변하기 전에 여러 차례 간극을 메워주었습니다. 때로는 단순히 “자동 수정됨 (AUTO-FIXED): 수락 기준 누락”과 같은 방식으로 말이죠.
구현되지 않은 버그는 디버깅할 필요도 없습니다.
3. build (구축)
구현은 단계별로 진행됩니다.
하지만 에이전트(Agent)는 단순히 코드가 컴파일되거나 테스트가 통과(Green)되었다는 이유만으로 단계를 완료로 표시할 수 없습니다. 각 기준에는 증거가 필요합니다.
그리고 그 증거는 검증됩니다.
증거가 충분하지 않다면, 해당 단계는 거부됩니다. 규칙이 위반되면, 해당 단계는 거부됩니다. 기준(criterion)이 누락되면, 실행(run)은 깔끔하게 앞으로 나아갈 수 없습니다.
이것이 기술 기반 워크플로우 (skill-based workflow)와의 핵심적인 차이점입니다.
에이전트 (agent)에게 단순히 검증을 요청하는 것이 아닙니다.
반드시 검증해야만 합니다.
4. wrap
마지막에 실행 (run)이 요약됩니다.
작은 작업의 경우, 이는 작업에 대한 검토입니다. 더 큰 노력의 경우, 여러 작업이나 단계에 걸친 통합 (roll-up)이 될 수 있습니다.
중요한 점은 wrap이 검증을 소급하여 수행하는 단계가 아니라는 것입니다.
그 시점에는 이미 검증이 완료된 상태입니다.
wrap은 무엇이 증명되었는지를 요약합니다.
두 번째 빌드
Anchored 방식이 존재하게 된 후, 이것이 실제로 차이를 만들어내는지 알고 싶었습니다.
그래서 실패했던 프로젝트를 다시 빌드했습니다.
수정(fixed)한 것이 아닙니다.
정리(cleaned up)한 것도 아닙니다.
다시 빌드(rebuilt)했습니다.
동일한 도메인. 동일한 일반적 문제. 하지만 이번에는 첫 번째 줄부터 Anchored 워크플로우 내부에서 진행했습니다.
결과가 더 나을 것이라고 확신했습니다.
수치는 여전히 저를 놀라게 했습니다.
| 지표 (Metric) | 이전 빌드 (Old build) | Anchored를 사용한 새 빌드 (New build with anchored) |
|---|---|---|
| 증거가 포함된 기준 (Criteria with evidence) | 915개 중 0개 | 514개 중 514개 |
| ... |
중요한 주의 사항: 이것은 깨끗한 A/B 테스트가 아니었습니다.
두 번째 빌드는 분명히 경험으로부터 이득을 얻었습니다. 저는 도메인을 더 잘 알게 되었습니다. 어떤 아키텍처 결정이 첫 번째 시도에서 문제를 일으켰는지 알고 있었습니다. 실패한 시도로부터 배웠습니다.
따라서 소요 시간, 커밋 (commits), 프롬프트 (prompts), 토큰 (tokens)은 유용한 맥락(context)이지만, 과학적인 증거는 아닙니다.
정말로 중요한 문장은 따로 있습니다:
514개의 기준 중 514개 모두에 증거가 첨부되어 있었습니다.
이전 빌드는 그러한 흔적을 전혀 만들어낼 수 없었습니다.
이전 프로젝트에서 "검증됨 (verified)"은 궁극적으로 하나의 느낌이었습니다.
새로운 프로젝트에서 그것은 시스템 내의 상태 (state)였습니다.
실제로 무엇이 변했는가
1. 나는 더 이상 검증 루프 (verification loop)가 아니었다
이것이 가장 큰 차이점이었습니다.
속도가 아닙니다.
토큰 소비량도 아닙니다.
코드 품질조차도 아닙니다.
가장 큰 차이점은 프로세스의 무결성 (integrity)이 더 이상 저의 규율 (discipline)에 의존하지 않게 되었다는 점입니다.
이전 빌드에서는 제가 병목 현상 (bottleneck)이었습니다. 매 실행이 끝날 때마다 저는 검토하고, 후속 질문을 던지고, 프롬프트 (prompt)를 다듬고, 발견 사항을 기록하고, 보고서를 읽고, 에이전트 (agent)가 다시 테스트하도록 만들어야 했습니다.
새로운 빌드에서는 제가 결과를 확인하기도 전에 검증기 (validator)가 그 작업의 상당 부분을 처리했습니다.
증거가 불충분하면 해당 단계는 거부되었습니다.
규칙을 위반하면 해당 단계는 거부되었습니다.
수락 기준 (acceptance criterion)이 제대로 충족되지 않으면 해당 단계는 열린 상태로 유지되었습니다.
대부분의 경우 저는 녹색 결과만을 확인했지만, 검사할 수 있는 추적 경로 (trail)가 함께 있었습니다.
자율적인 AI 코딩 실행 (autonomous AI coding runs)과 함께 작업할 때는 이것이 완전히 다르게 느껴집니다.
2. 붕괴된 왕복 과정 (round-trips)의 해결
이전 프로젝트에서는 제가 수동으로 수정을 주도했습니다.
티켓에 기록된 687개의 발견 사항.
48개의 조사 및 재시도 보고서.
9번이나 반복된 하나의 테스트 장면.
그것은 "AI 보조 개발 (AI-assisted development)"이 아니었습니다.
그것은 매우 빠른 주니어 개발자가 저지른 일을 사람이 뒤처리하는 것이었습니다.
새로운 워크플로 (workflow)에서는 그러한 루프 (loop) 중 상당수가 시스템 내부에서 발생했습니다.
검증기가 문제를 발견하고, 단계를 거부하면, 빌드 에이전트 (build agent)가 이를 수정하고, 다시 검증기가 확인하는 방식입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기