프로그래밍 언어 실전 가이드

DRY 원칙이란? 중복 없는 코드를 위한 DRY(Don't Repeat Yourself) 원칙과 실전 예제

devcomet 2025. 7. 16. 13:23
728x90
반응형

DRY 원칙 완벽 가이드 - 중복 코드 제거를 위한 개발 방법론
DRY 원칙이란? 중복 없는 코드를 위한 DRY(Don't Repeat Yourself) 원칙과 실전 예제

 

DRY 원칙은 소프트웨어 개발에서 코드 중복을 방지하고 유지보수성을 높이는 핵심 개발원칙으로,

같은 로직을 반복하지 않고 재사용 가능한 형태로 구현하는 클린코드 작성 방법론입니다.


DRY 원칙의 정의와 중요성

DRY(Don't Repeat Yourself) 원칙은 앤디 헌트(Andy Hunt)와 데이브 토마스(Dave Thomas)가

저서 『실용주의 프로그래머』에서 처음 소개한 소프트웨어 개발 원칙입니다.

이 원칙은 "모든 지식은 시스템 내에서 단일하고 명확하며 권위 있는 표현을 가져야 한다"는 철학을 바탕으로 합니다.

 

DRY 원칙의 핵심 개념

  • 동일한 코드나 로직을 여러 곳에 반복해서 작성하지 않기
  • 코드의 재사용성을 높여 유지보수 비용 절감
  • 버그 발생 위험을 줄이고 코드의 일관성 유지

DRY 원칙 작동 방식

[중복 코드 발견] 
       ↓
   [코드 분석]
       ↓
 [공통 로직 추출]
       ↓
[재사용 가능한 함수/클래스 생성]
       ↓
  [원본 코드에서 호출]
       ↓
 [코드 중복 제거 완료]

✅ 유지보수 시 장점:
[코드 수정 필요] → [단일 지점 수정] → [모든 호출 지점 자동 반영]

 

DRY 원칙 적용 과정

  1. 중복 코드 식별: 동일하거나 유사한 코드 블록 찾기
  2. 공통 로직 추출: 재사용 가능한 부분 분리
  3. 함수/클래스 생성: 추출된 로직을 독립적인 단위로 구성
  4. 호출 방식 변경: 원본 코드에서 새로운 함수/클래스 호출
  5. 테스트 및 검증: 기능 동작 확인

개발자면접에서도 자주 언급되는 DRY 원칙을 제대로 이해하고 적용하면, 더 나은 코딩습관을 형성할 수 있습니다.

DRY 원칙과 WET 코드의 차이점을 보여주는 인포그래픽 - 중복 코드 vs 재사용 가능한 함수
DRY 원칙 개념 설명


중복 코드가 발생하는 주요 원인

1. 복사-붙여넣기 습관

초보개발자들이 가장 자주 범하는 실수는 기존 코드를 복사해서 약간만 수정하는 것입니다.

단기적으로는 빠른 개발이 가능하지만, 장기적으로는 코드중복으로 인한 문제가 발생합니다.

 

2. 기능 요구사항의 유사성

비슷한 기능을 구현할 때 처음부터 새로 작성하는 경우가 많습니다.

이는 코드재사용의 기회를 놓치게 만듭니다.

 

3. 팀 간 소통 부족

다른 팀원이 이미 구현한 기능을 모르고 중복으로 개발하는 경우도 빈번합니다.

 

4. 리팩토링 지연

코드 리뷰나 리팩토링 과정에서 중복 코드를 발견했지만, 시간 부족으로 정리하지 않는 경우입니다.

코드 중복 발생 과정

개발 시나리오: 새로운 기능 구현 과정

[새로운 기능 요청] 
       ↓
   [기존 코드 검색]
       ↓
   유사한 코드 발견됨?
       ↓
   YES → [복사-붙여넣기] → [약간 수정] → ❌ 중복 코드 생성
       ↓
   [기능 구현 완료]
       ↓
   [코드 리뷰 단계]
       ↓
   중복 코드 발견됨?
       ↓
   YES → 시간 여유 있음?
       ↓
   YES → ✅ [리팩토링 수행] → [배포]
   NO  → ❌ [기술 부채 누적] → [유지보수 비용 증가]

🚨 주의점: 시간 압박으로 인한 리팩토링 지연이 가장 큰 문제!


DRY 원칙 위반 시 발생하는 문제점

문제점 설명 영향도
유지보수 비용 증가 동일한 수정을 여러 곳에서 해야 함 높음
버그 발생 위험 한 곳만 수정하고 다른 곳은 놓치는 경우 높음
코드 가독성 저하 중복된 코드로 인한 복잡성 증가 중간
개발 속도 저하 반복적인 수정 작업으로 인한 시간 낭비 중간
테스트 복잡성 동일한 로직을 여러 번 테스트해야 함 중간

 

DRY 원칙을 위반하면 소프트웨어아키텍처 전반에 악영향을 미치게 됩니다.

특히 대규모 프로젝트에서는 이런 문제들이 기하급수적으로 증가합니다.


DRY 원칙 자바 예제 - 실전 적용법

중복 코드 예시 (DRY 원칙 위반)

public class UserService {

    // 사용자 생성 시 유효성 검사
    public void createUser(String email, String password) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email is required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        if (password == null || password.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }

        // 사용자 생성 로직
        System.out.println("Creating user with email: " + email);
    }

    // 사용자 업데이트 시 유효성 검사
    public void updateUser(String email, String password) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email is required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        if (password == null || password.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }

        // 사용자 업데이트 로직
        System.out.println("Updating user with email: " + email);
    }
}

DRY 원칙 적용 후 개선된 코드

public class UserService {

    // 공통 유효성 검사 메서드로 함수분리
    private void validateUserInput(String email, String password) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email is required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        if (password == null || password.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
    }

    public void createUser(String email, String password) {
        validateUserInput(email, password);
        System.out.println("Creating user with email: " + email);
    }

    public void updateUser(String email, String password) {
        validateUserInput(email, password);
        System.out.println("Updating user with email: " + email);
    }
}

이처럼 자바dry 원칙을 적용하면 코드가 더 깔끔하고 유지보수하기 쉬워집니다.


DRY 원칙 파이썬 예제 - 데코레이터 활용

중복 코드 예시 (DRY 원칙 위반)

import time

class DataProcessor:

    def process_user_data(self, data):
        print("Starting user data processing...")
        start_time = time.time()

        # 실제 처리 로직
        processed_data = [item.upper() for item in data]

        end_time = time.time()
        print(f"User data processing completed in {end_time - start_time:.2f} seconds")
        return processed_data

    def process_order_data(self, data):
        print("Starting order data processing...")
        start_time = time.time()

        # 실제 처리 로직
        processed_data = [item * 2 for item in data]

        end_time = time.time()
        print(f"Order data processing completed in {end_time - start_time:.2f} seconds")
        return processed_data

DRY 원칙 적용 후 개선된 코드

import time
from functools import wraps

# 데코레이터를 활용한 DRY 원칙 적용
def timing_logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Starting {func.__name__}...")
        start_time = time.time()

        result = func(*args, **kwargs)

        end_time = time.time()
        print(f"{func.__name__} completed in {end_time - start_time:.2f} seconds")
        return result
    return wrapper

class DataProcessor:

    @timing_logger
    def process_user_data(self, data):
        return [item.upper() for item in data]

    @timing_logger
    def process_order_data(self, data):
        return [item * 2 for item in data]

파이썬dry 원칙을 적용할 때는 데코레이터, 클래스 상속, 제네릭 함수 등을 활용하면 효과적입니다.

리팩토링 전후 코드 비교 - 중복 코드에서 DRY 원칙 적용 코드로의 변화 과정
리팩토링 전후 코드 비교 - 중복 코드에서 DRY 원칙 적용 코드로의 변화 과정


DRY 원칙 적용 시 주의사항

1. 과도한 추상화 피하기

DRY 원칙을 지나치게 적용하면 오히려 코드가 복잡해질 수 있습니다.

# 과도한 추상화 예시 - 피해야 할 패턴
def universal_processor(data, operation_type, multiplier=1, case_conversion=None):
    if operation_type == "multiply":
        result = [item * multiplier for item in data]
    elif operation_type == "convert":
        if case_conversion == "upper":
            result = [item.upper() for item in data]
        elif case_conversion == "lower":
            result = [item.lower() for item in data]
    return result

2. 의미적 중복과 우연적 중복 구분

코드가 겉보기에 같더라도 의미가 다르면 무리하게 통합하지 않는 것이 좋습니다.

3. 성능 고려사항

함수 호출 오버헤드나 메모리 사용량을 고려해야 합니다.

특히 성능이 중요한 부분에서는 DRY 원칙보다 최적화를 우선시할 수 있습니다.


객체지향설계에서의 DRY 원칙 활용

상속을 통한 코드재사용

// 기본 클래스
public abstract class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    // 공통 메서드
    public void eat() {
        System.out.println(name + " is eating");
    }

    // 추상 메서드
    public abstract void makeSound();
}

