당신의 개발 스택에 안돈 코드(Andon Cord)가 없는 이유: 그것이 당신의 시스템을 망가뜨린다.
요약
AI 에이전트가 대규모 코드를 초고속으로 작성하는 시대에는 코드의 품질을 검증하고 멈출 수 있는 '안돈 코드(Andon Cord)' 시스템이 필수적입니다. Bun의 Zig에서 Rust로의 재작성 사례를 통해, AI 생성 코드의 잠재적 위험성과 이를 제어할 메커니즘의 중요성을 경고합니다.
핵심 포인트
- AI 에이전트는 언어 장벽 없이 대규모 코드를 단기간에 작성 가능함
- AI 생성 코드는 인간 작성 코드 대비 unsafe 블록 밀도가 매우 높을 수 있음
- 속도보다 중요한 것은 오류를 즉시 감지하고 중단할 수 있는 거부 메커니즘임
- 개발자의 역할이 코드 작성에서 에이전트의 실수를 식별하는 방향으로 변화함
지금까지 여러분은 자신이 아는 것, 팀이 아는 것, 또는 프로젝트가 요구하는 것에 기반하여 프로그래밍 언어를 선택했습니다. 데이터 스크립트에는 Python을, 백엔드 서비스에는 Go를, 드라이버에는 C나 어셈블리를 사용했죠. 논리는 간단했습니다. 특정 언어에 익숙한 인간은 결과물을 만들어내는 사람이라는 것이었습니다.
이 논리는 이제 끝났습니다.
Bun은 Claude를 주 에이전트로 사용하여 6일 만에 Zig의 960,000 라인을 Rust로 재작성했습니다. 추가된 라인은 1,009,257라인이었고, 커밋은 6,755개였으며, 기존 테스트 중 99.8%가 통과했습니다. 그 결과: AI가 생성한 Rust 코드에는 13,044개의 unsafe 블록이 포함되었는데, 이는 uv, Astral의 Python 패키지 매니저(비교를 위해 수작업으로 작성된 350,000라인의 Rust)에 있던 73개보다 훨씬 많은 숫자입니다. Rust 컴파일러는 이 모든 것을 허용했습니다. 하지만 인간들은 아니라고 했습니다. 무려 13,044번이나요. 차이점은 에이전트가 아니었습니다. 바로 **안돈 코드(andon cord)**였습니다.
인간은 더 이상 코드를 직접 작성하지 않습니다. 그저 설명만 하고, 에이전트가 코드를 작성합니다. Claude Code, Cursor, Copilot Workspace (래퍼는 중요하지 않습니다). 에이전트는 Rust를 어렵게 느끼지 않습니다. 보로(borrow checker)의 개념을 체득하는 데 3년이 필요할 필요도 없습니다. 어떤 언어로든 같은 속도와 무관심함으로 코드를 작성합니다.
따라서 새로운 프로젝트를 시작할 때, 질문은 바뀝니다. 자신이 어느 언어를 아느냐가 아니라, 에이전트에게 무엇을 잘못했는지 알려주고 500라인 이상의 실수가 쌓이기 전에 멈출 수 있을 만큼 충분히 빠르게 알 수 있는 언어는 무엇인가 하는 것입니다.
CI(Continuous Integration)가 '쓰레기'라고 불렀다
PR #30412는 5월 14일에 병합되었습니다. 새로운 Rust 코드 1,009,257라인이 추가되었고, 4,024라인이 삭제되었으며, 파일은 2,188개가 변경되었습니다. 첫 커밋부터 main까지 단 6일 만에 완료되었습니다. 바이너리 크기는 3MB에서 8MB로 줄었습니다. 리눅스 x64 환경에서 테스트 스위트의 99.8%가 통과했습니다.
GitHub의 CI는 이 Zig 삭제 PR을 자동으로 'ai slop'이라고 태그했습니다. 아무도 그 규칙을 수동으로 설정하지 않았습니다.
Hacker News의 토론은 742개의 댓글과 667개의 포인트를 기록했습니다. 기술 언론은 헤드라인으로 쓰기 좋은 수치인 '6일 만에 100만 줄'에 주목했습니다. 하지만 구조적인 각주에 대해서는 주목도가 낮았습니다. 즉, 완전히 수작업으로 작성된 350,000줄 규모의 유사한 Rust 프로젝트인 uv에는 73개의 unsafe 블록이 있었던 반면, AI가 생성한 Rust 코드에는 13,044개의 unsafe 블록이 있었습니다. 총합으로는 약 178배 더 많은 unsafe 블록이 있는 셈입니다. 코드 라인당 밀도로 계산하면 약 62배에 달합니다.
Rust 컴파일러는 그 모든 라인을 하나도 빠짐없이 승인했습니다.
Bun을 만든 Jarred Sumner는 팀이 "이미 몇 달 동안 직접 코드를 타이핑하지 않고 있다"고 확인했습니다. Zig에서 Rust로의 전환은 부분적으로 강제된 측면이 있습니다. Zig의 핵심 팀은 명시적인 'AI 기여 금지(no-AI-contributions)' 정책을 가지고 있으며, 이는 2025년 12월 Anthropic이 해당 프로젝트를 인수한 순간부터 Bun의 워크플로우와 호환되지 않게 되었습니다. 팀은 업스트림(upstream) 문화를 거스르는 대신 언어를 변경했습니다.
이것은 실패한 재작성(rewrite)이 아닙니다. 코드는 작동합니다. 이것은 적절한 거부 메커니즘(rejection mechanism) 없이 속도에만 치중하여 생성할 때 무엇이 통과되는지를 보여주는 사례입니다.
모두가 Rust를 말하지만, 아무도 그 이유를 말하지 않는다
개발자에게 왜 에이전트(agent)가 Rust를 사용해야 하는지 물으면 성능에 관한 답변을 듣게 됩니다. 더 빠른 바이너리, 메모리 안전성(memory safety), 제로 비용 추상화(zero-cost abstractions) 같은 것들 말입니다. 그 답변이 틀린 것은 아니지만, 에이전트에게 특화된 관점에서는 잘못된 이유입니다.
Rust는 2016년 이후 매년 Stack Overflow의 "가장 사랑받는 언어(most loved language)" 조사에서 1위를 차지해 왔습니다. 하지만 이 조사는 응답자가 실제로 그 언어를 작성하는 사람인지 묻지 않습니다. 오랫동안 "가장 사랑받는 언어"와 "가장 많이 사용되는 언어"는 매우 분리된 목록이었습니다 (빌로우 체커(borrow checker)가 채택 곡선에 미치는 영향 때문입니다). 에이전트에게는 채택 곡선이 없습니다. 그들은 빌로우 체커에 대해 감정을 느끼지 않습니다. 그들에게 있는 것은 컴파일 루프(compile loop)뿐입니다.
런타임 벤치마크 수치는 에이전트의 피드백 루프를 바꾸지 못합니다. 실행 시점에 Go보다 40% 더 빠르게 실행되는 Rust 바이너리는, 에이전트가 생성 과정에서 더 나은 코드를 작성하는지 여부와는 별개의 문제입니다. 바이너리의 속도는 에이전트가 실수를 잡아내는 속도에 영향을 주지 않습니다.
중요한 것은 환경이 에이전트에게 실수를 저질렀음을 얼마나 빠르고 정확하게 알려주느냐 하는 것입니다.
구문(Syntax)은 유효하지만 의미론(Semantics)이 깨져 있는 상태이며, 런타임(Runtime)에서 무언가 실패할 때까지는 아무런 신호가 없습니다. 그때가 되면 이미 잘못된 가정 위에 3개의 함수가 더 작성된 상태입니다. 연쇄 반응은 멈추지 않았습니다. Rust는 에이전트에게 즉시 알려줍니다: 컴파일 실패, 에러 메시지, 위치, 타입 불일치(Type mismatch), 그리고 수정 경로까지 말이죠. 에이전트는 이를 읽고, 수정하고, 다시 실행합니다.
안돈 코드 (The Andon Cord)
1950년대 도요타 공장에서는 모든 생산 라인을 따라 줄을 설치했습니다. 어떤 작업자라도 언제든 그 줄을 당길 수 있었습니다. 그러면 라인 전체가 멈췄습니다. 결함이 있는 부품이 도착하면 줄을 당겼고, 다음 부품이 그 위에 부착되기 전에 문제를 해결했습니다.
그들은 이것을 _안돈 코드(Andon cord)_라고 불렀습니다. 라인 끝에서 40분 동안 재작업을 하는 것보다 2분 동안 멈추는 것이 더 저렴했습니다. 이 제약 조건은 전체 시스템을 느리게 만드는 것이 아니라 더 빠르게 만들었습니다.
컴파일러는 에이전트를 위한 안돈 코드입니다. 루프는 다음과 같이 작동합니다: 에이전트가 코드를 작성하면, 컴파일러가 이를 검사합니다. 컴파일러는 코드를 통과시키거나, 아니면 줄을 당겨 구조화된 진단(Structured diagnostic)을 내보냅니다. 에이전트는 진단 내용을 읽고, 문제를 수정하고, 다시 실행합니다. 안돈 코드가 없다면, 에이전트는 잘못된 가정 위에 500줄의 코드를 작성하게 되고, 문제는 3번의 세션이 지난 후 런타임에서 원인이 아닌 증상만을 가리키는 스택 트레이스(Stack trace)로 나타납니다. 안돈 코드가 있다면, 문제는 몇 초 만에 나타나며, 에이전트가 즉시 조치를 취할 수 있을 만큼 충분히 구체적인 컴파일러 출력으로 제공됩니다.
이것이 AI 지원 코드 품질의 진짜 변수입니다. 어떤 모델을 사용하는지, 얼마나 주의 깊게 프롬프트(Prompt)를 작성하는지가 아니라, 환경이 부채가 쌓이기 전에 에이전트가 스스로 수정할 수 있도록 충분히 빠르고 정밀한 진단과 함께 줄을 당겨주느냐 하는 것입니다.
(완전히 주제에서 벗어난 이야기입니다: 이번 주에 아이와 함께 오래된 Hanna-Barbera 만화들을 보고 있는데, 제한된 프레임 예산(limited-frame-budget)의 애니메이션 스타일이 그것을 만든 예산 제약이 사라진 지 한참 후에도 사람들이 여전히 모방하는 미학(aesthetic)이 되었다는 점에 대해 계속 생각하게 됩니다. 매체에 하드웨어적으로 각인된 제작 한계라니 말이죠. 컴파일러와는 상관없는, 그냥 제 뇌가 작동하는 방식입니다.)
안돈 코드(Andon Cord)는 존재 여부만큼이나 그 풍부함(richness)이 중요합니다. "42번 라인에서 에러 발생"이라고 말하는 컴파일러는 에이전트에게 위치를 제공합니다. 반면 "42번 라인에서 Option<&u32>를 u32로 곱하려고 시도하고 있습니다. 먼저 .unwrap()을 호출하거나 Option에 대해 match를 사용하세요"라고 말하는 컴파일러는 에이전트에게 위치, 두 가지 타입 모두, 무엇을 시도했는지, 그리고 복구 경로(repair path)까지 제공합니다. 에이전트는 아무것도 추론할 필요가 없습니다. 읽고, 적용하고, 다시 실행하면 됩니다.
이는 왜 CLI가 AI 에이전트에게 MCP보다 유리한지를 설명하는 것과 동일한 원리입니다. 여러분이 선택한 환경은 무언가 고장 났을 때 얼마나 많은 신호(signal)가 돌아올지를 결정합니다. 언어 선택은 더 낮은 수준에서의 동일한 결정입니다.
코드 없음에서 완전한 코드로 (From No Cord to Full Cord)
스펙트럼 전반에 걸쳐 동일한 시나리오가 적용됩니다. 여러분에게 딕셔너리(dictionary)가 있습니다. 키(key)가 누락되었습니다. 여러분은 그 값을 산술 연산에 사용하려고 시도합니다.
Python: 코드 없음 (no cord).
data = {"price": 100}
total = data["quantity"] * data["price"]
KeyError: 'quantity'가 런타임(runtime)에 발생하며, 이는 아마도 3단계 아래의 함수에서, 혹은 프로덕션(production) 환경에서 발생할 수 있습니다. 에이전트는 생성 시점에 아무런 신호도 받지 못했습니다. 체인은 실행되었습니다. 부품은 결함이 있었습니다. 당길 코드가 없었기 때문에 아무도 코드를 당기지 않았습니다.
TypeScript (noUncheckedIndexedAccess 설정 시): 부분적인 코드 (partial cord).
const data: Record<string, number> = { price: 100 };
const quantity = data["quantity"]; // type: number | undefined
const total = quantity * data["price"];
...
실행 전에 포착됩니다. 짧은 메시지이며, 위치와 타입 제약(type constraint)이라는 실행 가능한 정보를 제공합니다. TypeScript는 메모리 레이아웃(memory layout)이나 스레드 안전성(thread safety)을 도와주지는 않지만, 애플리케이션 계층 로직(application-layer logic)에서는 이러한 종류의 실수를 안정적으로 잡아냅니다.
Go: 구문적 안돈 코드(syntactic cord), 의미적 안돈 코드(semantic cord)의 부재.
data := map[string]int{"price": 100}
total := data["quantity"] * data["price"]
fmt.Println(total) // 0을 출력하며, 에러는 발생하지 않음
Go는 사용되지 않는 임포트(import)나 사용되지 않는 변수의 컴파일을 거부합니다. 진정한 위생(hygiene)이죠. 하지만 누락된 키에 대한 맵 조회(map lookup)는 제로 값(zero value)을 조용히 반환합니다. data["quantity"]는 0을 반환합니다. total은 0이 됩니다. 함수는 계속 실행됩니다. 하류(downstream)의 무언가가 잘못된 숫자를 받게 되고, 에러 메시지는 3개의 함수 뒤에서 증상(symptom)만을 가리키며 나타납니다. Stack Overflow에서는 이를 "Go가 작동하는 방식"이라고 부릅니다. 당신의 에이전트(agent)는 이를 버그라고 부릅니다.
Go는 일반적인 서비스 코드베이스에서 약 2초 만에 컴파일됩니다. Rust는 비슷한 코드에서 30초 이상 걸립니다. 제 생각에 TypeScript의 엄격 모드(strict mode)는 대부분의 웹 서비스 사용 사례에서 실제로 Go보다 약간 앞선다고 생각하지만, 높은 동시성(concurrency) 요구 사항이 있는 팀의 경우에는 제 생각이 틀릴 수도 있습니다. Go의 안돈 코드(cord)는 실재하지만, 단지 좁을 뿐입니다. 구조(structure)는 포착되지만, 의미(semantics)는 포착되지 않습니다.
Rust: 완전한 안돈 코드(full cord).
use std::collections::HashMap;
let mut data = HashMap::new();
...
error[E0369]: cannot multiply `Option<&u32>` by `u32`
--> src/main.rs:8:21
|
...
위치, 두 타입 모두, 시도된 내용, 그리고 복구 경로(4줄)를 제공합니다. 에이전트는 이를 읽고, 적용하고, 다시 실행합니다. Rust 컴파일러는 마치 당신의 성공에 개인적인 이해관계가 있는 것처럼 들리며, 에이전트에게 있어 그것이 바로 도구에 기대하는 바입니다.
Ada: 최대치의 안돈 코드(maximum cord).
Ada는 1983년에 군용 임베디드 시스템 (embedded systems)에서 오류로 인해 사람이 사망하는 것을 방지하기 위해 설계되었습니다. 초기화되지 않은 변수 (uninitialized variables), 정수 오버플로 (integer overflow), 배열 범위 위반 (array bounds violations), 암시적 형 변환 (implicit type conversions) 등이 모두 컴파일 타임 (compile time)에 기본적으로 포착되며, 그 진단 결과는 대립적이라고 느껴질 정도로 정밀합니다. 화성 탐사선 (Mars rover)은 Ada를 실행합니다. 제임스 웹 우주 망원경 (James Webb Space Telescope)도 Ada를 실행합니다. 해당 컴파일러들은 인간이 오늘 이 문제를 처리하고 싶은 기분이 드는지 단 한 번도 물어본 적이 없습니다.
업계는 일반적인 소프트웨어 용도로 Ada를 대체로 거부했는데, 그 엄격함이 인간 개발자들에게 너무 고통스러웠기 때문입니다. 너무 많은 격식(ceremony)이 필요했고, 너무 많은 것들에 명시적인 주석(explicit annotation)을 달아야 했습니다.
Ada: 인간에게는 너무 엄격합니다. 하지만 에이전트(Agents)는 상관하지 않습니다.
안돈 코드(Cord) 없는 속도는 부채다
안전벨트는 자동차가 느릴 때가 아니라 빨라졌을 때 의무화되었습니다. 회로 차단기 (Circuit breakers)는 알고리즘 트레이딩 (algorithmic trading)이 아무런 제동 장치 없이 초당 수천 개의 주문을 실행하기 시작한 이후에 금융 시장에 추가되었습니다. 패턴은 이렇습니다: 생성 속도(generation speed)는 그에 상응하는 규모의 거부 인프라 (rejection infrastructure)를 필요로 합니다.
Bun의 재작성 과정에서 나타난 13,044개의 unsafe 블록은 Claude의 코드 생성 실패가 아닙니다. 그것은 에이전트가 의도적으로 안돈 코드(cord)를 피해 간 지점들이며, 의미론적으로 복잡한 섹션에서 빌림 검사기 (borrow checker)를 우회하기 위해 Rust의 unsafe 키워드를 사용한 결과입니다. 코드는 그곳에 있었습니다. 에이전트는 그 지점들에서 연결을 끊기로 선택한 것입니다. 이 부채는 구조적이고 감사 가능하며(auditable), Bun 팀은 이를 해결해 나갈 것입니다. 하지만 이 부채가 존재하는 이유는 생성 속도가 피드백 루프 (feedback loop)보다 앞서 나갔기 때문입니다.
여러분의 바이브 코딩 (vibe coding) 스택도 더 작은 규모로 동일한 패턴을 실행합니다. Claude Code 튜토리얼이 프로덕션에 대해 놓치고 있는 것에는 다음과 같은 환경 수준의 결정 사항들이 포함됩니다: 어떤 컴파일러를 쓸 것인지, 어떤 엄격함 설정(strictness settings)을 사용할 것인지, 어떤 타입 시스템 (type system)을 사용할 것인지 (이는 첫 번째 프롬프트가 입력되기 전에 설정되어야 합니다).
Next.js SaaS의 경우: strict: true와 noUncheckedIndexedAccess가 활성화된 TypeScript를 사용하세요. 이는 에이전트가 애플리케이션 계층 (application layer)에서 가장 빈번하게 생성하는 오류 유형을 잡아냅니다.
백엔드 서비스 또는 CLI의 경우: 성능 제약 조건에 따라 Go 또는 TypeScript를 사용하세요. Go의 2초 컴파일 루프는 의미론적 보장 (semantic guarantees)이 다소 약하더라도 빠른 반복 (iteration)을 가능하게 합니다.
시스템 소프트웨어, 에지 런타임 (edge runtimes), 또는 메모리에 직접 접근하는 모든 것의 경우: Rust를 사용하세요. 성능 때문이 아니라, 컴파일러 (compiler) 때문입니다.
미사일 유도 소프트웨어의 경우: Ada를 사용하세요. (아무도 묻지 않았지만, 정답은 Ada입니다.)
새 프로젝트를 시작하거나 기존 코드베이스를 감사 (audit)할 때 사용할 수 있는 2가지 프롬프트입니다:
AI 에이전트가 대부분의 코드를 작성할 프로젝트를 시작하려고 합니다.
에이전트가 실수를 했을 때 가장 풍부한 컴파일 타임 피드백 (compile-time feedback)을 제공할 수 있는 언어를 원합니다. 제가 해당 언어에 얼마나 익숙한지는 무시하세요.
...
AI 에이전트가 대부분의 코드를 생성하는 [언어] 코드베이스를 가지고 있습니다.
런타임 (runtime)에 도달하기 전, 컴파일 타임에 더 많은 오류를 잡아내기 위해 어떤 컴파일러 플래그 (compiler flags), 타입 체커 (type checker) 설정, 그리고 린터 (linter) 규칙을 활성화해야 할까요?
...
저는 매일 Claude Code를 사용합니다. 그 밑단에서 돌아가는 런타임은 Bun입니다. 저는 지난주까지 이 런타임이 Claude가 6일 동안 작성한 100만 줄의 코드로 실행되며, 그 안에는 감사를 기다리는 13,044개의 unsafe 블록이 있다는 사실을 몰랐습니다.
그것은 저를 겁나게 하지 않습니다. 테스트는 통과하니까요. Jarred Sumner는 운영 환경 (prod)에 살아있는 수류탄을 남겨둘 타입의 사람이 아닙니다.
그 사실이 저로 하여금 제 자신의 파이프라인 (pipelines)을 살펴보게 만들었습니다. 에이전트가 안전망 없이 빠르게 코드를 생성하도록 여지를 남겨두었던 곳들 말입니다. strict: true 없이 실행되는 TypeScript, 제약 조건 (constraint) 대신 주석으로만 남아 있는 스키마 검증 (schema validation) (컴파일러가 안돈 코드(Andon Cord)를 당기지 않는 모든 곳에서, 버그는 다른 이름으로 모여듭니다).
당신의 코드베이스에서 그것들은 unsafe 블록의 형태로 나타나지 않습니다. 6주 뒤에 운영 환경 버그 (prod bugs)의 형태로 나타납니다.
Sources
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기