결합도 (Coupling)

결합도는 서로 다른 모듈 간의 상호 의존성이나 연관성을 측정하는 지표이다.
낮은 결합도는 모듈이 독립적이며 변경 시 다른 모듈에 미치는 영향이 적음을 의미한다.

모듈이란 클래스, 컴포넌트, 패키지 등 코드의 논리적 단위를 의미한다.

특징 및 기능

  • 모듈 간 상호작용 정도를 나타냄
  • 소프트웨어 구조의 품질을 평가하는 지표로 사용
  • 유지보수성, 재사용성, 테스트 용이성에 영향을 미침

결합도의 종류

낮은 결합도부터 높은 결합도 순

  1. 내용 결합도(Content Coupling):
    가장 강한 형태의 결합도로, 한 모듈이 다른 모듈의 내부 동작에 직접 관여하는 경우이다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // 높은 내용 결합도의 예
    class UserService {
        private UserRepository userRepo;
    
        public void createUser(User user) {
            // UserRepository의 내부 구현에 직접 의존
            userRepo.userList.add(user);  // 내부 데이터 구조에 직접 접근
        }
    }
    
    // 개선된 버전
    class UserService {
        private UserRepository userRepo;
    
        public void createUser(User user) {
            // 공개된 인터페이스를 통해 상호작용
            userRepo.save(user);
        }
    }
    
  2. 공통 결합도(Common Coupling):
    여러 모듈이 전역 데이터를 공유하는 경우.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    // 높은 공통 결합도의 예
    public class GlobalConfig {
        public static Map<String, Object> sharedData;
    }
    
    class ServiceA {
        public void process() {
            GlobalConfig.sharedData.put("key", "value");
        }
    }
    
    // 개선된 버전
    public class Configuration {
        private Map<String, Object> data;
    
        public void setValue(String key, Object value) {
            data.put(key, value);
        }
    
        public Object getValue(String key) {
            return data.get(key);
        }
    }
    
  3. 제어 결합도(Control Coupling):
    한 모듈이 다른 모듈의 내부 로직을 제어하는 경우.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    // 높은 제어 결합도의 예
    class OrderProcessor {
        public void processOrder(Order order, String type) {
            if (type.equals("NORMAL")) {
                processNormalOrder();
            } else if (type.equals("EXPRESS")) {
                processExpressOrder();
            }
        }
    }
    
    // 개선된 버전 - 전략 패턴 사용
    interface OrderProcessingStrategy {
        void process(Order order);
    }
    
    class OrderProcessor {
        private OrderProcessingStrategy strategy;
    
        public void processOrder(Order order) {
            strategy.process(order);
        }
    }
    
  4. 스탬프 결합도(Stamp Coupling):
    모듈들이 동일한 자료구조를 매개변수로 전달하는 경우.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    // 높은 스탬프 결합도의 예
    class ReportGenerator {
        public void generateReport(CustomerData data) {
            // CustomerData의 전체 구조에 의존
        }
    }
    
    // 개선된 버전
    class ReportGenerator {
        public void generateReport(String customerName, List<Order> orders) {
            // 필요한 데이터만 매개변수로 전달
        }
    }
    

결합도를 측정하는 방법

  1. 정적 분석:

    • 팬인(Fan-in): 해당 모듈을 사용하는 다른 모듈의 수
    • 팬아웃(Fan-out): 해당 모듈이 사용하는 다른 모듈의 수
    • 불안정성(I): I = Fan-out / (Fan-in + Fan-out)
  2. 동적 분석:

    • 런타임 의존성 분석
    • 메서드 호출 그래프 분석

결합도를 낮추기 위한 전략

  1. 의존성 주입(Dependency Injection) 사용:

    1
    2
    3
    4
    5
    6
    7
    8
    
    // 의존성 주입을 통한 결합도 감소
    class UserService {
        private final UserRepository userRepository;
    
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
  2. 인터페이스 활용:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    // 인터페이스를 통한 추상화
    public interface PaymentGateway {
        void processPayment(Payment payment);
    }
    
    public class PayPalGateway implements PaymentGateway {
        public void processPayment(Payment payment) {
            // PayPal 구현
        }
    }
    
  3. 이벤트 기반 통신:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // 이벤트를 통한 느슨한 결합
    public class OrderService {
        private EventPublisher eventPublisher;
    
        public void createOrder(Order order) {
            // 주문 처리
            eventPublisher.publish(new OrderCreatedEvent(order));
        }
    }
    

도구

  1. SonarQube: 코드 품질 및 결합도 분석
  2. Structure101: 아키텍처 의존성 분석
  3. JDepend: Java 패키지 의존성 분석
  4. NDepend:.NET 코드 분석

주의사항

  1. 과도한 추상화는 오히려 복잡성을 증가시킬 수 있다.
  2. 모든 결합도를 제거하는 것은 불가능하며, 적절한 수준의 결합도는 필요하다.
  3. 결합도를 낮추기 위한 리팩토링은 신중하게 계획되어야 한다.

권장사항

  1. 인터페이스를 통한 계약을 정의하고 구현을 숨긴다.
  2. 의존성 주입을 활용하여 모듈 간 결합도를 낮춘다.
  3. 단방향 의존성을 유지하고 순환 의존성을 피한다.
  4. 공통 결합도를 피하기 위해 전역 상태 사용을 최소화한다.
  5. 정기적인 의존성 분석을 통해 결합도를 모니터링한다.

참고 및 출처