// 구체적인 클래스들
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " says: Woof!");
    }
}

public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " says: Meow!");
    }
}

객체지향 DRY 원칙 적용 구조

📋 클래스 상속 구조 (Inheritance)

        Animal (추상 클래스)
         ├── name: String
         ├── eat(): void (공통 메서드)
         └── makeSound(): void (추상 메서드)
                    ↑
        ┌───────────┴───────────┐
        │                       │
    Dog 클래스              Cat 클래스
   └── makeSound():         └── makeSound():
       "Woof!"                  "Meow!"

🔧 컴포지션 구조 (Composition)

    Logger (유틸리티 클래스)
     └── log(String): void
              ↑
    ┌─────────┼─────────┐
    │                   │
UserService        OrderService
├── logger          ├── logger
└── createUser()    └── createOrder()

✅ 장점: Logger 수정 시 모든 서비스에 자동 반영

컴포지션을 통한 코드재사용

public class Logger {
    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

public class UserService {
    private Logger logger = new Logger();

    public void createUser(String email) {
        logger.log("Creating user: " + email);
        // 사용자 생성 로직
    }
}

public class OrderService {
    private Logger logger = new Logger();

    public void createOrder(String orderId) {
        logger.log("Creating order: " + orderId);
        // 주문 생성 로직
    }
}

 

객체지향 프로그래밍에서 DRY 원칙을 적용한 클래스 다이어그램 - 상속과 컴포지션 활용
객체지향 프로그래밍에서 DRY 원칙을 적용한 클래스 다이어그램 - 상속과 컴포지션 활용


실전 리팩토링 예제

Before: 중복 코드가 많은 상황

class ReportGenerator:

