코드 중복도 (Code Duplication)

코드 중복도는 소프트웨어 내에서 동일하거나 유사한 코드가 반복되는 정도를 나타낸다.
중복된 코드는 유지보수를 어렵게 만들고, 버그 수정 시 여러 곳을 동시에 수정해야 하는 문제를 야기한다.
이는 일반적으로 바람직하지 않은 프로그래밍 관행으로 간주되며, 소프트웨어의 유지보수성과 확장성을 저해할 수 있다.

특징과 특성

  1. 유지보수 어려움: 중복된 코드는 변경 시 여러 곳을 동시에 수정해야 하므로 유지보수가 어려워진다.
  2. 버그 발생 가능성 증가: 한 곳의 수정을 다른 곳에 반영하지 않을 경우, 일관성이 깨지고 버그가 발생할 수 있다.
  3. 코드량 증가: 중복 코드는 전체 코드의 양을 증가시켜 가독성을 떨어뜨리고 디버깅을 어렵게 만든다.
  4. OCP(Open-Closed Principle) 위배: 중복 코드는 수정에 닫혀 있어야 한다는 SOLID 원칙에 위배된다.

중복 코드 감지 방법

  1. 수동 코드 리뷰: 개발팀이 협력하여 코드를 검토하고 중복을 식별한다.
  2. 코드 분석 도구 사용: SonarQube, PMD, ESLint 등의 도구를 활용하여 자동으로 중복 코드를 탐지한다.
  3. 버전 관리 시스템 활용: Git 등의 버전 관리 시스템을 통해 코드 변경 사항을 비교하여 중복을 식별한다.
  4. IDE 기능 활용: 대부분의 현대적인 IDE는 코드 중복을 찾는 기능을 내장하고 있다.

코드 중복의 유형

  1. 완전 중복(Exact Duplication):
    동일한 코드가 그대로 복사되어 사용되는 경우.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    // 완전 중복의 예
    class OrderCalculator {
        public double calculateTotal(List<Item> items) {
            double total = 0;
            for (Item item : items) {
                total += item.getPrice() * (1 - item.getDiscount());
            }
            return total * (1 + TAX_RATE);
        }
    }
    
    class InvoiceCalculator {
        public double calculateTotal(List<Item> items) {
            double total = 0;
            for (Item item : items) {
                total += item.getPrice() * (1 - item.getDiscount());
            }
            return total * (1 + TAX_RATE);
        }
    }
    
    // 개선된 버전
    class PriceCalculator {
        public double calculateTotal(List<Item> items) {
            double subtotal = calculateSubtotal(items);
            return applyTax(subtotal);
        }
    
        private double calculateSubtotal(List<Item> items) {
            return items.stream()
                .mapToDouble(item -> item.getPrice() * (1 - item.getDiscount()))
                .sum();
        }
    
        private double applyTax(double amount) {
            return amount * (1 + TAX_RATE);
        }
    }
    
  2. 구조적 중복(Structural Duplication):
    코드의 구조는 같지만 변수명이나 일부 값만 다른 경우.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    // 구조적 중복의 예
    public void processCustomerOrder(Order order) {
        if (order.isValid()) {
            if (order.getTotal() > 1000) {
                applyDiscount(order, 0.1);
            }
            sendConfirmation(order);
        }
    }
    
    public void processBusinessOrder(Order order) {
        if (order.isValid()) {
            if (order.getTotal() > 5000) {
                applyDiscount(order, 0.15);
            }
            sendConfirmation(order);
        }
    }
    
    // 개선된 버전
    public void processOrder(Order order, double threshold, double discountRate) {
        if (order.isValid()) {
            if (order.getTotal() > threshold) {
                applyDiscount(order, discountRate);
            }
            sendConfirmation(order);
        }
    }
    

중복 코드 검출 방법

  1. 토큰 기반 검출:
    코드를 토큰으로 변환하여 유사한 토큰 시퀀스를 찾는다.

    1
    2
    3
    4
    5
    6
    7
    8
    
    // 다음 두 코드 블록은 토큰 시퀀스가 유사합니다
    for (int i = 0; i < list.size(); i++) {
        sum += list.get(i);
    }
    
    for (int j = 0; j < array.length; j++) {
        total += array[j];
    }
    
  2. 추상 구문 트리(AST) 기반 검출:
    코드의 구조적 유사성을 분석한다.

  3. 메트릭 기반 검출:
    코드의 복잡도, 길이 등의 메트릭을 비교한다.

중복 제거 전략

  1. 추출 메서드(Extract Method) 패턴:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    // 중복된 코드를 메서드로 추출
    public class ReportGenerator {
        private void addHeader(Document doc) {
            doc.addTitle();
            doc.addDate();
            doc.addAuthor();
        }
    
        public void generateReport(Document doc) {
            addHeader(doc);
            // 보고서 특화 로직
        }
    
        public void generateSummary(Document doc) {
            addHeader(doc);
            // 요약 특화 로직
        }
    }
    
  2. 템플릿 메서드 패턴:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    abstract class DocumentGenerator {
        public final void generate() {
            addHeader();
            addContent();
            addFooter();
        }
    
        protected abstract void addContent();
    
        private void addHeader() {
            // 공통 헤더 로직
        }
    
        private void addFooter() {
            // 공통 푸터 로직
        }
    }
    
  3. 전략 패턴:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    interface PricingStrategy {
        double calculatePrice(Order order);
    }
    
    class RegularPricing implements PricingStrategy {
        public double calculatePrice(Order order) {
            // 일반 가격 계산
        }
    }
    
    class DiscountPricing implements PricingStrategy {
        public double calculatePrice(Order order) {
            // 할인 가격 계산
        }
    }
    
  4. 상속 활용: 여러 클래스에서 공통으로 사용되는 코드를 상위 클래스로 이동시킨다.

  5. 매개변수화: 유사하지만 일부 값만 다른 코드를 매개변수를 받는 함수로 통합한다.

도구

  1. SonarQube: 다양한 프로그래밍 언어를 지원하는 정적 코드 분석 도구로, 코드 중복을 포함한 다양한 코드 품질 이슈를 탐지한다.
  2. PMD: 자바, JavaScript 등 여러 언어를 지원하며, 코드 중복 감지 규칙을 제공한다.
  3. IntelliJ IDEA: 내장된 코드 분석 기능을 통해 중복 코드를 식별하고 리팩토링 제안을 제공한다.

주의사항

  1. 과도한 추상화 주의: 중복 제거를 위해 과도하게 추상화하면 코드의 복잡성이 증가할 수 있다.
  2. 컨텍스트 고려: 겉보기에 유사한 코드라도 다른 맥락에서 사용될 수 있으므로 신중히 판단해야 한다.
  3. 성능 고려: 중복 제거로 인한 함수 호출 증가가 성능에 미치는 영향을 고려해야 한다.

권장사항

  1. DRY 원칙 준수: “Don’t Repeat Yourself” 원칙을 따라 코드의 중복을 최소화한다.
  2. 모듈화 강조: 코드를 작은 단위의 모듈로 분리하여 재사용성을 높인다.
  3. 지속적인 리팩토링: 코드 베이스를 주기적으로 검토하고 리팩토링하여 중복을 제거한다.
  4. 코드 리뷰 문화 정착: 팀 내에서 정기적인 코드 리뷰를 통해 중복 코드를 조기에 발견하고 개선한다.

참고 및 출처