AI 코딩 에이전트 Claude에게 같은 코드베이스를 수백 번 연속 개선시키도록 시킨 후, 테스트 폭증과 유틸리티 남발, 품질 지표 집착이 만들어낸 기묘한 ‘최고 품질’ 코드베이스를 분석한 실험기
이런 실험들 중 하나를 본 적이 있는가? 사람들이 같은 이미지를 AI 에이전트에 여러 번 계속 다시 먹이는 실험 말이다.
혹은 Marques Brownlee의 유튜브 영상 중 동일한 영상을 1000번 다시 업로드하는 실험 말이다?
추수감사절 주말 동안 손이 좀 남는 시간이 있어서, Claude에게 음식의 설명 + 사진을 기반으로 대략적인 매크로 영양소를 추정해 주는 앱을 만들라고 시켰다. 이걸 제대로 동작하게 만들려면 꽤 흥미로운 셋업이 필요하지만, 그건 지루한 부분이다. Claude는 나를 위해 꽤 훌륭하고 쓸 만한 앱을 만들어줬는데, 거기서 한 발 더 나아가 나는 이 앱을 가지고 작은, 그리고 약간 사악한 실험을 하게 했다.
내 코드베이스 전체를 순회하면서 아래 명령을 실행하는 간단한 스크립트를 작성했다.
bash#!/usr/bin/env bash set -euo pipefail PROMPT="Ultrathink. You're a principal engineer. Do not ask me any questions. We need to improve the quality of this codebase. Implement improvements to codebase quality." MAX_ITERS="200" for i in $(seq 1 "$MAX_ITERS"); do claude --dangerously-skip-permissions -p "$PROMPT" git add -A if git diff --cached --quiet; then echo "No changes this round, skipping commit." else git commit --no-verify -m "yolo run #$i: $PROMPT" fi done
…그리고 난장판이 벌어졌다. 아무 제약 없이 200번 넘게 미친 듯이 실행했다. 중간에 특정한 것 하나에 과도하게 집착하는 것 같으면 프롬프트를 조금씩 수정하긴 했지만, 충분히 많은 반복을 거치자 커버하는 영역이 엄청나게 넓어졌다. 전체 코드 커버리지와 기능 코드보다 많은 테스트, Rust 스타일의 Result 타입, 그리고… 해시 함수의 엔트로피 추정(???)까지.
이 스크립트는 약 36시간 동안 돌아갔고, 그 결과를 이해하는 데도 시간이 좀 걸렸다. 이제 무엇을 했는지 살펴보자. 전체 리포는 여기에 있다. 봐야 할 브랜치는 highest-quality 다.
이 앱은 화면이 4–5개 정도 되는 규모다. 사진을 찍고, 설명을 더하고, AI 응답을 받는다. 딱 그 정도로 단순하다.


