AI 보조 개발이라는 관점에서 DRY 원칙을 다시 살펴보고, 중복이 나쁜 이유가 코드가 아니라 지식의 중복과 유한한 컨텍스트에 있음을 설명한다.
URL: https://kirilltolmachev.dev/is-dry-dying
이 글은 AI 보조 개발이라는 렌즈를 통해 프로그래밍 원칙을 다시 방문하는 사고 실험 시리즈의 첫 번째 글이다. 분명 우리는 패러다임 변화의 한가운데에 있지만, 그것이 어디로 이어질지는 아무도 확신하지 못한다. 그래서 나는 미래를 예측하려는 것이 아니라, 강한 시나리오에 비추어 전제들을 스트레스 테스트해 무엇이 끝까지 버티는지 보려 한다. 이 시리즈 전체에서 우리는 AI가 도구로 사용될 것이라는, 더 그럴듯한 예측을 바탕으로 판단한다. AI를 의식 있는 동료나, 유토피아적 특이점에서 태어난 신으로 보지 않는다.
DRY(Don't Repeat Yourself)는 The Pragmatic Programmer (Hunt & Thomas, 1999)에서 왔다. 실무에서 사람들은 이를 “메서드 추출(extract a method)” 패턴으로 사용하는 경우가 많다. 코드 중복을 발견하면, 재사용될 수 있는 공통 위치로 뽑아내야 한다는 식이다. 많은 사람들은 이를 Uncle Bob이 말하는 단일 책임 원칙(Single Responsibility Principle)의 _단일 책임_과 운율 맞추듯 연결시키곤 한다. 반면 Clean Architecture를 주의 깊게 읽은 독자라면 여기서 모순을 찾아낼 것이다. 하지만 그 이야기는 이후 글 중 하나에서 다루겠다.
DRY의 원래 정의는 대부분이 생각하는 것보다 더 넓다. “시스템 내 모든 지식은 단 하나의, 모호하지 않으며, 권위 있는 표현을 가져야 한다.” 이 글에서는 원칙의 핵심이 코드 중복이 아니라 지식의 중복에 있다는 점을 강조해 두자. 코드 중복은 지식 중복의 결과이지, 원인이 아니다.
DRY는 특정 문제를 해결한다. 인간은 중복을 추적하는 데 신뢰할 수 없다는 문제다. 세 개의 서비스에 같은 세금 계산 로직이 있다. 요구사항이 바뀐다. 당신은 그중 두 개를 업데이트한다. 세 번째는 조용히 잘못된 상태로 남아 있다가, 네 달 뒤 어떤 고객이 불만을 제기하면서 드러난다. 추상화는 개발자인 당신이 복사본들이 어디에 있는지 잊어버릴 수밖에 없다는 사실을 우회하는 방법이다.
다르게 말하면 DRY는 _컨텍스트 관리 전략_이다. 인간 개발자는 제한된 정신적 컨텍스트 안에서 일한다. 기억할 수 있는 것, 화면에 떠 있는 것, 최근에 리뷰한 것 같은 범위 말이다. DRY는 각 지식이 정확히 한 곳에만 존재하도록 보장함으로써, 머릿속에 들고 있어야 할 컨텍스트의 양을 줄여준다. 복사본이 없으면 복사본들이 어디 있는지 기억할 필요가 없다.
이 프레이밍이 중요한 이유는, 우리가 답하려는 질문을 바꿔 놓기 때문이다. 질문은 “중복이 나쁜가?”가 아니다(나쁘다). 질문은 “코드를 유지보수하는 주체가 컨텍스트를 관리하기 위해 DRY가 필요한가?”이다. 그리고 그 답은 그 주체가 얼마나 많은 컨텍스트를 가질 수 있는지에 전적으로 달려 있다.
AI를 끌어오기 전에, DRY는 이미 너무 공격적으로 적용했을 때의 잘 알려진 실패 모드를 가지고 있다. 이를 살펴볼 가치는 충분하다. 인간에게도 이 원칙에는 한계가 있음을 보여주기 때문이다.
두 서비스가 이메일을 검증한다:
// Service A
public bool ValidateEmail(string email)
{
return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}
// Service B - identical
public bool ValidateEmail(string email)
{
return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}
DRY 리팩터링을 통해 이를 공유 라이브러리로 추출한다:
// SharedLib.Email
public static bool ValidateEmail(string email)
{
return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}
6개월 후, Service A는 플러스 주소 지정(user+tag@domain.com)을 허용해야 한다. Service B는 명시적으로 허용하지 않는다. 플러스 주소 지정이 사기 방지 파이프라인에서 위험 신호이기 때문이다. 이제 당신은 파라미터가 점점 늘어나는 문제(parameter creep), 전략 패턴(strategy pattern), 혹은 포기하고 다시 중복을 허용하는 선택지 사이에서 고민하게 된다. 공유 라이브러리는 6개월 동안의 일관성을 사 줬지만, 이후에는 결합(coupling)이라는 부채가 되었다.
이것은 겉보기에는 같은 코드가 실제로는 같은 지식인 것처럼 취급될 때 벌어지는 일이다. 두 서비스는 비즈니스 규칙을 공유해서가 아니라, 우연히 같은 코드를 갖게 된 것이었다. 어떤 사람들은 이를 “잘못된 추상화(wrong abstraction)” 혹은 “우연적 중복(coincidental duplication)”이라고 부른다. 서비스들이 서로 다른 속도로 진화하는 마이크로서비스 아키텍처에서는 사람들이 인정하는 것보다 훨씬 자주 일어난다.
당신의 개발 환경에, 코드베이스의 의미적 인덱스(semantic index)를 구축할 수 있는 AI 도구가 있다고 가정하자. 문법적으로 동일한 코드뿐 아니라 기능적으로 동등한 코드까지 매핑한다. 한 곳을 변경하면 관련된 모든 인스턴스를 식별하고, 각 맥락에 맞는 diff를 생성한다. 영향을 받는 각 서비스에 대해, 해당 서비스의 제약에 맞게 조정된 변경 사항으로 PR을 열어 준다.
이 세계에서는 이메일 예제가 다르게 전개된다. AI는 세 개의 검증기를 모두 찾아내고, 플러스 주소 지정에 대해서는 의도적으로 분기되었지만 도메인 검증 로직은 공유한다는 것을 이해한다. 그리고 플러스 주소 지정 동작은 건드리지 않으면서 도메인 체크만 업데이트하는 서로 다른 세 개의 패치를 생성한다. 각 서비스는 자신의 검증기를 유지한다. 공유 라이브러리도 없다. 버전 결합도 없다. 배포 의존성도 없다. 하지만 일관되어야 할 부분은 일관되게 유지된다.
겉보기에는 이것이 DRY의 필요성을 줄이는 것처럼 보인다. AI가 코드베이스 전체에서 중복을 추적하고 동기화할 수 있다면, 굳이 공유 추상화를 뽑아낼 이유가 있을까? 각 서비스가 자신만의 복사본을 갖게 하고, 도구가 일관성을 처리하게 하면 된다. 로컬 가독성, 평평한 의존성 그래프, 독립적 배포를 얻고, AI가 CI 단계에서 일관성을 검증해 준다.
설득력 있어 보인다. 하지만 이 주장에는 구조적 문제가 있다.
중복 추적을 가능하게 하는 동일한 AI 역량은 코드 생성도 가속한다. AI가 중복을 동기화할 만큼 코드베이스를 이해할 수 있다면, 새로운 기능도 더 빠르게 생성할 수 있다. 개발자는 더 적은 시간에 더 많은 코드를 배포한다. 사람이 수동으로는 쓰지 않았을 보일러플레이트도, 한계 비용이 거의 0이기 때문에 생성된다. 코드베이스는 성장한다.
그리고 여기서 논증이 순환하게 된다. 성장한 코드베이스는 결국 AI의 컨텍스트 윈도우 한계를 초과한다. 중복을 추적해 주기로 했던 바로 그 도구가, 더 이상 모든 중복을 한 번에 볼 수 없게 된다.
이는 이론적 우려가 아니다. 현재 LLM의 컨텍스트 윈도우는 수만에서 수십만 토큰 수준이다. 제대로 된 마이크로서비스 아키텍처는 수십 개 레포지토리에 걸쳐 수백만 줄의 코드로 구성될 수 있다. RAG, 임베딩, 시맨틱 검색을 써도 AI가 동시에 추론할 수 있는 양에는 하드한 한계가 있다. 유효 컨텍스트를 늘리는 모든 기법은 정밀도와 신뢰성에서 트레이드오프를 동반한다.
불편한 깨달음은, DRY에 대한 반론이 _자기 패배적(self-defeating)_이라는 점이다. DRY를 약화시키는 힘(더 나은 AI, 더 많은 컨텍스트)이 동시에 DRY를 강화하는 조건(더 많은 코드, 더 큰 코드베이스, 컨텍스트 오버플로)을 만들어낸다.
한 걸음 더 물러서면 질문은 이렇게 바뀐다. 컨텍스트 윈도우가 코드베이스보다 더 빠르게 커지는가?
역사적 추세는 모호하지 않다. 코드베이스는 수십 년 동안 계속 커져 왔다. 개발자 생산성을 높인 모든 도구(IDE, 프레임워크, 패키지 매니저, CI/CD, 그리고 이제 AI 보조 개발)는 코드가 줄어들게 한 것이 아니라 더 많은 코드를 만들게 했다. 산업은 생산성 향상에 대해 “덜 쓰자”로 반응한 적이 없다. 우리는 더 야심찬 목표를 향해 더 많은 코드를 더 빠르게 쓴다.
컨텍스트 윈도우도 커지고 있다. 4K 토큰에서 32K, 128K, 백만을 넘어서는 수준까지. 하지만 컴퓨팅에서 익숙한 패턴이다. RAM과 애플리케이션 메모리 사용량 사이, CPU 속도와 소프트웨어 복잡도 사이, 네트워크 대역폭과 데이터 حجم 사이에서도 같은 경쟁을 봐 왔다. 자원은 성장하지만 수요가 더 빠르게 성장한다. 파킨슨의 법칙을 소프트웨어에 적용하면: 코드는 그것을 처리할 수 있는 컨텍스트를 채우기 위해 팽창한다.
이 패턴이 유지된다면(그렇지 않을 강한 이유가 없다면), AI는 언제나 자신이 완전히 이해할 수 있는 경계 근처에서 동작하게 된다. 인간보다 중복을 찾고 추적하는 능력은 확실히 훨씬 뛰어나겠지만, 전지전능의 여유를 갖지는 못한다. DRY를 유한한 컨텍스트 안에서 코드베이스를 관리 가능하게 만드는 전략으로 이해한다면, 그 컨텍스트가 인간의 것이든 기계의 것이든 상관없이 DRY는 여전히 유효하다.
이 모든 것이 AI가 DRY를 실천하는 방식에 영향을 주지 않는다는 뜻은 아니다. 영향은 실제로 존재한다. 다만 “DRY는 죽었다” 같은 단순한 결론보다 더 미묘하다.
임계점이 이동한다. AI는 인간보다 더 많은 중복을 추적할 수 있으므로, 중복이 위험해지는 지점은 더 멀리 밀려난다. 작은 유틸리티 수준의 중복(문자열 포매팅, 기본 검증)은 AI 도구가 일관성을 검증해 줄 수 있다면 굳이 추출할 가치가 없을 수 있다. 하지만 대규모 지식 중복, 공통 비즈니스 규칙, 규제 로직, 핵심 도메인 개념은 여전히 명시적 공유 구현의 이점을 누린다.
검증이 가능해진다. 중복을 선택하더라도 AI는 “이 다섯 개 메서드는 같은 비즈니스 규칙을 구현하고 있고, 그중 두 개가 드리프트(drift)했다”라고 알려줄 수 있다. 이것은 진짜로 새로운 능력이다. 운영(프로덕션) 버그를 통해서야 불일치를 발견하는 대신, 중복에 대해 정보에 기반한 결정을 내릴 수 있다. 원칙은 “절대 중복하지 마”에서 “가시성을 가지고, 의식적으로 중복하라”로 이동한다.
Hunt와 Thomas가 원래 정식화에서 강조했지만 업계가 실무에서 대부분 무시해 온 ‘코드 중복’과 ‘지식 중복’의 구분이, 이제는 운영적으로 의미를 갖게 된다. AI 도구는 코드 수준의 중복은 어느 정도 허용하면서도 지식 수준에서 DRY를 강제하는 데 도움을 줄 수 있다. 어쩌면 이것이야말로 이 원칙이 본래 의도했던 의미일지도 모른다.
다음에 공유 메서드를 추출하거나 공유 라이브러리를 만들려 할 때, 이렇게 물어볼 가치가 있다. 내가 지금 뽑아내려는 것은 _정말로 같은 비즈니스 개념_을 나타내기 때문인가, 아니면 지금 우연히 코드가 비슷해 보일 뿐인가? 그리고 별개로: 이 추상화가 설계를 진짜로 더 낫게 만들기 때문에 하는 것인가, 아니면 내 제한된 컨텍스트 안에서 동기화를 유지할 메커니즘이 필요해서 하는 것인가?
첫 번째 질문은 언제나 올바른 질문이었다. 두 번째 질문이야말로 AI가—점진적으로, 그리고 도구가 실제로 볼 수 있는 범위 안에서만—계산을 바꾸는 지점이다. 당분간 DRY는 여전히 실용적인 기본값으로 남는다. 그것이 신성한 원칙이어서가 아니라, 인간이든 인공이든 컨텍스트는 언제나 유한하기 때문이다. 그리고 유한한 한, 우리는 그 한계 안에서 일하기 위한 전략이 필요하다.