WTC - 단일 책임 원칙

김진성·2024년 11월 5일

WTC

목록 보기
5/6

메서드가 한 가지 기능만 수행하는지 확인하는 기준을 세우면 코드의 품질을 높이고 유지보수를 더 쉽게 할 수 있다.

메서드가 한 가지 기능만 수행하는지 확인하는 기준

  1. 중복 코드 확인:

    • 여러 메서드에 동일한 코드가 반복된다면, 해당 코드가 하나의 기능으로 재사용 가능한지 확인한다.
    • 중복되는 부분을 별도의 메서드로 분리해, 재사용 가능하게 하고 코드 중복을 줄인다.
  2. 메서드의 길이 제한:

    • 메서드가 15줄을 넘지 않도록 작성하면, 한 가지 기능을 수행하도록 자연스럽게 압축할 수 있다.
    • 메서드가 길어지면 여러 기능을 수행하는 경우가 많으므로, 의식적으로 기능별로 분리하는 연습을 한다.
  3. 메서드 이름을 통해 역할 파악하기:

    • 메서드의 이름이 복잡하거나 "and", "or" 등의 단어를 포함한다면, 한 가지 이상의 기능을 수행하고 있을 가능성이 크다.
    • 메서드 이름이 간결하게 하나의 행동이나 동작을 설명하는지 확인한다.
  4. 하나의 책임 원칙:

    • 메서드가 하나의 책임(Single Responsibility Principle, SRP)만 가지도록 합니다. 즉, 메서드는 하나의 작업이나 기능만 수행해야 하며, 다른 작업을 포함하지 않아야 한다.
    • 예를 들어, 데이터를 가져오고, 가공하고, 출력하는 작업을 한 메서드에서 처리하지 말고, 각 작업을 별도의 메서드로 분리한다.
  5. 의미 있는 구간으로 분리 가능 여부:

    • 메서드 내부에서 공백 라인으로 나눌 수 있는 구간이 많다면, 각 구간이 독립적인 기능을 하고 있을 가능성이 크다.
    • 각 구간을 분리해 의미 있는 이름을 가진 별도 메서드로 만들 수 있는지 검토해 본다.

예제 코드: 중복 코드 제거 및 메서드 분리

개선 전 코드

public class OrderProcessor {

    public void processOrder(Order order) {
        // 유효성 검사
        if (order == null || order.getItems().isEmpty()) {
            System.out.println("Invalid order.");
            return;
        }
        
        // 가격 계산
        double total = 0;
        for (Item item : order.getItems()) {
            total += item.getPrice() * item.getQuantity();
        }

        // 할인 적용
        if (order.isDiscountApplicable()) {
            total *= 0.9; // 10% 할인
        }

        System.out.println("Total order cost: " + total);
    }
}

개선 후 코드

위 코드는 여러 기능을 포함하고 있으며, 중복된 검증 코드도 존재한다. 이를 기능별로 분리하고, 유효성 검사와 계산 작업을 각각의 메서드로 나눠보자.

public class OrderProcessor {

    private static final double DISCOUNT_RATE = 0.9;

    public void processOrder(Order order) {
        if (!isValidOrder(order)) {
            displayInvalidOrderMessage();
            return;
        }

        double total = calculateTotalPrice(order);
        total = applyDiscountIfApplicable(order, total);

        displayTotalPrice(total);
    }

    // 1. 유효성 검사 메서드
    private boolean isValidOrder(Order order) {
        return order != null && !order.getItems().isEmpty();
    }

    // 2. 유효하지 않은 주문 메시지 출력 메서드
    private void displayInvalidOrderMessage() {
        System.out.println("Invalid order.");
    }

    // 3. 총 가격 계산 메서드
    private double calculateTotalPrice(Order order) {
        double total = 0;
        for (Item item : order.getItems()) {
            total += item.getPrice() * item.getQuantity();
        }
        return total;
    }

    // 4. 할인 적용 메서드
    private double applyDiscountIfApplicable(Order order, double total) {
        return order.isDiscountApplicable() ? total * DISCOUNT_RATE : total;
    }