“품질 향상 전” 버전도 이미 꽤 컸다. TS 코드가 약 2만 줄이고, 그중 약 9.7k 줄이 여러 __tests__ 디렉터리에 있었다. 이건 약간은 의도된 결과였다. Claude Code와 함께 작업할 때, 좋은 자기 검증용 하니스(self-validation harness)가 있으면 결과물의 품질이 크게 향상된다.
bashcloc . --exclude-dir=node_modules,dist,build,.expo,.husky,.maestro,Pods 132 text files. 127 unique files. 11 files ignored. github.com/AlDanial/cloc v 2.04 T=0.11 s (1167.4 files/s, 487085.6 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- JSON 4 0 0 23733 TypeScript 99 3019 1541 20160 Markdown 11 1004 0 2700 JavaScript 9 26 51 269 Bourne Shell 2 34 41 213 YAML 2 35 2 162 ------------------------------------------------------------------------------- SUM: 127 4118 1635 47237 -------------------------------------------------------------------------------
하지만 그 후에는 — 8만4천 줄! “코드베이스 품질 개선” 덕분에 2만 줄에서 8만4천 줄로 뛰어올랐다.
bashcloc . --exclude-dir=node_modules,dist,build,.expo,.husky,.maestro,Pods 285 text files. 281 unique files. 10 files ignored. github.com/AlDanial/cloc v 2.04 T=0.60 s (468.1 files/s, 268654.5 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- TypeScript 247 17587 18749 84185 JSON 5 0 0 24863 Markdown 14 4151 0 10391 JavaScript 9 41 140 598 Bourne Shell 3 41 41 228 YAML 3 50 3 215 ------------------------------------------------------------------------------- SUM: 281 21870 18933 120480 -------------------------------------------------------------------------------
테스트만 놓고 봐도 1만 줄에서 6만 줄로 늘어났다!
bashcloc . \ --exclude-dir=node_modules,dist,build,.expo,.husky,.maestro,Pods \ --match-d='__tests__' 138 text files. 138 unique files. 1 file ignored. github.com/AlDanial/cloc v 2.04 T=0.23 s (612.9 files/s, 346313.3 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- TypeScript 138 13919 3685 60366 ------------------------------------------------------------------------------- SUM: 138 13919 3685 60366 -------------------------------------------------------------------------------
이제 훨씬 더 안전해진 느낌이다.
테스트 수는 약 700개에서 무려 5369개로 늘어났다. 원래 프로젝트에는 실제 시뮬레이터를 사용하는 e2e 테스트가 있었다. 이 테스트들은 코딩 에이전트가 피드백 루프를 제대로 닫았는지 확인하는 데 꽤 중요하다. 그런데 품질을 개선하는 과정에서 이 테스트들은 잊혀진 듯하다 ¯\(ツ)/¯.
참고로, 주석 줄 수는 약 1500줄에서 1만8.7k 줄로 늘어났다.
자, 그렇다면 실제로 _무엇_을 했을까? 매 실행 후 Claude Code가 출력한 요약 로그를 전부 가지고 있다. 여기에서 직접 볼 수 있다.
Claude Code는 서드파티 라이브러리 쓰는 걸 정말 좋아하지 않는 것 같았고, 수많은 랜덤 유틸리티들을 새로 만들어냈다.
의존성 목록 자체는 꽤 작다는 점은 어느 정도 존중할 만하다. 다만 그 대가로 유지보수하기 아주 힘든 2만 줄 이상의 유틸리티 코드가 생겼다. 공급망 공격을 아주 철저히 피하고 싶었던 모양이다.
이 중 일부는 정말 불필요하며, 기성 라이브러리로 쉽게 대체할 수 있다.
어떤 것들은 그냥 미쳤다 — 내 최애 몇 가지를 소개한다!
이 모듈은 Rust의 Result<T, E>와 유사한 Result 타입을 제공합니다.나는 Rust의 결과 처리 시스템을 좋아한다. 하지만 이미 에러 throw 방식으로 표준화된 생태계 전체에 이걸 그대로 끌어다 쓰는 건 별로 잘 맞지 않는다고 본다. 이전 직장에서 Python에 이 방식을 도입해 보려고 실험했는데, 사람들에게 크게 와닿지 않았고 사용하는 것도 꽤 부자연스러웠다. 나는 이 방식은 피하고 싶다.
이 부분은 좀 웃겼다. AI가 당연하다는 듯 Rust에서 패턴을 가져오기 시작했기 때문이다. lib/option.ts도 있다.
몇몇 반복에서는 코딩 에이전트가 보안 엔지니어 모자를 쓰기도 했다. 예를 들어, “문자 다양성이 낮은 명백히 가짜 키”를 감지하기 위한 hasMinimalEntropy 함수를 만들어냈다. 왜 그런지는 모르겠다.
적절한 스케일링을 보장한다며 서킷 브레이킹과 지터를 포함한 지수 백오프까지 구현했다. 그런데 우리가 호출하는 API는 OpenAI/Anthropic이 전부다. 어쨌든 고맙다.
좋았던 점도 있다 — 엄격한 타입 체크를 보장하려는 데 많은 시간을 썼고, 과도한 캐스팅(as any as T)을 피하려고 꽤 신경 썼다. 이 점은 인정한다.
프롬프트는 어떤 버전이든 항상 코드베이스 품질을 개선하는 데 초점이 맞춰져 있었다. 그런데 AI 에이전트가 이 품질이라는 메트릭을 어떻게 받아들이는지 보는 건 꽤 실망스러웠다. 핵심 원칙은 몇 가지 허영 지표(vanity metric)를 정의하고, “숫자가 클수록 더 좋다”는 식으로 밀어붙이는 것이었다.
메시지 로그를 보면, 에이전트는 종종 추가된 테스트 수나 코드 커버리지(으…)가 어떤 임의의 퍼센티지를 넘어섰다며 자랑한다. 그 결과 품질을 명분으로 한, 유지보수 불가능한 괴물 같은 코드베이스가 만들어졌다. 하지만 숫자는 오르고 있다.
결론적으로, 이 프로젝트는 유지보수해야 할 코드가 훨씬 많아졌고, 그중 대부분은 크게 쓸모가 없다. 테스트가 엄청나게 많이 추가되었지만, 가장 중요했던 테스트들(앱이 실제로 여전히 동작하는지를 검증하던 maestro e2e 테스트)은 잊혀졌다. 타입 체크를 꽤 높은 품질로 유지하려 했던 순간 같은, “좋았던” 순간들이 아예 없었던 건 아니다.
"이 이미지를 1000번 다시 그려라" / "이 영상을 1000번 다시 업로드하라" 실험을 제대로 재현하려면, 루프를 두 단계로 구성했어야 할 것 같다.
물론 이 실험은 장난삼아 진행한 것이다. 내가 생각하는 ‘진짜로 중요한’ 방식으로 코드베이스 품질이 향상될 거라고 기대하지 않았다. 사실상 Claude Code를 실패하도록 프롬프트한 셈이고, 덕분에 꽤 웃긴 결과들이 나왔다.
그래도 나는 여전히 일상적인 개발에 코딩 에이전트를 사용한다. 적어도 AI가 작성한 코드를 리뷰하는 데 쓴 시간이 헛된 시간은 아니라고 느껴진다.
…아, 그리고 앱은 여전히 잘 동작하며, 새로운 기능은 하나도 없고, 새로 생긴 버그는 몇 개뿐이다.