    def generate_user_report(self, users):
        # 공통 헤더 생성
        report = "=" * 50 + "\n"
        report += "USER REPORT\n"
        report += "Generated at: " + str(datetime.now()) + "\n"
        report += "=" * 50 + "\n\n"

        # 사용자 데이터 처리
        for user in users:
            report += f"User: {user['name']}, Email: {user['email']}\n"

        # 공통 푸터 생성
        report += "\n" + "=" * 50 + "\n"
        report += "End of Report\n"
        report += "=" * 50 + "\n"

        return report

    def generate_order_report(self, orders):
        # 공통 헤더 생성 (중복)
        report = "=" * 50 + "\n"
        report += "ORDER REPORT\n"
        report += "Generated at: " + str(datetime.now()) + "\n"
        report += "=" * 50 + "\n\n"

        # 주문 데이터 처리
        for order in orders:
            report += f"Order: {order['id']}, Amount: {order['amount']}\n"

        # 공통 푸터 생성 (중복)
        report += "\n" + "=" * 50 + "\n"
        report += "End of Report\n"
        report += "=" * 50 + "\n"

        return report

After: DRY 원칙 적용 후

from datetime import datetime

class ReportGenerator:

    def _generate_header(self, title):
        header = "=" * 50 + "\n"
        header += f"{title}\n"
        header += "Generated at: " + str(datetime.now()) + "\n"
        header += "=" * 50 + "\n\n"
        return header

    def _generate_footer(self):
        footer = "\n" + "=" * 50 + "\n"
        footer += "End of Report\n"
        footer += "=" * 50 + "\n"
        return footer

