새벽 2시. diff 화면에서 if is_premium and not is_trial and billing_cycle == 'annual'라는 한 줄을 응시하며, “Request Changes” 버튼 위에 커서를 올린 채 쉽게 누르지 못하고 있다. Pull Request의 제목은 “customer와 broker의 할인 계산 로직 통합”이다. 두 코드 블록은 실제로 거의 똑같이 생겼다. 레코드 하나를 로드하고, 퍼센트 필드를 업데이트하고, 데이터베이스에 다시 쓴다. 어떤 엔지니어가 이 ‘중복’을 발견했고, entity_type 파라미터를 받는 통합 메서드를 추출해냈다. 깔끔하고 합리적이며 DRY 원칙에 충실해 보인다.
하지만 당신은 안다. customer 할인은 내일부터 구간별 가격 계산으로 바뀔 예정이고, broker 커미션 로직은 향후 2년 동안 변하지 않을 것이다. 지금 억지로 합쳐버리면, 표면적으로는 중복을 제거한 것처럼 보이지만 실제로는 진화 방향이 완전히 다른 두 개념을 하나로 용접해버리는 셈이다. 이것이 Sandi Metz가 10년 전 경고했던 함정이다. 2026년 6월, 그녀의 글이 409포인트, 272개 댓글로 HN 1위에 재등극했다. AI가 단번에 ‘제법 그럴듯한’ 500줄짜리 코드를 생성해주는 시대에, 이 원칙은 그 어느 때보다 더 재론되어야 할 필요가 있다.
Sandi Metz가 그린 부패의 지도
Metz는 2014년 RailsConf 강연에서 처음으로 “중복이 잘못된 추상화보다 훨씬 싸다(duplication is far cheaper than the wrong abstraction)“라는 말을 꺼냈고, 2016년에 이를 블로그 글로 정리했다. 그녀의 논증은 지극히 소박하며 어떤 이론적 틀에도 의존하지 않는다. 그녀가 기술한 것은 모든 사람이 경험했지만 이름 붙인 사람은 거의 없었던 하나의 퇴행 과정이다.
프로그래머 A가 중복 코드를 발견한다. 그는 공통 함수나 클래스를 추출하고, 모든 중복 지점을 대체한 뒤 만족스럽게 자리를 뜬다. 시간이 흐르고 새 요구사항이 도착한다. 기존 추상화가 “거의” 들어맞는다. 프로그래머 B가 인계받아, 기존 코드에 대한 존중에서 새로 만들지 않고 기존 메서드에 파라미터 하나를 추가한 뒤, 메서드 내부에 조건 분기를 하나 더 넣는다. 이어서 세 번째 요구사항, 네 번째 파라미터, 다섯 번째 if-else. 여덟 번째 단계에 이르면 당신이 나타난다. 수천 줄의 얽히고설킨 조건 로직을 마주한 채, 어떤 분기가 어느 호출자에 속하는지 이해하려 애쓰면서.
Metz가 제시한 해법도 마찬가지로 간결하다. 추상화를 다시 인라인으로 풀어헤쳐서, 각 호출자가 자신에게 진짜 필요한 코드만 갖게 한 다음, 맨 처음부터 다시 관찰하라는 것이다. 어떤 유사성이 진짜인지, 어떤 것이 단지 ‘닮아 보이는’ 것인지를 보기 위해.
이 말이 관통력을 갖는 이유는, 프로그래머 집단 내에서 거의 종교적 신념처럼 자리 잡은 믿음을 깨뜨리기 때문이다. ‘중복은 악이다. 중복 제거는 선험적으로 옳다’라는 믿음 말이다.
DRY의 역사적 부채: 데이터베이스에서 코드베이스로의 오역
DRY 원칙은 Andy Hunt와 Dave Thomas가 1999년 《실용주의 프로그래머》에서 제안했다. 원문의 표현은 이렇다: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”(시스템 내에서 모든 지식 조각은 단일하고, 모호하지 않으며, 권위 있는 표현을 가져야 한다). 강조점은 ‘지식’에 놓여 있다. ‘문자’가 아니다. 하나의 SQL 쿼리, 하나의 비즈니스 규칙, 하나의 설정 값. 이것들이 지식이다. 우연히 닮은 두 개의 for 루프는 지식이 아닐 가능성이 크다.
그러나 산업계는 이 구분을 전파 과정에서 점차 압축했다. “반복하지 말라(Don’t Repeat Yourself)“가 “반복되는 코드 줄이 있어서는 안 된다”로 바뀌었다. 휴리스틱 원칙이 경직된 규칙으로 격상되면서, 수많은 본래 존재하지 말았어야 할 추상화가 탄생했다. 범용 Repository 기반 클래스, 만능 Processor 메서드, 비즈니스 로직보다 파라미터 목록이 더 긴 service 함수들.
Metz가 한 일은 본질적으로 DRY의 재조정이다. 그것이 반대하는 것은 ‘성급한 DRY’다. 이 문장은 HN 토론에서도 반복적으로 강조되었다. “이 글은 추상화를 하지 말라고 말하는 것이 아니라, 무리해서 추상화하지 말라고 말하는 것이다.”
’잘못된 추상화’를 식별하는 공학적 신호
HN 댓글창에서 여러 엔지니어가 잘못된 추상화를 식별하는 자신만의 기준을 공유했다. “이 코드가 동일한 일을 하는가, 아니면 단지 동일하게 보이는가?” 이것이 가장 많이 인용된 핵심 판별 기준이다. 다음 신호들이 중첩되어 나타난다면, 그 추상화는 높은 확률로 잘못된 것이다.
파라미터가 구동하는 조건 분기. 하나의 메서드가 불리언이나 열거형 파라미터를 받아, 내부에서 서로 거의 겹치지 않는 코드 경로로 if-else 분기한다. 새 파라미터가 하나 추가될 때마다, 호출자가 이해해야 하는 상태 공간은 한 차원 더 곱해진다.
한 호출자의 동작을 수정했을 때, 다른 호출자를 위해 ‘방어 코드’를 추가해야만 한다. 이는 호출자들 사이에 진짜 동반 변화 관계가 존재하지 않는다는 의미다. 그저 오늘 우연히 비슷한 코드를 실행하고 있을 뿐이다.
추상화가 자기 존재 이유를 스스로 설명하지 못한다. 건강한 추상화는 호출자를 보지 않고도 그 책임을 이해할 수 있다. 만약 이 로직이 무슨 일을 하는지 이해하기 위해 매번 세 개의 호출 측 컨텍스트를 거슬러 올라가야 한다면, 그 추상화는 이미 가장 큰 가치인 ‘인지 부하 감소’를 상실한 것이다.
새 기능을 추가할 때, 가장 먼저 드는 생각이 이 추상화를 재사용하는 것이 아니라 우회하는 것이다. 이것이 가장 신뢰할 수 있는 신호다. 인간의 직관은 종종 합리화된 사후 해석보다 더 빨리 구조적 문제를 포착한다.
HN의 핵심 논쟁: 단일 진실 vs. 국소성
이번 HN 토론에서 두 개의 높은 득표 댓글이 논쟁의 경계를 정밀하게 설정했다.
사용자 lg5689의 주장은 ‘추상화 우선’ 진영의 핵심 근거를 대변한다: “단일 진실 원천 원칙은 항상 따라야 한다. 만약 중복된 두 코드가 갈라질 때 버그가 된다면, 리팩터링해야 한다. 중복은 코드에 보이지 않는 장거리 결합을 만든다.” 이 논리는 깔끔한 공학적 직관에서 비롯된다. 동일한 비즈니스 규칙이 두 곳에 흩어져 있으면, 언젠가 한쪽만 수정되고 다른 한쪽은 잊히면서 버그가 심어진다는 것이다.
사용자 jonahx의 응답은 Metz가 진정으로 주목하는 시나리오를 겨냥한다: “근본적으로, 이 글이 논하는 상황은 바로 진실 원천이 몇 개인지 아직 모르는 경우다. 이 두 위치에서 사용되는 것이 같은 알고리즘인가, 아니면 조금씩 다른 버전인가? 더 중요한 것은, 이것들이 같은 종류의 이유로 변경될 것인가 하는 점이다. 결정적으로, 잘못된 추상화는 국소성을 파괴한다. 그리고 국소성은 당신이 코드를 수정할 때 실제로 신경 쓰는 유일한 속성이다. 나는 이 변경 하나만 하고 싶다. 시스템의 무관한 부분에 대한 부작용을 걱정하지 않으면서.”
두 댓글 모두 일리가 있지만, 적용되는 시나리오가 다르다. 두 위치가 동일한 불변의 사실을 대표한다고 확신할 수 있다면 — 동일한 세율, 동일한 암호화 알고리즘, 동일한 데이터 검증 규칙 — 그때는 추상화가 옳은 선택이다. 단일 진실 원천의 이점이 추상화 자체의 비용을 크게 상회한다.
문제는 이 업계에서 우리가 “두 코드가 동일한 의미를 갖는지 판별하는 능력”을 과대평가해 왔다는 점이다. Metz가 든 예에서 두 계산은 매우 비슷해 보인다: customer 레코드를 로드하고 할인율을 갱신; broker 레코드를 로드하고 커미션율을 갱신. 오늘날 두 작업은 우연히 모두 ‘엔티티 로드-퍼센트 갱신’이라는 패턴을 공유한다. 그러나 customer 할인의 비즈니스 로직은 언제든 구간별 계산으로 바뀔 수 있고, broker 커미션은 계속 단일 퍼센트를 유지할 수 있다. 왜냐하면 이 두 필드는 법률, 계약, 회계 측면에서 본질적으로 완전히 다른 성격을 갖기 때문이다.
“똑같아 보이는 코드”와 “동일한 진실을 대표하는 코드” 사이의 경계선은, 대부분의 엔지니어가 인정하는 것보다 훨씬 그리기 어렵다.
AI 코드 생성이 이 문제를 어떻게 증폭시키는가
바로 이 지점에서 AI 프로그래밍 도구의 광범위한 도입 이후 Metz의 글이 다시 정상에 오른 이유가 설명된다.
LLM은 코드 생성에서 두 가지 구조적 경향성을 갖는다. 첫째, ‘겉으로 보이는 중복’을 제거하려는 자연스러운 경향이다. 하나의 프롬프트로 유사한 두 기능 모듈을 생성할 때, 모델은 훈련 데이터에서 가장 ‘표준적인’ 병합 방식을 추출해 파라미터화된 추상화를 내놓는다. customer와 broker의 비즈니스 경계가 무엇인지 묻지 않는다. 요구사항 논의에 참여한 적이 없기 때문이다. 그저 통계적으로 가장 최적의 공유 표현을 찾았을 뿐이다.
둘째, 더 은밀하고 더 위험한 것은 LLM이 생성한 추상화가 비정상적으로 매끈하다는 점이다. 네이밍은 적절하고, 들여쓰기는 정확하며, 파라미터 배치는 논리적 감각을 갖추고 있다. 인간 엔지니어가 만든 서투른 추상화에는 보통 냄새가 난다. 네이밍이 어색하고, 구조가 헐겁고, 무리하게 적응하고 있다는 느낌을 준다. LLM의 잘못된 추상화는 전문적이고, 자신감 넘치며, 완벽무결해 보인다. 리뷰어가 더 쉽게 통과시킨다.
HN 토론에서 여러 댓글 작성자가 이 긴장 관계를 지적했다. 누군가는 “LLM은 타고난 반(反)추상화 기계”라고 말했다. 비즈니스 의미론이 아니라 표면적 패턴만 이해하기 때문이라는 것이다. 또 다른 이는 “LLM이 복제 비용을 대폭 낮추었기 때문에, 추상화에는 훨씬 더 높은 입증 기준이 필요해졌다”고 했다. 더 날카로운 관찰도 있다: “내가 가장 많은 시간을 들여 고민하는 것은, LLM에게 기존 코드베이스가 실제로 어떻게 작동하는지 설명해주면서도, 그것이 오해로 인해 코드를 왜곡하지 않도록 하는 방법이다.”
흥미로운 공학적 현상이 하나 있다. AI가 생성한 대량의 코드는 추상화보다 복제되는 경향이 짙다. 그 이유는 모델이 Metz의 원칙을 이해해서가 아니다. 단발적 요청들 사이에서 모델은 파일을 가로지르는 지속적 기억을 결여한다. 관련 코드를 컨텍스트 윈도우에 밀어넣지 않는 한, 이전 세션에서 비슷한 것을 작성한 적이 있다는 사실을 알지 못한다. 그 결과 AI 산출물 안에는 ‘추상화되어야 할 것처럼 보이지만 추상화되지 않은’ 코드 덩어리와, ‘추상화되었지만 추상화 방향이 완전히 틀린’ 코드 덩어리가 공존한다. 두 종류의 오류가 동일한 리포지토리에 집중되어 있는 것은, 아마도 AI 보조 프로그래밍이 유지보수자에게 안겨주는 새로운 일상일 것이다.
두 가지 오류 사이에서 선택하기
Metz의 입장은 종종 “복제가 추상화보다 낫다”로 단순화되지만, 이는 공정하지 않다. 그녀의 진짜 의미는 이렇다: 만약 복제와 잘못된 추상화 중 하나를 반드시 선택해야 한다면, 복제를 선택하라. 이것은 2차 원칙이다. 무엇이 옳은지 말해주는 것이 아니라, 무엇이 옳은지 확신할 수 없을 때 어느 방향의 오류가 비용이 더 낮은지를 말해준다.
HN의 높은 득표 댓글 중 하나는 실용적인 조작 규칙을 제공한다. “3회의 규칙”: 첫 번째 등장에서는 그냥 적어둔다. 두 번째 등장에서는 중복을 용인하되 관찰을 시작한다. 세 번째 등장에서 추상화를 고려한다. 그리고 실제로 변화하는 축을 따라서만 추상화한다. 이 규칙은 결정적 전제 하나를 내포하고 있다. 진짜 패턴이 스스로 드러날 시간이 필요하다는 것이다. 코드가 리포지토리 안에서 일정 시간 실행된 후에야, 어떤 호출 지점이 같은 주기로 변하고 어떤 것들이 갈라설지가 식별 가능해진다.
또 다른 댓글 작성자의 요약은 더 날카롭다: “DRY의 반대말은 중복이 아니라 WET — Write Everything Twice(모든 것을 두 번 작성하라)다. 두 번 작성하고 관찰하라. 세 번째에 움직여라.”
데이터 이후의 공학적 판단
HN의 투표 데이터 — 409포인트, 272개 댓글 — 는 이것이 엔지니어 집단 내 아직 아물지 않은 균열을 건드렸음을 말해준다. 모두가 DRY가 잘못 쓰일 수 있다는 것을 안다. 문제는 신입 엔지니어들이 세대마다 입사할 때 받는 교육이 여전히 ‘중복 제거’를 코드 리뷰에서 도전 불가능한 우선순위로 삼고 있다는 점이다.
AI가 규정을 준수하는 코드를 대신 작성해주는 시대에, 진짜 희소 역량은 더 이상 ‘어떻게 추상화할 것인가’가 아니라 ‘언제 추상화할 것인가’다. 후자가 요구하는 것은 기교가 아니다. 인내심, 비즈니스 도메인을 지속적으로 관찰한 끝에 형성된 판단력, 그리고 매몰 비용 앞에서 추상화를 과감히 되돌릴 수 있는 냉정함이다. Metz의 원문은 오늘날까지 울려 퍼진다: “잘못된 추상화에 직면했을 때, 가장 빠른 전진 방법은 후퇴하는 것이다.”
이 주제에는 궁극적인 답이 없다. 필자는 이 양측의 논쟁에 대해 절대적 입장을 고수하지 않는다. 추상화는 소프트웨어 공학에서 진정한 의미의 초석 개념 중 하나지만, 그 가치는 타이밍과 컨텍스트에 크게 의존한다. 이 글은 복제로 추상화를 대체하자고 주장하는 것이 아니다. 지적하고자 하는 것은 한층 더 좁은 판단이다. 인간과 기계가 교대로 코드를 생산하는 새로운 일상 속에서, ‘조금 더 기다렸다 추상화하는 것’의 대가는 우리가 오랫동안 생각해온 것보다 더 낮을 수 있고, ‘잘못 추상화한 후 그것을 뜯어내는 것’의 대가는 우리가 예상해온 것보다 더 높을 수 있다는 사실이다.
필자 고지: 본 글은 Sandi Metz의 원문, 2026년 6월 HN 토론, 그리고 관련 공학 문헌의 정리와 분석을 바탕으로 한다. 절대적인 기술적 조언이 아니다. 공학적 의사결정은 팀 규모, 비즈니스 단계, 코드베이스 연한, 테스트 커버리지 등 구체적 컨텍스트와 함께 이루어져야 한다. 이 변수들 중 어느 하나만 달라져도 본 글의 판단 방향은 완전히 뒤집힐 수 있다.