저장소에서 가장 위험한 코드는 증명할 수 없는 동작이다
요약
코드의 품질보다 중요한 것은 저장소가 코드의 실제 동작 의도를 증명할 수 있는지 여부입니다. 테스트 통과 여부와 별개로, 비즈니스 로직의 이유(Reason)가 코드와 함께 보존되지 않으면 AI 도구 사용 시 심각한 버그를 초래할 수 있습니다.
핵심 포인트
- 코드 이력은 동작의 이유를 완벽히 설명하지 못함
- 테스트 통과는 의도된 동작의 보호를 보장하지 않음
- AI 코딩 도구는 맥락 없는 코드 단순화로 버그를 유발할 수 있음
- 저장소 내에 동작의 근거(증거)를 남기는 것이 중요함
우리는 코드 품질에 대해 논쟁하는 데 많은 시간을 소비합니다. 코드가 깨끗한가? 읽기 쉬운가? 테스트되었는가? 너무 영리한가? AI가 생성했는가? 모두 타당한 질문들이지만, 저는 우리가 더 중요한 질문 하나를 놓치고 있다고 생각합니다:
저장소(Repository)가 이 동작이 실제로 무엇을 해야 하는지 증명할 수 있는가?
개발자가 아닙니다. 제품 관리자(Product Manager)도 아닙니다. 이 이상한 엣지 케이스(Edge case)가 의도된 것임을 어떻게든 알고 있는, 4년 동안 팀에 몸담아 온 사람도 아닙니다. 바로 저장소 그 자체입니다.
시스템이 충분히 오래되면, "작동 방식"과 "저장소가 증명할 수 있는 것"은 서서히 서로 다른 것이 되어가기 때문입니다. 그리고 그 간극이 바로 버그가 숨어드는 곳입니다.
코드는 동작과 같지 않다
저장소는 코드, 커밋(Commit), 누가 파일을 언제 변경했는지를 보여줄 수 있습니다. 하지만 코드 이력은 동작 이력이 아닙니다.
Git은 다음과 같이 말해줄 수 있습니다:
이 함수는 화요일에 변경되었습니다.
하지만 대개 다음과 같이 말해주지는 못합니다:
이 함수는 결제 제공업체가 실패한 이벤트를 24시간 동안 재시도하기 때문에 중복 웹훅(Webhook) 전달을 처리합니다. 멱등성(Idempotency) 테스트를 업데이트하지 않고 이 로직을 변경하는 것은 위험합니다.
두 번째 문장이 팀에 실제로 필요한 것이며, 많은 코드베이스에서 이는 오직 누군가의 머릿속에만 존재합니다. 때로는 2022년의 Slack 스레드에, 혹은 아무도 찾을 수 없는 Jira 티켓에, 또는 당시에는 말이 되었던 PR(Pull Request) 코멘트에 남아있기도 합니다. 때로는 어디에도 존재하지 않기도 합니다.
코드는 여전히 실행됩니다. 테스트는 여전히 통과합니다. 팀은 여전히 배포합니다. 하지만 저장소는 그 _이유(Reason)_를 잃어버렸습니다.
AI가 이를 악화시키지만, 문제를 만든 것은 아니다
여기서 AI 코딩 도구를 탓하기는 쉽습니다. AI는 깨끗해 보이는 코드, 통과하는 테스트, 그리고 합리적으로 들리는 설명을 생성합니다. 하지만 모든 팀은 AI가 등장하기 훨씬 전부터 이런 코드를 가지고 있었습니다:
if (user.country === "US" && order.total > 0) {
enableManualReview = true;
}
왜일까요? 사기 방지 규칙(Fraud rule)? 세금 규칙(Tax rule)? 오래된 비즈니스 요구사항? 아니면 영구적으로 변해버린 임시 방편(Workaround)? 아무도 모릅니다.
이제 여기에 AI를 대입해 봅시다. 코딩 에이전트(Coding agent)는 해당 조건을 보고 이를 "단순화(simplify)"하려고 시도합니다. 리뷰어는 깔끔한 디프(diff)를 확인합니다. 원래의 동작(original behavior)에 대해 아무도 테스트를 작성한 적이 없기에 테스트는 통과하고, PR(Pull Request)은 머지(merge)됩니다. 2주 후, 누군가 왜 주문이 수동 검토(manual review) 단계로 들어오지 않는지 묻게 됩니다.
동작은 실제로 존재했습니다. 하지만 증거가 없었습니다.
통과하는 테스트 스위트가 이해의 증거는 아니다
저는 테스트를 사랑하지만, 테스트는 누군가가 단언(assert)하기로 기억해낸 것만을 증명할 수 있습니다. 테스트는 함수가 200을 반환한다는 것을 증명할 수 있습니다. 하지만 그 함수가 재시도(retry)를 해도 되는지, 재시도가 멱등성(idempotent)을 유지해야 하는지, 혹은 모바일 클라이언트가 의존하고 있기 때문에 타임아웃(timeout)이 존재하는지까지는 증명하지 못할 수도 있습니다.
테스트 스위트가 통과한다는 것은 종종 "우리가 작성한 체크 항목들이 여전히 통과한다"는 의미이지, "사용자가 의존하는 동작이 여전히 보호되고 있다"는 의미가 아닙니다.
이것이 바로 누락된 증거를 단순한 불편함이 아닌 하나의 신호(signal)로 취급해야 하는 이유입니다. 만약 PR이 결제 로직을 변경했는데 결제 관련 테스트가 하나도 변경되지 않았다면, 그것이 자동으로 해당 PR이 잘못되었다는 뜻은 아닙니다. 하지만 그것은 하나의 신호입니다. 만약 어떤 기능에 다섯 가지의 구현 방식, 세 가지의 명명 규칙(naming styles), 문서 부재, 그리고 해피 패스(happy paths)만을 다루는 테스트가 있다면, 그 기능에 가해지는 다음 변경은 보기보다 훨씬 더 위험합니다. 저장소는 당신에게 조용히 말하고 있는 것입니다: "작동은 할지 모르지만, 왜 그런지 완전히 설명할 수는 없습니다."
코드를 생성하는 것이 점점 더 쉬워지고 있기 때문에 이 차이는 이제 더욱 중요해졌습니다. 코드가 저렴해질 때, 이해(understanding)는 비싸집니다.
새로운 코드 리뷰 질문
전통적인 리뷰는 코드가 정확한지, 읽기 쉬운지, 안전한지, 그리고 테스트되었는지를 묻습니다. 이러한 질문들은 여전히 중요합니다. 하지만 저는 모든 위험한 PR에 한 가지 질문이 더 필요하다고 생각합니다.
이 변경 사항이 보존한다고 주장하는 동작은 무엇이며, 그 증거는 어디에 있는가?
결제 웹훅(billing webhooks)을 건드리는 PR을 예로 들어봅시다. 리뷰는 단순히 코드가 좋아 보이는지만 물어서는 안 됩니다. 다음과 같이 물어야 합니다: 이 변경이 중복 전달(duplicate delivery)에 영향을 주는가? 재시도 동작(retry behavior)에 영향을 주는가? 이벤트 순서(event ordering)에 영향을 주는가? 환불(refunds)에 영향을 주는가? 그리고 각 답변에 대해 — 증거가 있는가, 아니면 그저 확신(confidence)뿐인가?
이것은 관료주의를 추가하자는 것이 아닙니다. 저장소(repo) 스스로를 설명할 수 없기 때문에 안전해 보이는 변경 사항들의 병합(merge)을 거부하자는 것입니다.
"문서화 (Documentation)"만으로는 충분하지 않다
보통은 다음과 같이 답합니다: "더 나은 문서를 작성하라." 저도 어느 정도는 동의합니다. 문서가 시스템과 밀접하게 유지될 때는 유용하지만, 시스템으로부터 멀어질 때는 위험합니다.
"웹후크(webhooks)는 멱등성(idempotent)을 가진다"라고 적힌 README는 좋습니다. 중복 전달이 안전하다는 것을 증명하는 테스트는 더 좋습니다. 재시도(retries)가 왜 이런 방식으로 작동하는지 설명하는 결정 기록(decision record)은 훨씬 더 좋습니다. 그리고 그 주장(claim)을 실제 파일, 테스트, 라우트(routes), 그리고 최근의 차이점(diffs)과 연결해 주는 도구가 있다면 그것이 가장 완벽한 형태일 것입니다.
소프트웨어 유지보수의 미래는 더 많은 문서화가 아닙니다. 그것은 "증거에 기반한 (evidence-backed)" 문서화입니다. 여기에는 주장이 있고, 여기에는 증거가 있으며, 여기에는 여전히 불확실한 부분이 있다는 식입니다. 저장소는 "모른다"라고 말할 수 있어야 합니다. 그것이 아는 척하는 것보다 훨씬 더 건강합니다.
어쩌면 저장소에는 "이해 점수 (understanding score)"가 필요할지도 모른다
우리는 테스트 커버리지(test coverage), 빌드 시간(build time), 번들 크기(bundle size), 변경 빈도(churn), 보안 취약점(security findings), 의존성 리스크(dependency risk)를 측정합니다. 하지만 저장소가 자신의 중요한 동작을 스스로 설명할 수 있는지에 대해서는 거의 측정하지 않습니다.
저장소를 열었을 때 다음과 같은 것을 본다고 상상해 보십시오:
| 영역 | 증거 |
|---|---|
| 인증 (Authentication) | 강력함 |
| ... |
이것은 점수가 완벽하기 때문도 아니고, 인간의 검토(human review)를 대체하기 때문도 아닙니다. 다만 확신(confidence)이 가짜인 시스템의 부분들에 주의를 집중시키기 위함입니다. 그리고 가짜 확신은 비용이 많이 듭니다.
진짜 병목 현상 (The real bottleneck)
AI 도구들은 계속해서 발전할 것입니다. 더 많은 코드를 작성하고, 더 많은 PR을 올리고, 더 많은 버그를 수정하며, 더 많은 테스트를 생성할 것입니다. 그것은 문제가 아닙니다. 문제는 더 빠른 코드 생성(code generation)이 자동으로 더 나은 시스템 이해(system understanding)를 만들어내지는 않는다는 점입니다. 팀은 자신들이 보존하려는 동작을 더 적게 이해하면서도 더 많은 코드를 배포할 수 있습니다.
병목 현상은 구문(syntax), 상용구(boilerplate), 또는 리뷰 속도조차 아닙니다. 병목 현상은 증거(evidence)입니다. 우리는 이 동작이 무엇인지, 왜 존재하는지, 그리고 이 변경 사항이 이를 망가뜨리지 않는다는 것을 증명할 수 있습니까? 저장소가 이러한 질문에 답할 수 있을 때까지, 우리는 기억과 구전(folklore), 그리고 확신(confidence)에 의존하고 있습니다.
그리고 확신은 증거가 아닙니다.
여러분의 저장소에서 가장 위험한 코드는 항상 지저분한 코드, 오래된 코드, 또는 AI가 생성한 코드는 아닙니다. 때로는 모두가 의존하고 있지만 아무도 증명할 수 없는 동작이 가장 위험합니다. 일단 이를 찾기 시작하면, 어디에서나 그것을 보게 될 것입니다.
저는 DevTime이라는 오픈 소스 CLI 도구를 통해 이 아이디어를 탐구하기 시작했습니다. 이 도구는 저장소를 스캔하여 실제로 사물이 어떻게 작동하는지에 대해 증거에 기반한 설명을 생성하며, 모든 주장을 실제 파일 및 테스트와 연결합니다. 만약 이 글의 문제가 익숙하게 느껴진다면, 이에 대한 여러분의 피드백을 듣고 싶습니다.
여러분의 코드베이스에서 "모두가 알고 있는" 동작 중 실제로 아무도 증명할 수 없는 부분은 무엇인가요? 댓글로 알려주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기