2025-08-09 00:15

Tags:

DRY 원칙 핸드북

1. 만들어진 이유 (탄생 배경)

DRY(Don’t Repeat Yourself), 즉 “반복하지 말라”는 원칙은 앤디 헌트(Andy Hunt)와 데이브 토머스(Dave Thomas)가 1999년에 출간한 저서 **“실용주의 프로그래머 (The Pragmatic Programmer)“**에서 처음으로 명확하게 정의. 이들은 소프트웨어 개발 과정에서 반복되는 정보와 로직이 시스템 전체의 유지보수를 어렵게 만들고, 버그 발생 가능성을 높이는 핵심 원인임을 간파.

소프트웨어 시스템 내에서 어떤 지식(정보)의 단 한 조각이라도 모호하지 않고, 권위 있고, 단 하나뿐인 표현을 가져야 한다는 것이 이 원칙의 핵심. 여기서 ‘지식’이란 단순히 코드뿐만 아니라 데이터베이스 스키마, 테스트 계획, 문서 등 시스템을 구성하는 모든 정보 단위를 포함.

만약 동일한 로직이 여러 곳에 복사-붙여넣기 되어 있다면, 수정이 필요할 때 모든 복사본을 찾아 빠짐없이 고쳐야만 함. 하나라도 놓치면 시스템은 비일관적인 상태에 빠지고, 이는 곧 버그로 이어짐. DRY 원칙은 이러한 위험을 원천적으로 차단하고, 시스템을 더 예측 가능하고 관리하기 쉽게 만들기 위한 실용적인 철학에서 탄생.


2. 핵심 개념

DRY는 단순히 코드 중복을 제거하는 것 이상의 의미. 원칙의 본질은 **‘지식의 중복’**을 방지하는 것.

예를 들어, 쇼핑몰 애플리케이션에서 ‘부가가치세율 10%‘라는 정보가 있다고 가정.

  • 나쁜 예 (WET: Write Everything Twice):

    • 결제 로직에 price * 0.1 코드가 있음.

    • 주문 내역 표시 UI에 가격 * 0.1 계산 로직이 있음.

    • 세금계산서 생성 모듈에 total * 0.1 코드가 있음.

이 경우 ‘부가가치세율은 10%‘라는 지식이 세 곳에 흩어져 반복되고 있음. 만약 세율이 12%로 변경된다면, 개발자는 이 세 곳을 모두 찾아 수정해야 함.

  • 좋은 예 (DRY):

    • TAX_RATE = 0.1 이라는 상수를 한 곳에 정의.

    • 모든 관련 로직은 이 상수를 참조하여 세금을 계산. (price * TAX_RATE)

이제 세율이 변경되면 TAX_RATE 상수의 값만 0.12로 바꾸면 됨. 시스템 전체에 일관되게 변경 사항이 적용되며, 이것이 바로 DRY 원칙이 추구하는 **‘단일 진실 공급원(Single Source of Truth, SSOT)‘**의 구현.


3. 구조 및 적용

DRY 원칙은 시스템의 다양한 계층에서 여러 형태로 적용 가능.

  • 코드 레벨:

    • 함수(Functions)와 메서드(Methods): 가장 기본적인 적용 방식. 두 번 이상 사용되는 코드 블록이 있다면 즉시 함수로 추출.

    • 클래스(Classes)와 상속(Inheritance): 여러 객체가 공통된 데이터와 행위를 가질 때, 이를 클래스로 묶고 상속을 통해 재사용.

    • 헬퍼/유틸리티 클래스(Helpers/Utilities): 프로젝트 전반에서 사용되는 범용적인 기능(예: 날짜 포맷팅, 문자열 처리)은 별도의 유틸리티 모듈로 분리.

  • 시스템 아키텍처 레벨:

    • 서비스(Services): 여러 애플리케이션이 공통적으로 필요로 하는 기능(예: 사용자 인증, 결제)은 마이크로서비스나 별도의 API 서버로 분리하여 제공.

    • 설정(Configuration): 데이터베이스 연결 정보, API 키 등 환경에 따라 달라지는 값들은 코드에 하드코딩하지 않고, 별도의 설정 파일(.env, .json, .yml 등)로 분리하여 관리.

  • 데이터베이스 레벨:

    • 정규화(Normalization): 데이터베이스 설계에서 중복 데이터를 최소화하는 과정 자체가 DRY 원칙의 구현. 예를 들어, 사용자의 주소 정보가 여러 테이블에 흩어져 있다면, 별도의 ‘주소’ 테이블로 분리하고 외래 키(Foreign Key)로 참조.