    // 5. 총 가격 출력 메서드
    private void displayTotalPrice(double total) {
        System.out.println("Total order cost: " + total);
    }
}

개선 결과

  • 중복 코드 제거: 유효성 검사 코드가 별도의 메서드 isValidOrder로 분리되어 재사용이 가능하다.
  • 메서드 길이 감소: 각 메서드가 15줄을 넘지 않으며, 하나의 역할만 담당하도록 기능을 분리한다.
  • 단일 책임 원칙 적용: processOrder 메서드는 주문 처리의 전체 흐름을 담당하고, 실제 작업은 각 기능별 메서드에 위임하여 단일 책임을 유지한다.

확인 기준

  • 단일 책임 원칙 준수 여부
  • 메서드 이름이 간결하고 하나의 동작만 표현하는지
  • 중복 코드 여부
  • 의미 있는 코드 그룹화 가능 여부
  • 독립적으로 테스트 가능한지

독립적으로 테스트 가능한지

  1. 외부 상태나 의존성을 요구하지 않는지 확인

    메서드나 클래스가 특정 외부 상태(파일, 데이터베이스, 네트워크)에 의존하지 않고 독립적으로 동작할 수 있어야 한다.
    예를 들어, 계산 메서드가 외부 데이터베이스 연결이나 파일 시스템 상태에 의존하지 않으면 독립적으로 테스트하기 쉽다.
    필요한 경우, 의존성을 외부로 분리하여 의존성 주입(DI)이나 Mocking 기법을 사용하여 독립성을 높일 수 있다.

  2. 입력값만으로 테스트 가능 여부 확인

    메서드나 클래스가 입력값을 기반으로 출력을 결정할 수 있다면 독립적으로 테스트하기 쉬운 구조이다.
    예를 들어, calculateTotalPrice와 같은 메서드는 입력으로 주어진 Order 객체만으로 계산 결과를 반환하기 때문에 외부 상태에 영향을 받지 않는다. 이는 독립적인 테스트가 가능한 메서드이다.

  3. 변경할 필요 없는 외부 객체에 의존하지 않는지 확인

    테스트 대상 메서드가 외부 객체에 의존하지 않거나, 의존하더라도 해당 객체를 변경할 필요가 없는 경우 독립적인 테스트가 가능하다.
    외부 객체의 상태를 조작해야만 테스트가 가능한 경우, 이는 독립적인 테스트가 어렵다는 신호이다. 이런 경우 Mock 객체를 사용하거나 해당 객체를 분리하는 방법을 고려할 수 있다.

  4. 단순 출력이나 계산을 위한 메서드인지 확인

    단순히 계산이나 변환을 수행하는 메서드는 외부 상태에 영향을 주지 않기 때문에 독립적으로 테스트하기 쉽다.
    예를 들어, applyDiscountIfApplicable 메서드는 할인율을 계산하여 반환하는 역할만 하므로, 독립적인 입력값에 따라 결과를 테스트할 수 있다.

  5. 의존성 주입을 통한 외부 의존성 분리 여부 확인

    외부 종속성이 필요한 경우, 의존성 주입(DI)을 사용하여 테스트 시 쉽게 대체할 수 있는 구조로 만들면 독립적인 테스트가 가능하다.
    예를 들어, 데이터베이스 연결이 필요한 클래스는 DB 연결 객체를 외부에서 주입받도록 하면, 테스트 시에 Mock DB를 주입하여 독립적으로 테스트할 수 있다.

  6. 단위 테스트 작성 시 쉽게 검증할 수 있는지 확인

    단위 테스트를 작성해보면서 독립적인 테스트가 가능한지 확인할 수 있습니다. 테스트가 잘 동작하지 않거나 외부 설정이 필요하다면 독립성이 부족하다는 신호이다.
    예를 들어, 테스트 코드에서 다른 클래스나 외부 리소스를 설정하지 않고, 단일 메서드만 테스트할 수 있는지 확인해야한다.

profile
minimalist

0개의 댓글