
AI가 생성한 코드베이스의 비대화 제거하기
요약
AI 에이전트를 사용하여 생성된 코드베이스의 불필요한 코드(AI bloat)를 제거하는 실험과 그 결과를 다룹니다. 기능을 유지하면서도 코드 라인을 약 32% 줄이는 데 성공했으며, 이를 자동화하기 위한 'SLOC 목표 설정' 아이디어를 제안합니다.
핵심 포인트
- AI 에이전트 생성 코드는 장황하고 인지 부채를 유발할 수 있음
- 실험을 통해 기능 저하 없이 코드의 약 32%를 제거함
- SLOC(Source Lines of Code)를 목표로 설정하는 에이전트 기술 제안
AI 에이전트(AI Agents)의 사용은 특유의 냄새를 풍깁니다... 명확성과 간결함이 부족하고 장황하며 따라가기 힘든 README.md를 보는 것만으로도, 해당 GitHub 리포지토리(GH Repo) 소유자가 Claude에 취해 있었다는 것을 알 수 있습니다. AI가 생성한 코드베이스에서 (기능을 저하시키지 않으면서) 코드 라인의 40%를 잘라내는 주말 실험을 진행한 것은 AI 비대화(AI bloat)가 어떤 모습인지에 대한 눈을 뜨게 해주는 경험이었습니다. 이 학습 내용들은 에이전트 기술(agent skill)로 정제되었습니다.
지난 가을, 저는 미디어 플레이어인 Flutter 앱을 완전히 AI로 구축하기 시작했습니다. 단순히 느낌대로 코딩(vibe-coded)했다고 말하고 싶지는 않습니다. 저는 에이전트들이 문서를 유지하도록 압박했고, 자동화된 테스트 커버리지(automated tests coverage)를 밀어붙였으며, 피드백 루프(feedback loops)에 투자했습니다(예: Flutter 앱 구동을 위한 인체공학적인 CLI 생성). 이 결과물은 외부에서 실행하고 건드릴 수 있는 상태였습니다. 에이전트 주변에는 구조가 존재했습니다.
하지만 저 또한 코드를 그리 많이 읽지는 않았습니다. 너무 게을렀기 때문입니다. 더 정확히 말하면, 코드를 읽는 것이 마치 포털을 여는 것처럼 느껴졌습니다. 일단 들여다보기 시작하면, 단순히
코드베이스가 비대해지고 있다는 느낌을 받았습니다. 더 자세히 들여다보거나 심층 분석(deep dive)을 할 정신적 여유(혹은 흥미와 동기)가 없었습니다. 인지 부채 (cognitive debt)가 계속 쌓여갔습니다.
나의 비대화 제거 실험 (My Debloat Experiment)
| 측정 항목 | 전 (Before) | 후 (After) |
|---|---|---|
| 앱 코드 (Dart + Native) | 19,772 | 13,509 |
| ... |
모든 기능을 보존하고, 분석기(analyzer)를 깨끗하게 유지하며, Android 에뮬레이터와 Linux 데스크톱 빌드 모두에서 런타임 체크(runtime checks)를 수행한 결과, 앱 전체 코드의 31.7%를 줄였습니다. 이 과정에서 잠재적인 버그 두 개도 수정되었습니다.
/goal-sloc
OpenAI와 Anthropic 팀은 최근 Codex/Claude에 /goal 모드를 출시했습니다. 여기서 한 가지 아이디어가 떠올랐습니다. "SLOC를 목표로 설정하자" — 이것이 내 코드베이스의 불필요한 요소(BS)를 제거하기 위해 직접 손을 더럽히지 않고도 게으르게 할 수 있는 방법이 될 수 있을까?
SLOC(Source Lines of Code)는 측정하기 쉬운 조잡한 대리 지표(proxy)입니다... 그리고 위험한 지표이기도 합니다. 하지만 조잡한 대리 지표라도, 모델이 난장판 위에 설명 레이어를 하나 더 추가하는 대신 실제적인 단순화를 찾도록 강제할 수 있다면 여전히 유용할 수 있습니다.
이 실험은 /goal-sloc으로 발전했습니다. 이는 에이전트가 지표를 악용(game the metric)하지 못하게 하면서, 코드 라인 수를 강제 함수(forcing function)로 사용하는 작은 에이전트 기술(agent skill)입니다.
효과가 있었던 것들
- 데드 코드(dead code) 삭제
- 완전히 연결되어 있지만 아무런 동작도 하지 않던 no-op 플레이스홀더 서브시스템 제거
- 디버그 하네스(debug harness)를 배포 코드에서 분리
- 중복된 상태 레이어(state layer) 제거
- 테스트가 훌륭한 동작 명세(behavioral spec) 역할을 하는 경우, 테스트를 바탕으로 클린룸 재작성(clean-room rewrites) 수행
- 커스텀 로깅 코드를 성숙한 라이브러리로 교체
일부 작업은 가치가 있었으나 수치상으로는 큰 변화를 주지 못했습니다. 심층 모듈 재배치(Deep module reshuffles), 더 나은 경계 설정(better boundaries), 그리고 훅/컨트롤러 리팩토링(hook/controller refactors)은 코드 라인 수(SLOC)를 거의 변화시키지 않으면서도 설계를 개선할 수 있습니다. 이는 심층 모듈(deep modules)에 관한 Pocock의 관점과 정확히 일치합니다. 즉, AI는 얕고 누수되는 모듈(shallow, leaky modules)을 헤집고 다니는 대신, 단순한 인터페이스와 테스트 가능한 경계를 통해 작업할 수 있을 때 더 나은 성능을 발휘합니다. 이것이 유용한 발견 중 하나였습니다. 만약 목표가 코드 품질이라면, SLOC가 유일한 보상(reward)이 되어서는 안 됩니다. 최고의 아키텍처 작업 중 일부는 라인 카운터(line counter) 상으로는 인상적으로 보이지 않습니다.
또한 물리적인 하한선(hard floor)도 존재했습니다. Flutter 프로젝트는 생성된 코드와 플랫폼 스캐폴딩(platform scaffolding)을 포함합니다. 이것이 커스텀 네이티브 코드라면 일부 줄일 수 있습니다. 하지만 상당 부분은 그저 하한선일 뿐입니다. Gradle, CMake, Xcode 파일, 매니페스트(manifests), 라인으로 집계되는 바이너리 에셋(binary assets), 그리고 제품 결정에 따라 지원하거나 삭제해야 하는 플랫폼 디렉토리 등이 이에 해당합니다.
전체 내용은 여기에서 확인할 수 있습니다.
설정 (The Setup)
이 앱은 지난 가을에 시작된 Flutter 코드베이스로, 100% AI의 도움을 받아 구축되었습니다. 인간의 기여는 "모든 서브시스템을 이해한다"라기보다는 "하네스(harness)를 설정하고, 사양(specs)을 작성하며, 테스트를 요청하고, 방향을 계속 유지한다"에 더 가까웠습니다. 이 차이는 매우 중요합니다.
사람들이 AI 코딩에 대해 말하는 위안이 되는 이야기가 있습니다. 테스트, 사양/문서(specs/docs), 그리고 피드백 루프(feedback loops)가 있다면 제대로 하고 있는 것이라는 이야기입니다. 바이브 코딩(Vibe Coding)이 아닌 에이전틱 엔지니어링(Agentic Engineering) 🕶️... 저는 이것이 대체로 사실이라고 여전히 믿습니다. 하지만 이것이 코드가 건강하게 유지된다는 것을 의미하지는 않습니다. 이는 코드가 건강 상태가 조용히 악화되는 동안에도 계속 움직일 수 있음을 의미할 뿐입니다.
이 퇴보는 하나의 극적인 실패로 나타나지 않았습니다:
- 주변에 불필요한 스캐폴딩 (scaffolding)을 둘러싼 채 추가되는 기능들;
- 보고된 증상은 해결했지만 주변의 기이한 현상들은 그대로 남겨둔 버그 수정;
- 마치 주석의 양이 명확성과 동일한 것처럼 쌓여가는 장황한 주석들;
- 모델, 영속성 (persistence), UI, 플랫폼 채널 (platform channels)에 여전히 연결되어 있는 no-op 또는 플레이스홀더 (placeholder) 서브시스템들;
- 배포용 소스 코드에 남아 있는 디버그 및 자동화 하네스 (harness) 코드;
- 모델이 "아키텍처"를 의례적인 절차 (ceremony)로 학습했기 때문에 다른 상태 계층 (state layers)을 그대로 복제하는 상태 계층들.
이것이 AI가 개발한 코드의 특수한 위험성입니다. 가까이서 보면 종종 어리석어 보이지 않습니다. 각각의 추가 사항은 그 순간에는 방어 가능해 보입니다. 비대화 (bloat)는 축적에서 발생합니다. 모든 에이전트 턴 (agent turn)은 약간의 국소적인 타협, 약간의 설명적 잔여물, 약간의 방어적 추상화 (defensive abstraction)를 남깁니다. 충분한 턴이 지나면 모든 개별 단계가 합리적으로 보였을지라도 시스템은 더 무거워지며, 실패 모드 (failure modes)가 복합적으로 작용합니다.
Matt Pocock의 강연, ["Software Fundamentals Matter More Than Ever">(https://youtu.be/v4F1gFy-hqg?si=YH5fcyjMMfKjzobi)은 정확한 고통의 지점을 짚어냈습니다. 저는 코드에 깊이 파고들고 싶지 않았고, 그럴 용기도 없었습니다... John Ousterhout는 복잡성 (complexity)을 시스템의 구조에 관한 것이며, 이해하고 수정하기 어렵게 만드는 모든 것으로 정의합니다. "The Pragmatic Programmer"는 소프트웨어 엔트로피 (software entropy)에 대해 이야기합니다. 즉, 전체 설계에 신경 쓰지 않고 국소적으로 이루어진 변화가 변화를 거듭하며 쌓이는 것을 말합니다. Pocock의 표현은 더 날카로웠습니다: 코드는 저렴하지 않습니다. 나쁜 코드는 AI 시대에 더 비용이 많이 듭니다. 왜냐하면 변경하기 어려운 코드베이스는 당신 자신과 AI 에이전트 모두가 양질의 변경을 수행하는 것을 방해하기 때문입니다.
저는 그 프레임워크가 마음에 들었습니다. 또한 제가 기계에 절반쯤 위임해 버린 코드베이스를 붙잡고 앉아 영웅적인 아키텍처 리뷰를 수행할 생각은 없다는 것도 알고 있었습니다. 저는 제가 위임할 수 있는 제약 조건 (constraint)을 원했습니다.
왜 SLOC인가
그 수치는 측정하기 쉽습니다. 에이전트(agent)에게 목표를 부여합니다. 이는 "코드베이스를 단순화해 주세요"라는 요청을 취향의 논쟁에서 점수판이 있는 게임으로 바꿔줍니다. Claude Code에서 저는 /goal 모드를 외부 루프(outer loop)로 사용하려고 시도했습니다. 목표를 설정하고, 에이전트가 작업하게 하고, 측정하고, 계속하는 방식입니다.
저의 초기 희망은 일종의 자율적인 Ralph 루프(autonomous Ralph loop)였습니다. 에이전트가 스스로를 점검하며 계속 작업하다가, 결국 훨씬 더 작으면서도 여전히 작동하는 앱을 가지고 돌아오는 것이었습니다. 나중에 돌아와서 결과를 검사하는 과거의 Claude 컴파일러/자율성(autonomy) 실험에 더 가까운 형태를 원했습니다.
하지만 그런 일은 일어나지 않았습니다. Claude Opus 4.8은 저에게 너무 자주 확인을 요청했습니다. 처음에는 목표 루프가 제가 원하는 대로 작동하지 않는 것처럼 느껴졌습니다. 돌이켜보면, 빈번한 중단이 오히려 실행 과정을 살렸을지도 모른다고 생각합니다. 상호작용을 되돌아보니, 완전 자율 운영(fully autonomous operation)은 잘 풀리지 않았을 것 같습니다. 에이전트에게는 교정이 필요했습니다. 특히 무엇이 진정한 진전(progress)으로 간주되는지에 대해서 말이죠...
SLOC를 줄이는 저렴한 방법은 명백합니다. 주석을 다듬고, 줄을 빽빽하게 채우고, 포맷을 변경하고, 카운트되는 경로에서 코드를 옮기는 것입니다. 카운터 수치는 줄어들지만 시스템을 파악하기는 더 어렵게 만드는 헬퍼(helper) 함수들을 추출하는 것도 방법입니다. 프롬프트(prompt)가 충분히 허술하다면 문서와 테스트를 삭제할 수도 있습니다. 에이전트가 이런 행동을 하기 위해 악의적일 필요는 없습니다. 그저 눈에 보이는 보상(reward)을 최적화하기만 하면 됩니다.
그리고 저는 보상 해킹(reward hacking)을 목격했습니다.
초기의 몇몇 "승리" 중 일부는 주석 정리였습니다. 그것은 속임수처럼 보일 수 있지만, 순전히 가짜였다고 생각하지는 않습니다. 과도한 AI 주석은 실제적인 문제입니다. 그것들은 컨텍스트(context)를 팽창시킵니다. 미래의 에이전트가 이해하는 능력을 저하시킵니다. 또한 실제로 중요한 몇 안 되는 주석들을 숨기면서 뻔한 코드들을 설명하느라 시간을 낭비합니다. 저의 현재 규칙은 간단합니다. 모든 주석 줄은 그 자리에 있을 가치를 스스로 증명해야 합니다.
그럼에도 불구하고, 주석 삭제가 전략이 될 수는 없습니다. 만약 코드베이스가 주변의 설명글(prose)이 사라졌다는 이유만으로 작아진 것이라면, 그 시스템은 의미 있게 단순해진 것이 아닙니다. 그저 조용해졌을 뿐입니다.
그 차이가 기술의 핵심이 되었습니다.
이 기술은 주로 부정행위 방지 장치입니다
/goal-sloc은 "더 작게 만들어줘"라고 말하는 마법 같은 프롬프트가 아닙니다. 핵심은 에이전트(agent)가 스스로에게 거짓말을 하고 있지 않다는 것을 증명하게 만드는 것입니다.
이 기술은 사전 점검(preflight)에서 시작됩니다:
- 측정 도구를 읽고 숫자가 실제로 무엇을 세는지 정의합니다.
- 기준점(baseline)과 영역별 세부 내역을 기록합니다.
- 더 이상 줄일 수 없는 최소치(irreducible floor)를 계산합니다.
- 코드를 삭제하기 전에 테스트, 정적 분석(static analysis), 그리고 런타임 앱 구동 체크가 작동하는지 확인합니다.
- grep을 신탁(oracle)처럼 사용하는 대신, 데드 코드(dead-code) 및 의존성 분석을 위해 시맨틱 도구(semantic tools)를 사용합니다.
- 줄 바꿈 변경 사항을 비교할 수 있도록 포맷팅(formatting)을 고정합니다.
- 검증된 작은 마일스톤(milestones) 단위로 작업합니다.
그다음 에이전트에게 정직한 축소 순서를 부여합니다: 첫째로 데드 코드, 둘째로 플레이스홀더(placeholder) 서브시스템, 셋째로 잘못 배치된 개발/테스트 스캐폴딩(scaffolding), 그 다음으로 실제 중복 코드, 주석 정리는 오직 위생(hygiene) 차원에서만 수행하며, 그다음으로 위험도가 높은 클린룸(clean-room) 재작성, 아키텍처 단순화, 마지막으로 라이브러리가 진정으로 더 나은 엔지니어링인 경우 라이브러리로 위임하는 순서입니다.
가장 중요한 규칙은 자기 감사(self-audit)입니다: 몇 번의 마일스톤마다 축소된 내용이 구조적인 것인지 아니면 저렴한 방식(cheap)인지 분류합니다. 만약 저렴한 방식이 지배적이라면, 에이전트는 작업을 중단하고 지표를 속이고 있음을 인정하거나, 구조적인 개선 여지가 고갈되었음을 보고해야 합니다.
이것은 거의 너무 당연하게 들릴 수도 있습니다. 하지만 실행 과정에서는 전혀 당연하지 않았습니다. 이 규칙이 없었다면, 모델은 점수판을 움직이게 만드는 쉬운 방식(easy levers)을 향해 계속 표류했을 것입니다.
이 기술은 에이전트(Agent)에게 언제 멈춰야 하는지도 알려줍니다. 이는 매우 중요합니다. 에이전트는 다음 단계의 증가가 더 이상 위험을 감수할 가치가 없다는 사실을 인정하는 데 서툽니다. 프롬프트(Prompt)가 계속해서 활동에 보상을 준다면, 에이전트는 불필요한 변화(Churn)를 만들어낼 것입니다. 중단 조건이 없는 SLOC(Source Lines of Code, 소스 코드 라인 수) 목표는 '리팩토링-후회-되돌리기(refactor-regret-revert)' 루프를 초래합니다. 즉, 시스템을 변경하고, 무언가를 망가뜨리고, 이를 패치(Patch)하고, 코드를 다시 확장한 뒤, 이 모든 난장판을 '학습'이라고 부르는 것입니다.
올바른 결론은 때때로 다음과 같습니다: "우리는 바닥에 가까워졌습니다. 남은 작업은 SLOC 변화가 없는 아키텍처(Architecture) 설계나 제품 범위(Product scope)에 관한 것입니다. 인간에게 물어보세요."
Opus 4.8이 잘했던 점과 이상했던 점
저는 긴 연휴 세션 동안 Claude Opus 4.8을 사용했습니다. 경험은 강력했지만, "하루 동안 내버려 두어도 될 정도"의 의미는 아니었습니다.
이 모델은 매우 정직했으며, 저는 그 점을 높게 평가합니다. 의구심을 표출하기도 했고, 수정을 받아들였습니다. 이전의 대부분의 모델이 그랬던 것처럼, 진전을 보여주려고 애쓰거나 "억지스러운 바람(ugly-wishing)"을 부리는 느낌이 들지 않았습니다. 이러한 정직함은 중요했습니다. 왜냐하면 SLOC 감소에는 명백한 보상 해킹(Reward-hacking) 경로가 존재하며, 에이전트는 중단될 수 있어야 했기 때문입니다.
동시에, 종종 망설이는 것처럼 느껴지기도 했습니다. 때로는 너무 수줍어하는 것 같았습니다. Claude Opus 4.8의 시스템 카드(System card)에는 제 경험과 예상보다 더 일치하는 문구가 있습니다:
"난이도에 따라 편차가 가장 크게 나타나며, 이는 Claude Opus 4.8이 이전 모델들과 가장 차별화되는 지점이기도 합니다. Claude Opus 4.8은 Opus 4.7과 유사하게 전반적으로 어려운 작업을 선호하지 않지만, 그 정도가 더 심합니다."
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기