4. 사용법 (코드 예시)

WET (반복적인) 코드

Python

# 주문 처리 함수
def process_order(order):
    # 고객에게 이메일 발송
    # ... 이메일 발송 로직 ...
    print(f"Sending confirmation email to {order.customer_email}")
    # ...

# 회원가입 처리 함수
def register_user(user):
    # 신규 회원에게 환영 이메일 발송
    # ... 이메일 발송 로직 ...
    print(f"Sending welcome email to {user.email}")
    # ...

위 코드에서는 ‘이메일 발송 로직’이라는 지식이 두 함수에 걸쳐 중복되고 있음.

DRY (원칙을 적용한) 코드

Python

# 이메일 발송을 책임지는 단일 함수
def send_email(recipient_email, subject, body):
    # ... 이메일 발송 로직 ...
    print(f"Email sent to {recipient_email} with subject: {subject}")
    # ...

# 주문 처리 함수
def process_order(order):
    send_email(order.customer_email, "Order Confirmation", "Thank you for your order.")

# 회원가입 처리 함수
def register_user(user):
    send_email(user.email, "Welcome!", "Thank you for registering.")

이제 이메일 발송 방식(예: SMTP 서버 변경, 템플릿 엔진 도입)을 수정해야 할 때, send_email 함수 하나만 변경하면 됨. 코드가 훨씬 간결하고, 유지보수가 용이해짐.


5. 심화 내용

  • DRY vs WET vs AHA:

    • WET (Write Everything Twice): DRY의 반대. 일단 반복해서 작성하는 것.

    • AHA (Avoid Hasty Abstractions): 성급한 추상화를 피하라는 원칙. DRY를 너무 맹목적으로 적용하면, 아직 패턴이 명확하지 않은데도 섣불리 코드를 추상화하게 됨. 이는 오히려 잘못된 추상화를 낳아 더 복잡한 코드를 만들 수 있음. 일반적으로 세 번 이상 반복될 때 추상화를 고려하는 ‘Rule of Three’가 좋은 지침이 됨.

  • 잘못된 추상화의 위험: 서로 다른 맥락에서 우연히 형태만 비슷해 보이는 코드를 하나의 함수로 묶는 것은 위험. 예를 들어, ‘사용자 이름 유효성 검사(515자)‘와 ‘상품명 유효성 검사(515자)‘는 현재 규칙이 같더라도, 미래에는 각기 다른 방향으로 변경될 가능성이 높음 (사용자 이름은 특수문자 금지, 상품명은 허용 등). 이런 경우, 이 둘을 validate_length(text, min, max)와 같이 묶는 것은 잘못된 추상화가 될 수 있음.

  • DRY와 다른 원칙과의 관계:

    • SoC (Separation of Concerns): 관심사의 분리 원칙. DRY는 SoC를 달성하기 위한 구체적인 방법론 중 하나.

    • YAGNI (You Ain’t Gonna Need It): 필요하지 않은 기능은 만들지 말라는 원칙. AHA와 유사하게, 미래를 예측하여 과도하게 DRY를 적용하는 것을 경계.

DRY 원칙은 코드의 품질을 높이는 강력한 도구이지만, 기계적으로 적용하기보다는 **‘지식의 본질적인 중복인가, 아니면 우연의 일치인가’**를 항상 고민하며 신중하게 접근해야 함.

이 핸드북이 DRY 원칙을 이해하는 데 도움이 되었기를 바람. 혹시 특정 상황에서 DRY 원칙을 어떻게 적용할지 더 구체적인 예시가 필요하다면 알려주기 바람.

References

DRY 원칙