Software Design

DRY 원칙 제대로 알기, 코드 중복보다 무서운 건 잘못된 추상화

코드 리뷰를 하다 보면 유난히 자주 마주치는 장면이 있어요.

이석우iOS Developer3분 읽기
DRY 원칙 제대로 알기, 코드 중복보다 무서운 건 잘못된 추상화 대표 이미지

코드 리뷰를 하다 보면 유난히 자주 마주치는 장면이 있어요.

기능 하나 고치려고 검색했는데, 거의 똑같은 코드가 세 군데서 나오는 겁니다. 하나만 고치면 나머지 두 개는 그대로 버그로 남는 거죠.

오늘의 주인공은 이 문제를 정면으로 겨냥하는 원칙, DRY예요. 지난번에 다뤘던 KISS 원칙과 짝꿍처럼 붙어 다니는 녀석입니다.

같은 코드 세 벌, 버그도 세 벌이 됩니다
같은 코드 세 벌, 버그도 세 벌이 됩니다

DRY는 “Don’t Repeat Yourself”의 약자입니다. 같은 지식을 시스템 안에서 반복하지 말라는 원칙이에요.

1999년에 나온 책 <실용주의 프로그래머>에서 앤디 헌트와 데이브 토머스가 정리한 개념인데, 원문 정의가 꽤 정확합니다.

모든 지식은 시스템 안에서 단 하나의, 모호하지 않은, 권위 있는 표현을 가져야 한다.

여기서 눈여겨볼 단어는 ’코드’가 아니라 ’지식’이에요. 이게 오늘 이야기의 핵심입니다.


코드 복붙이 왜 문제일까?

복사-붙여넣기 자체가 죄는 아니에요. 문제는 그 다음에 벌어집니다.

같은 로직이 세 군데 있으면, 요구사항이 바뀔 때 세 군데를 전부 찾아서 고쳐야 해요. 하나라도 놓치면 그게 버그가 됩니다.

// 배송비 계산이 세 파일에 흩어져 있다면
// Cart.swift
let fee = total >= 50000 ? 0 : 3000

// Checkout.swift
let shippingFee = totalPrice >= 50000 ? 0 : 3000

// OrderSummary.swift
let delivery = price >= 50000 ? 0 : 3000

어느 날 “무료배송 기준을 3만 원으로 내리자”는 요구가 들어오면? 세 파일을 전부 찾아야 합니다. 검색어도 제각각이라 하나쯤은 꼭 놓치고요.

// 지식을 한 곳에 모으면
// Shipping.swift
enum ShippingPolicy {
    static let freeShippingThreshold = 50000
    static let fee = 3000

    static func shippingFee(for total: Int) -> Int {
        total >= freeShippingThreshold ? 0 : fee
    }
}

이제 정책이 바뀌면 딱 한 곳만 고치면 됩니다. 이게 DRY예요.


코드가 아니라 ’지식’의 중복입니다

그런데 여기서 많은 분들이 한 번 삐끗합니다. DRY를 “비슷하게 생긴 코드를 전부 합쳐라”로 읽는 거예요.

DRY가 말하는 중복은 코드 모양이 아니라 지식, 그러니까 비즈니스 규칙의 중복입니다.

이 구분이 왜 중요하냐면, 세상에는 우연히 닮은 코드가 있거든요.

상황 중복일까?
배송비 계산 로직이 세 군데 진짜 중복 (같은 지식)
회원가입 검증과 이벤트 응모 검증이 우연히 비슷 가짜 중복 (다른 지식)
상수 3000이 배송비와 포인트 적립 기준에 각각 등장 가짜 중복 (다른 의미)

회원가입 검증과 이벤트 응모 검증이 지금은 둘 다 “이름 필수, 전화번호 11자리”라서 코드가 똑같아 보여도, 두 규칙은 서로 다른 이유로 바뀝니다. 이걸 하나로 합쳐두면 이벤트 규칙 바꾸다가 회원가입이 터져요.

닮았다고 다 같은 지식은 아니더라고요
닮았다고 다 같은 지식은 아니더라고요

섣부른 추상화가 중복보다 비쌉니다

그래서 요즘 커뮤니티에서는 이런 말도 자주 나옵니다. “잘못된 추상화보다는 중복이 낫다(prefer duplication over the wrong abstraction)“고요. 샌디 메츠라는 유명한 루비 개발자가 한 말이에요.

비슷해 보이는 코드 두 개를 억지로 합치면 이런 일이 벌어집니다.

  1. 공통 함수를 만든다
  2. 한쪽 요구사항이 바뀌어서 옵션 파라미터를 추가한다
  3. 다른 쪽도 바뀌어서 if 분기를 추가한다
  4. 어느새 파라미터 5개짜리 아무도 이해 못 하는 함수가 된다

이쯤 되면 차라리 복붙 두 벌이 나았던 겁니다.

억지로 합친 함수가 무너지는 4단계
억지로 합친 함수가 무너지는 4단계

그래서 실무에서는 ’3의 법칙(Rule of Three)’을 많이 써요. 같은 패턴이 두 번 나오면 일단 참고, 세 번째 나왔을 때 추상화하는 겁니다. 세 번쯤 반복되면 그게 진짜 같은 지식인지 우연인지 보이거든요.


제가 쓰는 판단 기준

저는 중복을 발견하면 이 질문 하나를 던집니다.

이 두 코드는 같은 이유로 바뀌는가?

같은 이유로 바뀐다면 진짜 중복이니 합칩니다. 다른 이유로 바뀐다면 생긴 게 아무리 닮았어도 그냥 둡니다.

합치기 전에 이 질문 하나면 충분합니다
합치기 전에 이 질문 하나면 충분합니다

오늘의 결론

DRY 원칙, 핵심만 추리면 이렇습니다.

  • 중복의 단위는 코드 모양이 아니라 지식(비즈니스 규칙)입니다.
  • 같은 이유로 바뀌는 코드만 합치세요. 우연히 닮은 코드는 그대로 두세요.
  • 확신이 없으면 3의 법칙. 세 번째 반복 때 추상화해도 늦지 않습니다.

KISS가 “단순하게 유지해라”라면, DRY는 “지식을 한 곳에만 둬라”입니다. 둘 다 결국 변경 비용을 줄이자는 얘기예요.

검색 한 번에 세 군데씩 걸리는 로직, 하나쯤 떠오르실 텐데요. 그게 오늘의 리팩토링 후보입니다.