GenAI 개발의 오류 #1: 더 빠른 코드 생성(Code Generation)이 더 빠른 엔지니어링을 의미하는가
요약
생성형 AI를 통한 코드 생성 속도 향상이 전체 엔지니어링 속도의 향상으로 직결되지 않는다는 점을 지적합니다. AI 도입 후 발생하는 코드 리뷰 과부하와 인지 부채 등 '환멸의 계곡' 현상의 원인을 분석합니다.
핵심 포인트
- 코드 생성 속도와 엔지니어링 생산성은 별개임
- AI 도입 후 코드 리뷰 과부하 및 인지 부채 발생
- 엔지니어링은 시간에 걸쳐 통합된 프로그래밍 과정임
- 단순 코드 생성이 아닌 아키텍처 이해가 핵심
이 글은 생성형 AI (Generative AI)를 사용하여 구축할 때 팀들이 범하는 잘못된 가정들에 관한 8개의 포스트 시리즈 중 첫 번째입니다. 각 가정은 그럴듯하게 들리지만, 아키텍처 (Architectural) 실패로 이어지며, 이미 먼저 교훈을 얻은 분야에서 해결된 문제들입니다.
시기적 배경에 대하여: 이 시리즈는 AI 보조 개발 (AI-assisted development)에 대한 환멸의 계곡 (Trough of disillusionment)이 시작되었기 때문에 작성되었습니다.
Gartner의 하이프 사이클 (Hype cycle)이 변하고 있습니다. Byron Cook (Amazon 부사장, 자동 추론 담당)은 이를 명확하게 말합니다: "생성형 AI (Generative AI)가 환멸의 계곡으로 미끄러져 들어가고 있습니다." 헤드라인이 바뀌고 있습니다 — "바이브 코딩 (Vibe coding)의 여름은 끝났다." 6~12개월 전에 AI 코딩 도구를 도입한 팀들은 병목 현상에 직면하고 있습니다: 코드 리뷰 (Code review)의 과부하, 인지 부채 (Cognitive debt)의 누적, 아무도 진단할 수 없는 운영 환경의 버그 (Production bugs), 그리고 아무도 코드베이스 (Codebase)를 이해하지 못해 아무도 수행할 수 없는 아키텍처 변경 (Architectural changes).
환멸은 AI가 쓸모없기 때문에 발생하는 것이 아닙니다. AI 보조 코딩 (AI-assisted coding)은 실질적인 생산성 향상을 제공합니다. 환멸은 이 8가지 오류, 즉 생산성이 '어디서' 오는지와 생성이 빨라질 때 '무엇이' 변하는지에 대한 잘못된 가정들 때문에 발생합니다. 팀들은 10배 빠른 엔지니어링 (10x engineering)을 기대했습니다. 하지만 그들이 얻은 것은 10배 빠른 코드 생성 (10x code generation)과 나머지 모든 것에서의 1배 속도였습니다. 기대와 현실 사이의 격차가 바로 환멸의 계곡입니다.
계곡을 벗어나는 방법은 AI 코딩 도구를 포기하는 것이 아닙니다. 계곡을 피할 수 없게 만든 잘못된 가정들을 바로잡는 것입니다. 이 시리즈의 각 포스트는 하나의 가정을 지목하고, 그것이 왜 실패하는지 보여주며, 이론이 아닌 동일한 벽에 부딪혔다가 극복해낸 분야들의 해결책을 제시합니다.
이러한 가정들을 먼저 바로잡는 팀은 다른 모든 이들보다 앞서 계곡에서 벗어날 것입니다. 그렇지 못한 팀은 그 안에서 수년을 보내게 될 것입니다.
오류 (The Fallacy)
"AI는 코드를 10배 더 빠르게 작성한다. 따라서 엔지니어링도 10배 더 빨라진다."
이것이 유혹적인 이유
데모는 매우 설득력이 있습니다. 당신이 기능을 설명하면, 에이전트(Agent)가 코드를 작성합니다. 몇 시간이 아닌 몇 분 만에 말이죠. 당신은 그것을 배포(Ship)하고, 기능은 정상적으로 작동합니다. 작성된 코드 라인 수, 병합된 PR(Pull Request) 수, 전달된 기능 등 속도(Velocity)는 실질적이며 측정 가능합니다. 팀장은 경영진에게 10배의 개선을 보고합니다. 경영진은 더 많은 AI 도구(Tooling)에 자금을 지원합니다. 모두가 흥분합니다.
이 흥분 아래 깔린 가정은 다음과 같습니다: 코드 생성(Code Generation)이 병목 현상(Bottleneck)이었다는 것입니다. 그것을 더 빠르게 만들면, 모든 것이 더 빨라질 것이라고 믿습니다. 마치 느린 프린터를 빠른 프린터로 교체하는 것과 같습니다. 같은 문서를 출력하되, 기다리는 시간은 줄어드는 것처럼 말이죠.
이것이 틀린 이유
엔지니어링(Engineering)은 코드 생성이 아닙니다. Google의 Titus Winters가 언급했듯이, 엔지니어링은 시간에 걸쳐 통합된 프로그래밍(Programming integrated over time)입니다. 코드 생성은 더 큰 시스템 내의 하나의 하위 시스템(Sub-system)일 뿐입니다. 다른 하위 시스템들은 다음과 같습니다:
- 컴파일 (Compilation): 코드가 많아지면 컴파일 시간(Compile times)이 길어집니다. 바이너리(Binaries) 크기가 커집니다. 빌드(Builds)가 더 빈번해집니다.
- 테스트 (Testing): 의존성(Dependencies)은 코드베이스 크기에 따라 이차 함수적으로 증가합니다. 코드가 10배 늘어나면 테스트를 위한 컴퓨팅 자원(Test compute)은 100배 더 필요할 수 있습니다.
- 코드 리뷰 (Code review): 리뷰어들은 10배 더 큰 변경 사항을 마주하거나, 10배 더 많은 변경 사항을 마주하게 됩니다. 이들이 병목 현상이 되거나, 혹은 대충 승인(Rubber-stamp)해 버리는데, 이는 더 나쁜 상황입니다.
- 버전 관리 (Version control): 10배의 처리량(Throughput)에 최적화되어 있지 않습니다. 아무도 계획하지 않았던 성능 한계가 나타납니다.
- 릴리스 및 롤백 (Release and rollback): 문제를 감지할 수 있는 속도보다 더 빠르게 릴리스한다면, 롤백(Rollback) 체계가 무너집니다. 이제 각 롤백은 여러 개의 충돌하는 변경 사항들과 싸워야 합니다.
- 인간의 이해 (Human understanding): 개발 과정에서 아무도 정신적 모델(Mental model)을 구축하지 않았습니다. 프로그램의 이론(Theory)이 형성되지 않은 상태입니다.
- 검증 (Verification): 기존에 존재하던 것과 동일한 스캐너(Scanners)와 테스트를 사용합니다. 더 빨라지지도, 더 포괄적이지도 않습니다.
당신은 단 하나의 하위 시스템을 10배 빠르게 만들었습니다. 나머지 7개는 변하지 않았습니다. 시스템 전체가 10배 빨라지지는 않습니다. 오히려 빠른 하위 시스템과 느린 하위 시스템 사이의 인터페이스(Interfaces)에서 시스템이 망가집니다.
당신이 이미 이해하고 있는 비유
1980년대에는 CPU 클록 속도(Clock speeds)가 18개월마다 두 배씩 증가하기 시작했습니다. 하지만 메모리 속도는 그렇지 않았습니다. 1990년대 중반에 이르러 CPU는 10년 전보다 100배 더 빨라졌지만, DRAM은 기껏해야 10배 정도 빨라졌을 뿐입니다. 이 격차에는 이름이 있었습니다: 바로 메모리 벽 (the memory wall) 입니다.
1985: CPU ████░░░░░░
Mem ████░░░░░░ (대략적으로 일치함)
...
시스템은 더 빨라지지 않았습니다. CPU는 캐시 미스 (cache miss)가 발생할 때마다 메모리를 기다리며 유휴 상태 (idle)로 머물렀습니다. CPU를 더 빠르게 만드는 것은 문제를 더 악화시켰습니다. 속도가 빨라질수록 유휴 사이클 (idle cycles)이 늘어났고, 격차는 더 벌어졌습니다.
업계의 첫 번째 본능은 메모리를 더 빠르게 만드는 것이었습니다. 그것은 미미한 도움만 주었습니다. DRAM의 물리적 특성이 속도가 올라갈 수 있는 한계를 제한했기 때문입니다. 선도적인 하위 시스템 (CPU)은 뒤처지는 하위 시스템 (메모리)을 무차별적인 개선 (brute-force improvement)으로는 메울 수 없는 격차만큼 앞질러 버렸습니다.
이를 해결한 발명품은 바로 캐시 계층 구조 (the cache hierarchy) 였습니다. L1, L2, L3 — CPU와 느린 DRAM 사이의 작고 빠른 계층들입니다. 캐시는 메인 메모리를 더 빠르게 만드는 것이 아닙니다. 요청의 95%를 느린 계층에 도달하기 전에 처리하는 빠른 중개자 (intermediary)를 배치하는 것입니다.
이제 이를 매핑해 봅시다:
CPU 속도 = 코드 생성 속도 (AI 에이전트)
메모리 속도 = 인간의 검증 속도 (코드 리뷰, 이해)
메모리 벽 (The memory wall) = 엔지니어링 병목 현상 (리뷰, 테스트, 이해)
...
CPU를 더 빠르게 만든다고 해서 메모리 벽을 해결할 수 없습니다. 코드 생성 속도를 높인다고 해서 엔지니어링 병목 현상을 해결할 수 없습니다. 두 문제 모두 빠른 생산자 (producer)와 느린 소비자 (consumer) 사이에 빠른 중개자를 추가함으로써 해결됩니다.
붐 (The boom)
오직 더 빠른 코드 생성에만 투자하는 팀은 다음과 같은 과정을 경험합니다:
1~3개월 차: 속도 급증. 데모는 인상적입니다. 경영진은 흥분합니다. PR (Pull Request)이 더 빨리 머지(merge)됩니다. 기능이 더 빠르게 출시됩니다. 지표(metrics)가 훌륭해 보입니다.
4~6개월 차: 코드 리뷰가 병목 현상이 됩니다. 리뷰어들이 따라잡지 못합니다. 그들은 요령을 피우기 시작합니다 — 이해 없이 승인하거나, 읽는 대신 훑어봅니다. 또는 그들이 제약 요인 (constraint)이 되어 파이프라인을 막고, 빠르게 움직이는 엔지니어들을 좌절시킵니다.
7-9개월 차: 아무도 진단할 수 없는 운영 환경(production)의 버그들. 코드는 작동하지만, 그것이 '어떻게(HOW)' 작동하는지 아무도 이해하지 못합니다. AI가 생성한 코드를 디버깅(debugging)하는 것이 직접 코드를 작성하는 것보다 더 오래 걸리는데, 개발자가 기반으로 삼을 수 있는 멘탈 모델(mental model)이 없기 때문입니다. 기능 전달(feature delivery) 속도는 빨라지지만, 장애 대응(incident response)은 오히려 느려집니다.
10-12개월 차: 아키텍처 변경(architectural change) — 리팩터링(refactor), 마이그레이션(migration), 보안 개편(security overhaul) 등이 필요해집니다. 하지만 코드베이스(codebase)를 이해하는 사람이 아무도 없기에 아무도 이를 수행할 수 없습니다. 몇 달 만에 구축된 시스템은 몇 달 만에 변경될 수 없습니다. 인지 부채(cognitive debt)가 상환 기일을 맞이합니다.
이러한 궤도에 있는 모든 팀은 메모리 벽(memory wall)을 경험하고 있습니다. 코드 생성 서브시스템(CPU)이 검증 및 이해 서브시스템(memory)보다 앞서 나갔습니다. 시스템 수준의 처리량(throughput)은 증가하지 않았습니다. 병목 현상(bottleneck)이 "코드 작성"에서 "코드 이해"로 이동했으며, 이는 훨씬 더 비용이 많이 드는 작업입니다.
해결책
메모리 벽에 대한 해결책은 더 빠른 CPU가 아니었습니다. 그것은 캐시(cache)였습니다. 엔지니어링 병목 현상에 대한 해결책은 더 빠른 코드 생성(code generation)이 아닙니다. 그것은 명세 계층(specification layer) — 즉, 코드 머신과 인간의 이해 사이를 잇는 빠른 중개자입니다.
이전: AI 에이전트(AI agent) → 코드 → 인간이 모든 줄을 검토 → 배포(ship)
(모든 변경 사항이 벽에 부딪힘)
...
명세 게이트(specification gate)는 선언된 속성(properties)에 따라 모든 변경 사항을 기계적으로, 결정론적으로(deterministically), 코드 생성 속도에 맞춰 확인합니다. 속성의 예로는 다음과 같습니다: "인증 없는 공개 엔드포인트(public endpoint) 없음", "IAM 그래프 내 권한 상승(privilege escalation) 경로 없음", "PII(개인정보) 필드를 노출하는 API 응답 없음". 각 속성은 모든 변경 사항에 대해 확인됩니다. 변경 사항은 속성을 충족하거나, 충족하지 못하거나 둘 중 하나입니다.
인간은 명세(작고, 안정적이며, 변화가 느림)를 검토합니다. 머신은 명세에 따라 코드를 검증합니다(빠르고, 철저하며, 모든 변경 사항에 대해 수행). 검토의 병목 현상은 사라집니다. 이는 검토를 생략했기 때문이 아니라, 검토의 수준이 적절한 곳으로 이동했기 때문입니다. 인간은 의도(intent)를 검토하고, 머신은 구현(implementation)을 검증합니다.
여러분은 이미 이러한 캐시 계층 구조(cache hierarchy)의 일부를 보유하고 있습니다. 타입 시스템(Type systems)은 L1 캐시입니다. 만약 AI가 타입 계약(type contract)을 위반하는 코드를 생성하면, 컴파일러가 사람이 확인하기 전에 즉시 이를 잡아냅니다. 자동화된 테스트 스위트(Automated test suites)는 L2 캐시입니다. 생성된 코드가 동작 계약(behavioral contract)을 위반하면, CI(지속적 통합)가 머지(merge) 전에 이를 잡아냅니다. 부족한 것은 L3입니다. 즉, 아무도 테스트를 작성하지 않은 속성들, 즉 보안 불변량(security invariants), 아키텍처 경계(architectural boundaries), 서비스 간 계약(cross-service contracts)을 확인하는 사양 게이트(specification gate)가 필요합니다.
사양(specifications)이 새로운 산출물일 필요는 없습니다. 모듈 인터페이스(Module interfaces), API 계약(API contracts), 타입 시그니처(type signatures), 데이터베이스 스키마(database schemas) 등은 이미 모든 성숙한 코드베이스에 존재합니다. Parnas는 1972년에 이미 이것들을 만들라고 우리에게 말했습니다. 부족한 조각은 사양이 아닙니다. 그것은 기계적인 강제(mechanical enforcement)입니다.
이를 예측한 법칙
300만 개 이상의 특허 분석에서 도출된 TRIZ의 '하위 시스템의 비균등 진화 법칙(Law of Non-Uniform Evolution of Sub-Systems)'은 다음과 같습니다:
시스템의 다양한 부분들의 진화 속도는 균일하지 않다. 시스템이 복잡할수록 그 구성 요소들의 진화는 더욱 비균등해진다. 이러한 비균등성은 시스템 충돌을 야기하며, 이를 해결하기 위해서는 새로운 발명이 필요하다.
하나의 하위 시스템은 진화했습니다(코드 생성). 하지만 다른 하위 시스템들은 진화하지 않았습니다(검증, 이해, 테스트, 검토). 이 비균등성이 시스템 충돌(병목 현상, 인지 부채, 아키텍처 마비)을 만들어냈습니다. 이러한 충돌은 새로운 발명(사양 계층 — 개발자 생태계를 위한 캐시 계층 구조)을 요구합니다.
이 법칙은 메모리 벽(memory wall)을 예측했습니다. 항공 분야에서의 플라이 바이 와이어(fly-by-wire) 요구 사항을 예측했습니다(엔진이 조종사가 반응할 수 있는 속도보다 빨라졌습니다). 금융 거래에서의 사전 거래 리스크 체크(pre-trade risk checks)를 예측했습니다(알고리즘이 인간의 감독이 따라갈 수 있는 속도보다 빨라졌습니다). 그리고 이 법칙은 AI 지원 개발을 위한 사양 계층을 예측합니다. 동일한 구조적 이유로, 동일한 종류의 충돌을 해결하기 위해서 말입니다.
이번 주에 당신이 할 수 있는 일
코드 생성에 더 많이 투자하지 마십시오. 뒤처지고 있는 하위 시스템에 투자하십시오.
1. 실제 병목 현상(bottleneck)이 어디인지 측정하십시오. "PR 오픈(PR opened)"부터 "PR 머지(PR merged)"까지의 시간. "버그 보고(bug reported)"부터 "원인 파악(root cause identified)"까지의 시간. "아키텍처 결정(architectural decision made)"부터 "영향을 받는 모든 코드 업데이트(all affected code updated)"까지의 시간. 만약 코드 생성(code generation)이 빨라짐에 따라 이 중 어느 하나라도 늘어났다면, 그것이 바로 당신의 뒤처지는 하위 시스템(sub-system)입니다.
2. 기존의 사양(specification) 하나를 선택하여 기계적으로 강제하십시오. 당신의 API 계약(API contract). 당신의 데이터베이스 스키마(database schema). 당신의 모듈 인터페이스(module interface). 모든 변경 사항이 이를 충족하는지 검증하는 CI 체크(CI check)를 추가하십시오. 사람이 검토하는 것이 아니라 결정론적(deterministically)으로 검증해야 합니다. 예를 들어, OpenAPI 사양을 신뢰할 수 있는 단일 원천(source of truth)으로 취급하고, 생성된 코드가 여기서 벗어나면 빌드를 실패하게 만드십시오. 단 하나의 사양. 단 하나의 게이트(gate). 이번 주에 실행하십시오.
3. 효과를 측정하십시오. 해당 게이트가 리뷰 프로세스(review process)에서 놓쳤을 법한 것을 잡아냈습니까? 리뷰어가 기계적으로 검증된 속성들을 건너뛸 수 있게 되어 리뷰 프로세스가 빨라졌습니까? 변경 사항에 대한 팀의 신뢰도가 높아졌습니까?
캐시(cache)가 메모리(memory) 자체를 빠르게 만드는 것은 아닙니다. 사양 게이트(specification gate)가 인간을 빠르게 만드는 것도 아닙니다. 두 가지 모두 간극이 가장 큰 곳에 빠른 중간 매개체를 배치함으로써 문제를 해결합니다. CPU-메모리 벽(CPU-memory wall)은 30년 전에 해결되었습니다. 코드 생성-검증 벽(code-generation-verification wall)도 동일한 문제입니다. 해결책 또한 동일한 아키텍처입니다.
_시리즈 다음 편: 오류 #2 — "출력이 올바르게 보인다면, 그것은 올바른 것이다." * 그럴듯함(plausible)이 왜 올바름(correct)이 아닌지, 형식 검증(formal verification)이 그 차이에 대해 무엇을 가르쳐 주는지, 그리고 왜 가장 많은 코드를 배포하는 팀이 종종 가장 많은 부채(debt)를 안고 있는 팀인지에 대해 다룹니다.*
GenAI 개발의 오류: 모든 팀이 저지르고 있는 8가지 가정. 각각의 가정은 아키텍처 실패로 이어집니다. 그리고 각각은 이미 해결된 문제입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기