
AI가 작성한 코드를 유지보수 불가능하게 만들지 않기 위해, 효과적인 대책을 효과가 큰 순서대로 나열했다
요약
AI를 활용한 개발 시 발생하는 코드 파편화와 유지보수 문제를 해결하기 위한 실무적인 가이드를 제공합니다. 설계 결정 사항을 기록하고 AI 지시 파일에 반영하여 코드의 일관성을 유지하는 구체적인 방법을 제안합니다.
핵심 포인트
- 설계 결정 이유를 코드와 함께 기록하여 맥락 유지
- CLAUDE.md에 '결정한 것' 섹션을 추가해 설계 판단 기록
- 결정 로그(Decision Log)를 통해 과거의 선택과 포기한 안 관리
- AI의 제안을 무비판적으로 수용하지 않고 차이점(Diff) 검토
반년 전에 AI와 함께 만든 플러그인을 열어보고 가장 먼저 깨달은 것은, 날짜를 포맷팅(Formatting)하는 처리가 3곳에 있었다는 점이었습니다.
유틸리티 함수에 하나, 클래스의 메서드(Method)에 하나, 템플릿에 직접 작성된 것이 하나. 모두 조금씩 작성 방식이 달랐습니다. 보고된 표시 어긋남은 그중 하나인 오래된 코드 때문이었습니다. 수정하려고 해도 어떤 것을 고쳐야 끝나는지, 전부 고쳐야 하는지 판단하는 데에만 시간이 녹아내렸습니다. 8할을 AI에게 맡겨 빠르게 만든 코드였습니다. 사양서(Specification)는 쓰지 않았습니다.
이 기사는 그러한 상태를 반복하지 않기 위해 바꾼 것들을 효과가 있었던 순서대로 나열한 것입니다. 에이전트(Agent) 사용을 그만두자는 이야기가 아닙니다. 사용을 중단하지 않으면서 속도를 유지한 채, 반년 뒤의 내가 유지보수할 수 있는 상태로 만들기 위한 구체적인 처방만을 적겠습니다.
설정이나 파일명은 버전에 따라 달라집니다. 아래는 실제 기기에서 확인한 시점의 것입니다. 최종적으로는 자신의 환경에서 /permissions나 /config 각 도구의 설정 파일을 열어 동작을 확인해 주세요.
- Claude Code:
claude --version의 값 (공개 시 실제 기기의 번호로 수정) - Codex CLI:
codex --version의 값 - Node.js / PHP 8.3 / WordPress (대상은 플러그인과 테마)
- 영속 지시 파일: Claude Code는
CLAUDE.md, Codex CLI는AGENTS.md
이하는 8할을 AI에게 맡겼다가 한 번 실패한 인간이, 그 반성으로부터 재구축한 운용 방식입니다. 만능인 방법을 소개하는 것이 아니라, 같은 실수를 피하고 싶은 분들을 위한 메모로서 읽어주시면 적절한 거리감이 될 것 같습니다.
대책에 앞서 원인을 딱 3줄만 말씀드리겠습니다. 빠른 것 자체는 나쁘지 않습니다. 속도와 맞바꾸어 빠져나가는 것들이 나중에 영향을 미칩니다.
설계의 이유가 채팅창 안에만 남아 창을 닫으면 사라진다. 차이점(Diff)을 읽지 않고 수용하기 때문에 코드와 내가 친숙해질 시간이 제로가 된다. 구조를 세션(Session)마다 AI에게 맡기기 때문에 유사한 처리가 서로 다른 방식으로 흩어진다. 서두의 "날짜 처리가 3곳"은 세 번째의 전형적인 사례였습니다.
이후의 대책은 전부 이 세 가지 중 하나를 막기 위한 것입니다. 효과가 컸던 순서대로 나열합니다. 위에서 두 가지만이라도 실천하면 반년 뒤의 자신은 상당히 편해질 것입니다.
가장 효과적이었습니다. 채팅에 흘러가 사라졌던 "왜"를 코드와 같은 장소에 남기는 것. 하고 있는 것은 두 가지입니다.
하나는 CLAUDE.md의 사용법을 바꾼 것입니다. AI를 위한 지시서로서뿐만 아니라, 후반부에 「결정한 것」 섹션을 추가하여 설계 판단을 몇 줄씩 적습니다. 무엇을 했느냐가 아니라 왜 그렇게 했느냐를 적습니다. 그리고 포기한 안과 그 이유도 적습니다. 이 포기한 이유가 나중에 가장 효과적입니다. 반년 뒤의 자신은 괜찮아 보이는 다른 안을 떠올려 다시 만들다가 똑같은 구멍에 다시 빠지기 때문입니다.
## 지킬 것 (AI에게 읽히는 규약)
- 함수·훅(Hook)·옵션 키는 접두사 `tmfs_`를 붙인다
- 공개 API를 변경하는 제안은 구현 전에 반드시 멈추고 확인시킨다
...
지시와 기록이 같은 파일이면 제목으로 섞이기 때문에, 전반부를 「지킬 것」, 후반부를 「결정한 것」으로 나누고 있습니다. AI가 후반부를 읽어도 해가 없으며, 과거의 판단을 바탕으로 한 제안이 돌아오기도 하므로 공존의 이점도 있습니다.
또 하나는 기능을 하나 추가할 때마다 docs/decisions.md에 한 단락만큼 결정 로그(Decision Log)를 남기는 것입니다. 완성 후에 몰아서 사양서를 쓰는 것은 한 번 시도해 보았으나 지속되지 않아 그만두었습니다. 끝나고 나서 쓰는 사양서는 거짓이 섞이기 쉬운 데다, 심리적 부담이 커서 뒤로 미루게 됩니다. 한 단락이라면 기능을 마무리하는 기세 그대로 적을 수 있습니다.
### 2026-06-05 레이트 제한 (Rate Limit)
- 외부 API로의 전송을 1분에 20건으로 제한. 상대방의 상한이 30건/분이라 여유를 두었다.
- AI는 "제한은 불필요"라고 작성했지만, 과거에 큐(Queue)가 쌓였던 사고가 있으므로 도입했다.
...
AI에게 "결정 로그를 써줘"라고 부탁하면 그럴듯한 문장이 돌아오지만, 코드에서 역산한 그럴싸한 설명인 경우가 많습니다. 실제 이유와는 비슷해 보여도 차이가 있습니다. 초안은 밑바탕으로 받아들이고, 실제로 자신이 무엇을 생각해서 결정했는지 직접 수정해서 남겨주세요. 반년 뒤에 효과를 발휘하는 것은 진짜 이유뿐입니다.
차이점(Diff)을 읽지 않고 수용하는 습관을 고치기 위해 설정에서도 손을 댔습니다.
이전에는 settings.json에서 defaultMode를 acceptEdits로...
설정에서 손을 댔던 이야기입니다(settings.json 관련 이전 게시글). 확인 작업을 줄여 쾌적하게 만들어주는 설정이지만, 유지보수 측면에서는 "읽지 않고 넘어가는 것"을 부추깁니다. 그 반동으로 전부 다 읽으려고 시도했다가는 속도의 의미가 없어지고 반나절 만에 좌절하고 말았습니다. 극단적인 방식은 지속될 수 없습니다.
그래서 반드시 제가 읽어야 할 곳만 소수로 압축했습니다.
## 리뷰의 검문소 (반드시 인간이 읽음)
- 공개 API (Hook 이름, 함수 시그니처, REST Route)를 변경하는 차이(diff)
- DB 스키마, 테이블, 옵션 키를 변경하는 차이(diff)
...
선택한 것은 나중에 되돌리기 어려운 것들뿐입니다. 경계(Boundary), 데이터, 안전, 그리고 너무 큰 차이(diff). Hook 이름이 조용히 바뀌면 의존성이 깨지고, 이스케이프(Escape) 처리가 하나라도 빠지면 반년 뒤에 보안 지적을 받고 나서야 깨닫게 됩니다. 내부 리팩터링(Refactoring)이나 테스트 추가는 틀려도 데미지가 작거나 테스트가 잡아냅니다. 데미지의 크기로 선을 그으니 읽어야 할 양이 현실적이 되었고, 오히려 지속할 수 있었습니다.
서두에 언급한 "3곳에 같은 처리"는 구조를 세션마다 AI에게 통째로 맡긴 결과였습니다. AI는 내버려 두면 범위를 넓혀갑니다. 새로운 함수를 만들고, 새로운 파일을 나누고, 기존에 헬퍼(Helper)가 있는데도 비슷한 것을 하나 더 만듭니다.
틀(Frame)만 인간이 고정하고, 그 안에서 움직이게 하는 방식으로 바꿨습니다. AGENTS.md (Codex CLI용)와 CLAUDE.md 양쪽 모두에 동일한 규약을 작성합니다. 한쪽에만 작성하면 도구를 바꾸는 순간 스타일이 변하기 때문입니다.
## 명명 및 구조 규약
- 최상위 계층은 admin / public / includes 3개로 제한. 신설 금지.
- 새로운 클래스를 추가하기 전에, 기존에 유사한 클래스가 있는지 반드시 확인할 것.
...
효과적인 방법은 멋대로 신설하지 못하게 하는 것입니다. 분할은 제안 수준에 머물게 합니다. 멋대로 분할되면 목적한 코드가 어디로 갔는지 알 수 없게 됩니다. WordPress 플러그인이라면 접두사(Prefix) 규약이 이미 있으므로, 머릿속의 암묵적인 규칙을 문자로 옮겨 적는 것만으로도 첫걸음이 됩니다. 암묵적인 상태로 두면 AI에게도, 미래의 자신에게도 전달되지 않습니다.
AI에게 쓰게 하면 주석(Comment)이 늘어납니다. 게다가 대부분 "무엇을 하고 있는지"에 대한 주석인데, 이는 반년 뒤에는 도움이 되지 않습니다. 코드를 읽으면 알 수 있는 내용을 중복해서 적는 것이며, 수정할 때 잊어버리면 코드와 어긋나 거짓 정보가 됩니다.
리뷰 시 "무엇을" 하는지에 대한 주석은 줄이고, 대신 코드를 읽어도 알 수 없는 "왜(Why)"를 접점에 추가합니다.
// 서명 검증은 hash_equals를 사용한다. ==를 사용하면 타이밍 공격(Timing Attack)으로
// 한 글자씩 돌파될 여지가 있기 때문.
if ( ! hash_equals( $expected, $given ) ) {
...
왜 ==로는 안 되는가. 이것은 코드 어디에도 적혀 있지 않은 판단입니다. 반년 뒤의 내가 "==로도 충분하지 않나"라고 생각하며 되돌리려 할 때, 이 두 줄이 저지해 줍니다. AI에 대한 지시도 "주석을 달아줘"가 아니라 "자명하지 않은 부분에만 이유를 담은 주석을 달 것. 동작 설명은 불필요"로 바꾸면 노이즈가 줄어듭니다.
커밋 메시지(Commit Message)도 마찬가지입니다. AI에게 맡기면 "Fix bug"와 같이 diff를 보면 알 수 있는 한 줄로 끝나기 쉽습니다. 첫 줄에는 무엇을 했는지(AI에게 맡겨도 됨)를 적고, 본문에 "왜"를 한 문장만 직접 추가합니다.
fix: 환율 조회를 타임아웃 3초로 제한함
결제 화면이 환율 API 대기로 인해 멈춘다는 보고가 있었기 때문.
정확한 환율보다 화면 응답을 우선하기로 판단함.
git blame으로 이 줄에 도달했을 때, 왜 짧은 타임아웃인지 그 자리에서 알 수 있습니다. 코드와 주석, 결정 로그와 커밋. "왜"를 남길 수 있는 장소는 여러 곳이 있으며, 변경 사항과 가장 가까운 곳에 한 문장만 있으면 충분합니다.
사양서(Specification)를 지속하기 어렵다면, 테스트에 사양의 역할을 겸하게 합니다. 하는 방법은 테스트 이름을 "무엇을 검증할 것인가"가 아니라 "어떻게 동작해야 하는가"로 작성하는 것입니다.
// before
public function test_verify() { ... }
// after
...
PHPUnit은 메서드 이름에 일본어를 사용할 수 있습니다. 테스트 목록을 위에서부터 훑어보는 것만으로도 이 플러그인이 무엇을 약속하고 있는지 읽을 수 있습니다. 게다가 이 사양은 동작이 변하면 테스트가 실패하므로, 방치하여 어긋날 걱정이 없습니다.
AI에게 시킬 때도 "커버리지(Coverage)를 높여줘"가 아니라 "지켜야 할 동작을, 동작 기준의 이름을 가진 테스트로 작성해줘"라고 요청합니다. 내부 구현을 따라가는 테스트는 리팩터링을 할 때마다 깨져서 결국 주석 처리되어 무덤으로 갑니다. 외부에서 본 약속을 확인하는 테스트는 내부가 바뀌어도 살아남습니다. 반년 뒤에 제대로 작동하고 있었던 것은 후자뿐이었습니다.
WordPress는 실제 DB나 Hook(훅)이 얽혀 있어 단체 테스트 (Unit Test)를 하기 어려운 부분도 있습니다. 그럴 때는 무리하지 않고, WP-CLI로 실행하는 절차나 관리자 화면의 확인 절차를 docs/에 불렛 포인트로 남기는 식의 회피 전략도 사용합니다. 테스트할 수 없는 것은 절차로서 읽을 수 있는 형태로 만듭니다. 형식은 상관없습니다. 반년 뒤에 재현할 수 있는 것만을 목표로 합니다.
여기서부터는 가볍지만 효과가 있었던 것들입니다.
의존성 (Dependency)을 하나 추가할 때마다, 결정 로그 (Decision Log)에 이유를 한 줄 적습니다. AI는 최신 버전을 넣으려는 경향이 있지만, 최신이 반드시 안정적이라는 뜻은 아니며, 플러그인 사용자들은 오래된 PHP 버전을 사용할 수도 있습니다. "PHP 8.0에서도 작동시키고 싶어서 8.1 이상의 구문을 사용하는 라이브러리는 피했다"와 같은 외부 상황은 코드만 읽어서는 알 수 없습니다. 채택하지 않은 이유도 적어두면, 반년 뒤에 똑같은 검토를 반복하지 않아도 됩니다.
README의 맨 앞에는 미래의 자신을 위한 안내를 몇 줄만 적습니다.
## 이 프로젝트에 대하여 (반년 뒤의 자신에게)
- Stripe 결제 후 감사 메일을 보내는 WordPress 플러g인.
- 먼저 읽는다면 includes/class-tmfs-mailer.php의 send()부터.
...
어떤 코드인지, 어디서부터 읽기 시작해야 하는지, 판단의 근거는 어디에 있는지, 주의할 점은 무엇인지. 이 4줄이면 충분합니다. 숲에 들어가기 전의 지도입니다. AI에게 맡기면 포괄적인 설명을 만들어주지만, 포괄적인 설명은 체력이 소모되어 읽히지 않습니다. 짧은 안내가 재방문 시에는 더 효과적이었습니다.
비교가 아니라, 유지보수성을 높이는 도구로서의 병용입니다. Claude Code로 작성한 코드를 Codex CLI에 "설명해줘", "유지보수의 약점을 지적해줘"라고 읽힙니다 (반대 방향도 마찬가지입니다). 작성한 본인은 의도를 알고 있는 만큼 설명이 소홀해지기 쉽지만, 의도를 모르는 다른 에이전트 (Agent)는 반년 뒤의 자신과 비슷한 백지 상태의 시선으로 읽습니다. 설명이 막히는 부분은 미래의 자신도 막힐 부분입니다. 그곳이 바로 문서가 부족한 지점입니다.
실제로 "이 Hook의 실행 순서에 의존하고 있지만, 전제 조건이 코드에서 읽히지 않는다"라는 지적을 받고, 이곳에는 주석이 필요하다는 것을 깨달은 적이 여러 번 있습니다.
다른 에이전트의 설명이 반드시 사실인 것도 아닙니다. "캐시를 사용하고 있다"라고 설명되었지만 실제로는 사용하고 있지 않았던 적도 있었습니다. 설명이나 지적은 자신이 놓친 관점을 찾아내기 위한 초안 (Draft)으로 사용하고, 내용은 스스로 확인하십시오. 두 개를 사용하는 것은 답을 두 배로 얻기 위해서가 아니라, 두 번째 시각을 빌리기 위해서입니다.
긴 세션 (Session)을 닫기 전에, 에이전트 스스로에게 "이 세션에서 결정한 설계 판단과 이유만을 decisions.md에 불렛 포인트로 작성해줘. 구현 설명은 필요 없어"라고 부탁하여 판단을 기록하게 하는 마무리 단계도 거칩니다. 대화가 사라지기 전에, 사라져서는 안 될 것들만 육지로 끌어올리는 감각입니다. 이 역시 출력을 그대로 붙여넣지 않고, 자신의 기억과 대조하며 수정합니다.
매번 전부 다 하지는 않습니다. 무겁기 때문입니다. 규모가 작더라도 건너뛰지 않는 것은 두 가지입니다.
- 판단을 남긴다 (CLAUDE.md의 "결정한 것"과 decisions.md의 한 단락)
- 관문(Checkpoint)만은 읽는다 (경계, 데이터, 보안, 큰 차이점)
나머지 테스트 정리, 주석과 커밋 (Commit)의 "왜", 의존성이나 README, 다른 에이전트를 통한 설명 등은 프로젝트가 성장하여 혼자 감당할 수 없게 되었을 때 추가한다는 정도의 온도로 임하고 있습니다. 처음부터 완벽하게 하려고 하면 무엇 하나 지속할 수 없습니다.
솔직히, 버린 것들도 적어두겠습니다. 완성 후에 몰아서 쓰는 사양서 (Specification)는 거짓이 섞이기 쉽고 시작하기가 무거워, 몇 번이나 미루다 결국 쓰지 않았습니다. 차이점 (Diff)을 전부 읽는 것은 속도와 양립할 수 없어 반나절 만에 좌절했습니다. 제대로 된 ADR (Architecture Decision Record) 템플릿은 양식을 떠올리는 것이 번거로워 사흘 만에 그만두었고, 불렛 포인트 몇 줄로 무너뜨리니 지속할 수 있었습니다. 매 커밋마다 무거운 이유를 적는 것도 의욕이 너무 앞서서, 지금은 나중에 신경 쓰일 것 같은 변경 사항에만 적고 있습니다.
나열해 보면 공통점이 있습니다. 너무 무거웠거나, 너무 완벽을 추구했거나. 지속할 수 있었던 것은 모두 "잠시 손을 멈추는" 정도의 가벼운 것들이었습니다. 요령을 피우는 것처럼 보일 정도로 가볍게 하면 지속할 수 있다는 것이, 반년 동안 해보며 가장 깊이 깨달은 점입니다.
속도를 전부 버리는 것이 아니라, 이음새(Joint)에서만 손을 멈추는 형태로 정착했습니다. 판단할 때, 경계를 바꿀 때, 기능을 하나 종료할 때. 그 순간에만 속도를 늦추고 "왜"를 한 줄 남깁니다. 그 사이의 작업은 에이전트에게 맡겨 빠르게 진행합니다. 효과가 있는 곳만 느리게 하는, 완급 조절입니다.
덤으로 깨달은 것은, "왜"를 남기는 습관이 생기니 AI에 대한 지시 (Prompt) 자체도 좋아졌다는 것입니다. 자신이 무엇을 결정하려고 하는지를 말로 표현할 수 있게 되면, 전달하는 지시도 모호하지 않게 됩니다. 미래를 위한 기록이 지금의 속도도 조금씩 끌어올려 줍니다. 이는 시작하기 전에는 생각하지 못했던 부분입니다.
도입부의 세 곳에 흩어진 날짜 처리 로직은 아직 한 곳으로 모으지 못했습니다. 이를 수정할지, 아니면 조용히 역할을 다하게 둘지 결정하지 못한 채 남아 있습니다. 다만, 지금 만들고 있는 것은 반년 뒤의 내가 망설임 없이 나아갈 수 있도록 만들어졌다는 느낌이 듭니다. 읽을 수 없게 된 집 앞에 열쇠만 든 채 멍하니 서 있는 경험은, 한 번으로 충분했습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기