    def _generate_report_template(self, title, data_processor):
        report = self._generate_header(title)
        report += data_processor()
        report += self._generate_footer()
        return report

    def generate_user_report(self, users):
        def process_users():
            content = ""
            for user in users:
                content += f"User: {user['name']}, Email: {user['email']}\n"
            return content

        return self._generate_report_template("USER REPORT", process_users)

    def generate_order_report(self, orders):
        def process_orders():
            content = ""
            for order in orders:
                content += f"Order: {order['id']}, Amount: {order['amount']}\n"
            return content

        return self._generate_report_template("ORDER REPORT", process_orders)

이렇게 리팩토링하면 코드최적화와 동시에 유지보수성이 크게 향상됩니다.


DRY 원칙과 다른 개발 원칙들의 관계

SOLID 원칙과의 연관성

DRY 원칙은 SOLID 원칙 중 단일 책임 원칙(SRP)과 밀접한 관련이 있습니다.

각 클래스나 함수가 하나의 책임만 가지면 자연스럽게 중복 코드가 줄어듭니다.

개발 원칙들 간의 관계

🎯 DRY 원칙과 다른 개발 원칙들의 상관관계

         DRY 원칙
    (Don't Repeat Yourself)
            │
    ┌───────┼───────┐
    │       │       │
 SOLID   KISS    YAGNI
 원칙    원칙     원칙
    │       │       │
    │       │       └── "현재 필요한 중복만 제거"
    │       └── "과도한 추상화 주의"
    └── "함수 분리와 단일 책임"

🔄 상호 작용:
• SOLID → DRY: 단일 책임으로 자연스런 중복 제거
• DRY → SOLID: 공통 함수 분리로 책임 분산
• KISS ↔ DRY: 단순함 vs 재사용성의 균형점 찾기
• YAGNI + DRY: 미래가 아닌 현재 중복에 집중

DRY vs 다른 원칙들 비교

원칙 목적 DRY와의 관계 주의사항
SOLID 객체지향 설계 상호 보완적 과도한 추상화 주의
KISS 단순성 유지 때로 상충 균형점 찾기 중요
YAGNI 불필요한 기능 배제 현재 중복에 집중 미래 예측 금지

KISS 원칙과의 균형

KISS(Keep It Simple, Stupid) 원칙과 DRY 원칙 사이에는 때로 상충되는 부분이 있습니다.

과도한 추상화는 코드를 복잡하게 만들 수 있으므로 적절한 균형이 필요합니다.

YAGNI 원칙과의 조화

YAGNI(You Aren't Gonna Need It) 원칙에 따르면, 미래에 필요할 것이라고 예상되는 기능을 미리 만들지 않는 것이 좋습니다.

DRY 원칙도 마찬가지로 현재 필요한 중복 제거에 집중해야 합니다.


팀 개발에서의 DRY 원칙 실천 방법

1. 코드 리뷰 체계화

정기적인 코드 리뷰를 통해 중복 코드를 발견하고 개선합니다.

## 코드 리뷰 체크리스트
- [ ] 중복된 로직이 있는가?
- [ ] 공통 함수로 분리할 수 있는가?
- [ ] 유틸리티 클래스 활용이 가능한가?
- [ ] 상속이나 컴포지션을 활용할 수 있는가?

2. 공통 라이브러리 구축

팀 내에서 자주 사용하는 기능들을 공통 라이브러리로 만들어 관리합니다.

3. 문서화와 공유

구현된 공통 기능들을 문서화하여 팀원들이 재사용할 수 있도록 합니다.

4. 정적 분석 도구 활용

SonarQube, PMD 등의 도구를 활용하여 중복 코드를 자동으로 감지합니다.

팀 개발에서의 DRY 원칙 적용 프로세스

📋 체계적인 DRY 원칙 적용 워크플로우

1️⃣ 개발 단계
   [개발 시작] → [코드 작성] → [셀프 코드 리뷰]
                                      ↓
2️⃣ 1차 검토 단계                      중복 발견?
   중복 발견 시 → [리팩토링 수행] → [팀 코드 리뷰]
                                      ↓
3️⃣ 팀 리뷰 단계                      리뷰어가 중복 발견?
   중복 발견 시 → [공통 라이브러리 검토]
                           ↓
4️⃣ 라이브러리 활용 판단              기존 라이브러리 활용 가능?
   YES → [라이브러리 적용]
   NO  → [새 공통 함수 생성]
                           ↓
5️⃣ 문서화 및 공유 단계              [문서화 및 팀 공유]
                           ↓
6️⃣ 자동화 검증 단계                [정적 분석 도구 실행]
                           ↓
7️⃣ 최종 단계                      [배포 완료]

🔧 도구 활용: SonarQube, PMD, ESLint 등으로 자동 감지

DRY 원칙 적용을 위한 실전 팁

1. 점진적 리팩토링

한 번에 모든 중복 코드를 제거하려고 하지 말고, 점진적으로 개선해나가는 것이 좋습니다.

2. 테스트 코드 작성

리팩토링 전후에 동작이 동일한지 확인하기 위해 테스트 코드를 작성합니다.

3. 설계 패턴 활용

템플릿 메서드 패턴, 전략 패턴, 팩토리 패턴 등을 활용하여 중복을 줄입니다.

4. 함수형 프로그래밍 개념 도입

고차 함수, 콜백 함수 등을 활용하여 코드 재사용성을 높입니다.

DRY 원칙 적용 전략 가이드

🎯 중복 코드 타입별 대응 전략

중복 코드 발견
       ↓
   패턴 분석 단계
       ↓
┌──────┼──────┬──────┐
│      │      │      │
로직   구조   설정   데이터
중복   중복   중복   중복
│      │      │      │
↓      ↓      ↓      ↓


🔧 대응 방법

1️⃣ 로직 중복
   → 공통 함수 생성
   → 헬퍼 클래스 활용
   → 유틸리티 메서드 구현

2️⃣ 구조 중복  
   → 상속 구조 설계
   → 인터페이스 활용
   → 컴포지션 패턴 적용

3️⃣ 설정 중복
   → 설정 파일 통합
   → 환경 변수 활용
   → 상수 클래스 생성

4️⃣ 데이터 중복
   → DTO/VO 통합
   → 매핑 함수 생성
   → 변환 유틸리티 구현

✅ 각 단계별 필수 작업
• 테스트 코드 작성 → 점진적 리팩토링 → 동작 검증 → 문서화

결론

DRY 원칙은 단순히 코드 중복을 피하는 것을 넘어서, 더 나은 소프트웨어 설계와 유지보수를 위한 핵심 원칙입니다.

올바른 DRY 원칙 적용을 통해 코드의 품질을 높이고, 개발 생산성을 향상시킬 수 있습니다.

핵심 포인트:

  • 중복 코드 제거를 통한 유지보수 비용 절감
  • 함수분리와 모듈화를 통한 코드 재사용성 향상
  • 적절한 추상화 수준 유지
  • 팀 차원의 체계적인 접근

DRY 원칙을 마스터하면 더 나은 개발자로 성장할 수 있으며, 클린코드 작성 능력도 자연스럽게 향상됩니다.

지속적인 학습과 실습을 통해 DRY 원칙을 내재화하고, 실무에서 효과적으로 활용해보시기 바랍니다.


참고 자료

SOLID 원칙이란? 실전 예제로 쉽게 이해하는 객체지향 설계 5대 원칙

 

SOLID 원칙이란? 실전 예제로 쉽게 이해하는 객체지향 설계 5대 원칙

개요SOLID 원칙은 객체지향 프로그래밍에서 유지보수 가능하고 확장 가능한 소프트웨어를 설계하기 위한 5가지 핵심 설계 원칙으로,개발자 면접과 실무에서 필수적으로 알아야 할 개념입니다.초

notavoid.tistory.com

 

리팩토링이란? 코드 품질을 높이는 리팩토링 실전 가이드와 예제

 

리팩토링이란? 코드 품질을 높이는 리팩토링 실전 가이드와 예제

리팩토링의 정의와 중요성리팩토링은 소프트웨어 개발에서 코드의 외부 동작을 변경하지 않으면서 내부 구조를 개선하는 과정입니다.코드개선의 핵심은 가독성, 유지보수성, 확장성을 높이는

notavoid.tistory.com

 

728x90
반응형