Coupling and Cohesion
Coupling 과 Cohesion 은 소프트웨어 아키텍처 설계에서 모듈 간의 의존성과 모듈 내부 요소들의 응집도를 평가하여 시스템의 품질을 향상시키는 핵심 개념이다. 결합도는 모듈 간 상호 의존성의 강도를 나타내며, 낮을수록 독립적이고 변경에 강한 구조가 된다. 응집도는 모듈 내부 구성요소들이 얼마나 밀접하게 관련되어 있는지를 나타내며, 높을수록 명확한 책임과 목적을 가진 모듈이 된다. 낮은 Coupling 과 높은 Cohesion 은 시스템의 유지보수성, 확장성, 재사용성을 높이며, 이러한 원칙은 모듈화, 정보 은닉, 단일 책임 원칙 등과 밀접하게 연관되어 있다. 현대의 마이크로서비스, 클린 아키텍처 등 모든 아키텍처 패턴의 이론적 기반이 된다.
핵심 개념
- 결합도 (Coupling): 소프트웨어 모듈 또는 컴포넌트 간의 상호 의존성 정도를 나타내며, 낮은 결합도 (Loose Coupling) 가 바람직하다. 결합도가 높으면 (High Coupling) 한 모듈의 변경이 다른 모듈에 영향을 주기 쉽고, 유지보수가 어려워진다.
- 응집도 (Cohesion): 모듈 내부의 구성요소 (함수, 클래스 등) 가 얼마나 밀접하게 관련되어 하나의 목적을 위해 동작하는지를 나타낸다. 높은 응집도 (High Cohesion) 는 모듈이 명확한 책임과 목적을 갖고 있어 이해, 유지보수, 재사용이 용이하다.
항목 | 커플링 (Coupling) | 응집도 (Cohesion) |
---|---|---|
정의 | 모듈 간 의존성 | 모듈 내부 요소 간 연관성 |
목표 | 낮은 결합도 (Loose Coupling) | 높은 응집도 (High Cohesion) |
주요 포인트 | 독립성 확보, 변경 전파 최소화 | 책임 명확화, 유지보수성 향상 |
이상적 상태 | 각 모듈은 외부와 최소한으로만 통신 | 모듈은 한 가지 책임만 가짐 |
영향 | 결합도가 낮을수록 변경 영향 최소화, 유지보수 용이 결합도가 높으면 수정 전파 증가 | 응집도가 높을수록 책임 명확, 코드 이해/재사용 용이 응집도가 낮으면 모듈 기능이 모호 |
유형 | - Content - Common - Control - Stamp - Data - Message - No Coupling | - Coincidental - Logical - Temporal - Procedural - Communicational - Sequential - Functional |
실무 적용 예 | 인터페이스/DI 로 결합도 최소화 | SRP 적용으로 기능 명확히 분리 |
설계 원칙 연계 | DIP, ISP, OCP | SRP, KISS, DRY |
측정 기준 | 인터페이스 수, 의존 객체 수 | 기능 목적의 단일성, 내부 연관성 |
테스트 | 낮을수록 단위 테스트 용이 | 높을수록 단위 테스트 용이 |
- 상호관계: 이상적인 설계는 낮은 결합도와 높은 응집도를 동시에 추구하는 것이다. 높은 응집도는 자연스럽게 결합도를 낮추는 효과가 있다.
배경
커플링과 응집도 개념은 1960 년대 후반 Larry Constantine 이 Structured Design 방법론의 일환으로 개발했다. Stevens, Myers & Constantine (1974) 논문과 Yourdon & Constantine (1979) 저서를 통해 정립되어 소프트웨어 공학의 표준 용어가 되었다.
목적 및 필요성
- 소프트웨어 품질 향상: 유지보수성, 확장성, 안정성 개선
- 복잡성 관리: 시스템의 복잡도를 체계적으로 관리
- 변경 영향 최소화: 한 모듈의 변경이 다른 모듈에 미치는 영향 감소
- 재사용성 증대: 독립적인 모듈을 통한 코드 재사용 촉진
- 테스트 용이성: 독립적인 단위 테스트 가능
특징
- 결합도는 모듈 간, 응집도는 모듈 내부에 초점
- 결합도와 응집도는 서로 보완적이며, 균형이 중요
주요 원리 및 작동 원리
graph TB A[소프트웨어 시스템] --> B[모듈 A] A --> C[모듈 B] A --> D[모듈 C] subgraph "커플링 (모듈 간 관계)" B -.->|낮은 커플링| C B -.->|낮은 커플링| D C -.->|낮은 커플링| D end subgraph "응집도 (모듈 내부)" B --> B1[기능1] B --> B2[기능2] B1 ---|높은 응집도| B2 C --> C1[기능1] C --> C2[기능2] C1 ---|높은 응집도| C2 end
낮은 결합도 (Low Coupling) 와 높은 응집도 (High Cohesion) 를 달성하기 위한 핵심 원칙
원칙 | 설명 | 적용 대상 | 관련 품질 속성 |
---|---|---|---|
SRP (Single Responsibility Principle) | 하나의 모듈은 하나의 책임만을 가져야 함 | 클래스, 함수, 서비스 | 응집도 향상 |
DIP (Dependency Inversion Principle) | 고수준 모듈이 저수준 모듈에 의존하지 않도록 추상화에 의존 | 계층 간 통신, 모듈 간 의존성 | 결합도 감소 |
ISP (Interface Segregation Principle) | 클라이언트가 사용하지 않는 인터페이스에 의존하지 않아야 함 | 인터페이스 설계 | 결합도 감소, 응집도 향상 |
OCP (Open/Closed Principle) | 기능 확장은 열려 있고 변경에는 닫혀 있어야 함 | 모듈, 클래스 구조 | 변경 최소화, 결합도 감소 |
DRY (Don’t Repeat Yourself) | 중복된 로직을 제거하고 재사용성을 높임 | 함수, 유틸리티, 서비스 | 응집도 향상 |
KISS (Keep It Simple, Stupid) | 단순한 구조와 설계를 유지 | 전반적 아키텍처 | 구조적 응집도 향상 |
모듈화 (Modularity) | 시스템을 작고 독립적인 구성 요소로 분리 | 아키텍처, 프로젝트 구성 | 결합도 감소, 응집도 향상 |
정보 은닉 (Encapsulation) | 객체 내부 구현을 외부에 노출하지 않음 | 클래스, 객체 | 결합도 감소 |
낮은 결합도와 높은 응집도를 위한 구현 기법
구현 기법 | 정의 | 구성 요소 | 목적 / 효과 | 실제 예시 |
---|---|---|---|---|
인터페이스 기반 설계 (Interface-Based Design) | 구현체가 아닌 추상화에 의존하여 설계 | - 인터페이스 - 다형성 구현체 - 팩토리 또는 DI | - 결합도 감소 - 확장성 향상 | - PaymentProcessor 인터페이스Java interface , Python ABC |
의존성 주입 (DI) (Dependency Injection) | 필요한 의존성을 외부에서 주입 | - 인터페이스 - 구현체 - 주입 방식 (생성자, 세터 등) DI 컨테이너 | - 강한 결합 해소 - 테스트 용이 | - Spring Framework, NestJS, FastAPI DI |
모듈화 / 캡슐화 (Modularization / Encapsulation) | 시스템을 기능 단위 모듈로 분리하고 내부 구현은 숨김 | - 기능별 디렉터리 구조 - 네임스페이스 - 접근 제어자 | - 응집도 향상 - 내부 변경 영향 최소화 | - Python 패키지, Java 모듈, npm 패키지 |
단일 책임 분리 (Single Responsibility Principle) | 클래스나 모듈이 하나의 책임만 가지도록 분리 | - 책임 단위 기준 수립 - 역할 기반 분리 | - 높은 응집도 확보 - 유지보수 용이성 증가 | - OrderService vs PaymentService 분리 |
이벤트 기반 아키텍처 (Event-Driven Architecture) | 이벤트를 중심으로 모듈 간 비동기 통신 구현 | - Publisher / Subscriber Event Bus / Queue - 메시지 포맷 | - 느슨한 결합 - 확장성과 병렬 처리 가능 | - Kafka, RabbitMQ, Custom EventBus |
리팩토링 (Refactoring) | 기능은 유지하면서 코드 구조를 개선하는 과정 | - 코드 스멜 탐지 - 책임 이동 - 중복 제거 | - 응집도 증가 - 결합도 감소 - 유지보수성 향상 | - Extract Method, Replace Inheritance with Composition |
계층 분리 (Layered Architecture) | 기능을 계층으로 나눠 책임 분리 | - Presentation Layer Application Layer Domain Layer Infrastructure Layer | - 응집도 증가 - 결합도 감소 및 관심사 분리 | - MVC, Clean Architecture, Onion Architecture |
분류에 따른 종류 및 유형
커플링 유형
유형 | 정의 | 특징 | 영향 | 실무 고려사항 | 권장 여부 | 예시 |
---|---|---|---|---|---|---|
Content Coupling(내용 결합) | 한 모듈이 다른 모듈의 내부 구현 (변수, 로직 등) 에 직접 접근 | 가장 강한 결합내부 변경 시 외부 영향 큼 | 유지보수성 낮음디버깅 어려움 | 모듈 간 캡슐화 원칙 위배로 인해 위험 | ❌ 피해야 함 | 모듈 A 가 모듈 B 의 내부 변수에 직접 접근 |
Common Coupling(공통 결합) | 여러 모듈이 동일한 전역 변수나 공유 자원 사용 | 모듈 간 간접 연결전역 상태 공유 | 사이드 이펙트 발생 가능성 높음 | 테스트 어려움, 상태 동기화 문제 | ❌ 피하는 것이 바람직 | 여러 모듈이 동일한 전역 변수 사용 |
External Coupling(외부 결합) | 외부 환경이나 프로토콜에 종속 | 파일 포맷, 외부 API, 디바이스 종속성 존재 | 외부 변경에 의한 영향 큼 | 외부 API/파일 포맷에 의존할 경우 버전 관리 필수 | ⚠ 통제 불가 외부 조건 시 불가피 | 특정 파일 형식, 통신 프로토콜 |
Control Coupling(제어 결합) | 호출자가 호출 대상에게 제어 플래그 전달 | 제어 흐름이 호출자에 의해 결정됨 | 흐름 변경에 의존, 유연성 낮음 | 책임이 불명확, 조건 분기 로직 증가 | ⚠ 필요 최소한으로 제한 | 함수에 동작 방식을 결정하는 플래그 전달 |
Stamp Coupling(스탬프 결합) | 복합 구조체 (객체, DTO 등) 를 전달하지만 일부 필드만 사용 | 필요한 필드 외에도 전체 구조 전달 | 인터페이스 불명확, 재사용성 낮음 | 명확한 인터페이스 정의로 해결 가능 | ⚠ 최소화 권장 | 함수에 전체 객체를 전달하지만 일부 필드만 사용 |
Data Coupling(데이터 결합) | 간단한 데이터 (값, 기본 타입) 만 전달 | 가장 약한 결합모듈 독립성 유지 | 유연성 높고 재사용 쉬움 | 인터페이스 명확, 유닛 테스트 용이 | ✅ 적극 권장 | 기본 데이터 타입만 매개변수로 전달 |
시나리오 기반 예시
시나리오: 주문 처리 시스템
결합도 유형별 문제점 및 리팩토링 요약
- 내용 결합: 내부 구현 의존 → 정보 은닉, 인터페이스 활용
- 공통 결합: 전역 데이터 의존 → 의존성 주입, 모듈화
- 외부 결합: 외부 포맷/환경 의존 → 추상화, 인터페이스 분리
- 제어 결합: 제어 플래그 의존 → 함수 분리, 단일 책임 원칙 적용
- 스탬프 결합: 불필요한 데이터 전달 → 필요한 데이터만 전달
- 데이터 결합: 필요한 데이터만 전달 → 결합도 최소, 최적 설계
내용 결합 (Content Coupling)
문제 코드 (Python):
문제점:
order.py
가Inventory
클래스의 내부 구현에 직접 접근하여 수정하므로,Inventory
클래스의 내부 구조가 변경되면order.py
도 함께 수정해야 함
리팩토링 방안:
Inventory
클래스에decrease_stock
메서드를 추가하여 외부에서 직접 내부 데이터를 수정하지 않도록 함
개선 코드 (Python):
|
|
공통 결합 (Common Coupling)
문제 코드 (Python):
문제점:
GRAVITY
라는 전역 변수를 여러 모듈에서 공유하므로,GRAVITY
의 값이 변경되면 이를 사용하는 모든 모듈에 영향을 미침
리팩토링 방안:
- 전역 변수 대신 상수를 클래스나 함수 내부로 캡슐화하여 사용
개선 코드 (Python):
외부 결합 (External Coupling)
문제 코드 (Python):
문제점:
read_data
함수가 특정 파일 형식에 의존하므로, 다른 형식의 데이터를 읽으려면 함수 자체를 수정해야 함
리팩토링 방안:
- 데이터 읽기 기능을 인터페이스로 추상화하고, 다양한 파일 형식에 대한 구체적인 구현을 별도로 제공
개선 코드 (Python):
|
|
제어 결합 (Control Coupling)
문제 코드 (Python):
문제점:
process_data
함수가mode
플래그에 따라 다양한 처리를 하므로, 새로운 형식이 추가될 때마다 함수가 복잡해짐
리팩토링 방안:
- 전략 패턴을 사용하여 각 형식에 대한 처리를 별도의 클래스로 분리
개선 코드 (Python):
|
|
스탬프 결합 (Stamp Coupling)
문제 코드 (Python):
문제점:
print_order_summary
함수가Order
객체 전체를 전달받지만, 실제로는id
만 사용하므로 불필요한 데이터가 전달됨
리팩토링 방안:
- 필요한 데이터만 매개변수로 전달하여 데이터 결합 수준으로 낮춤
개선 코드 (Python):
데이터 결합 (Data Coupling)
예시 코드 (Python):
특징:
- 함수가 필요한 데이터만 매개변수로 전달받아 처리하므로, 모듈 간의 결합도가 낮고 재사용성이 높음
응집도 유형
응집도 유형 | 설명 | 특징 | 예시 |
---|---|---|---|
우연적 응집 (Coincidental Cohesion) | 관련 없는 기능들이 우연히 하나의 모듈에 포함됨 | 가장 낮은 응집도, 유지보수 어려움 | 다양한 유틸리티 함수들이 하나의 클래스에 모여 있음 |
논리적 응집 (Logical Cohesion) | 유사한 기능들이 하나의 모듈에 포함되지만, 각 기능이 독립적으로 작동 | 기능 간 연관성 약함 | 입력 처리 모듈이 키보드, 마우스, 터치 입력을 모두 처리 |
시간적 응집 (Temporal Cohesion) | 동시에 실행되는 작업들이 하나의 모듈에 포함됨 | 실행 시점이 동일하지만 기능 간 연관성 약함 | 시스템 초기화 시 실행되는 설정 로딩, 로그 초기화 등 |
절차적 응집 (Procedural Cohesion) | 특정 순서로 실행되는 작업들이 하나의 모듈에 포함됨 | 순서에 의존, 데이터 공유는 없음 | 파일 열기 → 데이터 읽기 → 데이터 처리 |
통신적 응집 (Communicational Cohesion) | 동일한 데이터를 사용하는 작업들이 하나의 모듈에 포함됨 | 데이터 중심, 기능 간 연관성 있음 | 고객 정보 조회, 수정, 삭제 기능이 하나의 모듈에 포함됨 |
순차적 응집 (Sequential Cohesion) | 한 작업의 출력이 다음 작업의 입력이 되는 작업들이 하나의 모듈에 포함됨 | 데이터 흐름 중심, 기능 간 강한 연관성 | 데이터 수집 → 데이터 정제 → 데이터 저장 |
기능적 응집 (Functional Cohesion) | 모듈 내 모든 요소가 단일 기능 수행에 기여 | 가장 높은 응집도, 이상적 구조 | 수학 계산 모듈이 덧셈, 뺄셈, 곱셈, 나눗셈을 수행 |
시나리오 기반 예시
시나리오: 사용자 데이터 처리
우연적 응집 (Coincidental Cohesion)
문제 코드 (Python):
문제점:
- 서로 관련 없는 기능들이 하나의 클래스에 포함되어 있어 응집도가 낮음
리팩토링 방법:
- 각 기능을 별도의 클래스로 분리하여 응집도를 높임
논리적 응집 (Logical Cohesion)
문제 코드 (Python):
문제점:
- 입력 방식별로 기능이 다르지만 하나의 클래스에 포함되어 있어 응집도가 낮음
리팩토링 방법:
- 입력 방식별로 클래스를 분리하여 응집도를 높임
시간적 응집 (Temporal Cohesion)
문제 코드 (Python):
문제점:
- 시스템 초기화 시 동시에 실행되지만 기능 간 연관성이 약함
리팩토링 방법:
- 각 기능을 별도의 클래스로 분리하고, 초기화 순서를 관리하는 클래스를 추가
|
|
절차적 응집 (Procedural Cohesion)
문제 코드 (Python):
문제점:
- 작업 순서에 의존하며, 각 기능이 독립적으로 재사용되기 어려움
리팩토링 방법:
- 각 기능을 별도의 클래스로 분리하고, 프로세스 관리 클래스를 추가
|
|
통신적 응집 (Communicational Cohesion)
문제 코드 (Python):
|
|
문제점:
- 동일한 데이터를 다루지만, 기능별로 분리되어 있지 않아 테스트 및 유지보수가 어려움
리팩토링 방법:
- 각 기능을 명확하게 분리하고, 공통 데이터 접근을 위한 클래스를 추가
|
|
|
|
- Before: 단일 클래스가 조회/수정/삭제 책임을 모두 가짐 → 확장성 및 테스트 어려움
- After:
Repository
는 데이터 접근만,Service
는 비즈니스 로직만 → 통신적 응집 구조 유지 + SRP 충족
순차적 응집 (Sequential Cohesion)
문제 코드 (Python):
문제점:
- 각 단계가 순차적으로 실행되며, 중간 결과를 외부에서 활용하기 어려움
리팩토링 방법:
- 각 단계를 별도의 클래스로 분리하고, 파이프라인 관리 클래스를 추가
|
|
기능적 응집 (Functional Cohesion)
문제 코드 (Python):
문제점:
- 모든 메서드가 수학 연산이라는 단일 기능을 수행하므로 응집도가 높음
리팩토링 방법:
- 필요에 따라 연산별로 클래스를 분리하거나, 현재 구조를 유지
장점과 단점
구분 | 항목 | 설명 |
---|---|---|
✅ 장점 | 유지보수성 향상 | 모듈 간 독립성으로 인해 변경 영향 범위 최소화 |
재사용성 증대 | 독립적인 모듈을 다른 프로젝트에서 재사용 가능 | |
테스트 용이성 | 각 모듈을 독립적으로 단위 테스트 가능 | |
확장성 | 새로운 기능 추가 시 기존 코드 영향 최소화 | |
병렬 개발 | 팀원들이 독립적인 모듈을 동시에 개발 가능 | |
가독성 향상 | 명확한 책임 분리로 코드 이해 용이 | |
⚠️ 단점 | 초기 복잡도 증가 | 설계 단계에서 더 많은 시간과 노력 필요 |
성능 오버헤드 | 모듈 간 통신으로 인한 런타임 비용 | |
과도한 추상화 | 불필요한 인터페이스로 인한 복잡성 증가 | |
일관성 관리 어려움 | 분산된 모듈 간 데이터 일관성 유지 복잡 | |
디버깅 복잡성 | 모듈 간 상호작용 추적의 어려움 |
도전 과제 및 해결책
도전 과제 | 설명 | 해결책 | 상세 전략 / 기술 |
---|---|---|---|
모듈 간 강한 결합 | 하나의 모듈 변경이 다른 모듈에 파급 영향 | 의존성 주입 (DI), 인터페이스 분리 | DIP, IoC 컨테이너, 인터페이스 기반 설계 |
낮은 응집도 | 모듈 내 기능 간 연관성이 낮아 코드가 분산됨 | 단일 책임 원칙 (SRP), 기능 단위 모듈화 | SRP, 기능 중심 디렉터리 구조 |
테스트 어려움 | 결합도 높고 응집도 낮아 단위 테스트 불가 | 모듈 분리, 의존성 주입, Mock 객체 활용 | TDD, 테스트 더블 (Mock, Stub), pytest, unittest |
변경 전파 위험 | 한 곳 변경 시 연쇄적 영향 발생 | 모듈 경계 명확화, 계층 아키텍처 적용 | 계층적 설계 (Presentation-Application-Domain-Infrastructure) |
과도한 추상화 | 불필요하게 복잡한 계층, 인터페이스 설계 | 변경 요구에 맞춘 점진적 리팩토링 | 실제 요구 중심 추상화, YAGNI 원칙 적용 |
추상화 부족 | 구체 구현에 의존 → 테스트 및 변경 어려움 | 인터페이스 도입, 전략 패턴 활용 | 인터페이스, 팩토리 패턴, OCP 적용 |
성능 vs 유연성 | 느슨한 결합은 성능 저하 유발 가능 | 병목 지점 파악 후 선택적 최적화 | 프로파일링 도구, 캐싱, CQRS 분리 |
분산 시스템 일관성 | 마이크로서비스 간 데이터 일관성 유지 어려움 | 이벤트 소싱, Saga, CQRS 패턴 | Kafka, EventBridge, Outbox 패턴 |
인터페이스 변경 위험 | 인터페이스 변경 시 다른 팀/모듈에 영향 | 계약 테스트, API 버전 관리 | Pact, Swagger, OpenAPI, SemVer |
모듈 과도 분리 | 너무 많은 모듈로 인한 복잡도 증가 | 유사 책임 통합, 도메인 중심 모듈화 | DDD, Bounded Context 설계 |
협업 및 코드 일관성 | 다양한 설계 스타일, 코드 리뷰 난이도 | 코드 리뷰 가이드, 자동화 도구 | SonarQube, ESLint, Black, Prettier |
지속적 품질 유지 | 시간 지남에 따라 설계 원칙 붕괴 위험 | 주기적 리팩토링 및 리뷰 프로세스 | 리팩토링 스프린트, 코드 헬스 체크 |
도메인과 설계 간 괴리 | 기술 중심 구조로 도메인 이해도 저하 | 도메인 모델 중심 아키텍처 설계 | DDD (Domain-Driven Design) |
실무 적용 예시
분야 | 적용 사례 | 커플링 감소 방법 | 응집도 향상 방법 |
---|---|---|---|
웹 애플리케이션 | MVC 아키텍처 | Controller-Service-Repository 계층 분리 | 각 계층별 단일 책임 할당 |
마이크로서비스 | 서비스 분해 | API Gateway, 이벤트 스트리밍 | 도메인별 서비스 분할 |
모바일 앱 | MVVM 패턴 | 데이터 바인딩, 옵저버 패턴 | View 별 ViewModel 분리 |
라이브러리 설계 | 플러그인 시스템 | 인터페이스 기반 확장 | 기능별 플러그인 모듈화 |
데이터베이스 | ORM 계층 | Repository 패턴 | 엔티티별 Repository 분리 |
활용 사례
사례 1: 전자상거래 플랫폼
시나리오: 대규모 전자상거래 플랫폼의 마이크로서비스 아키텍처 설계
시스템 구성:
graph TB subgraph "Frontend" Web[웹 앱] Mobile[모바일 앱] end subgraph "API Gateway" Gateway[API Gateway] end subgraph "Core Services" User[사용자 서비스] Product[상품 서비스] Order[주문 서비스] Payment[결제 서비스] Inventory[재고 서비스] end subgraph "Supporting Services" Notification[알림 서비스] Analytics[분석 서비스] Search[검색 서비스] end subgraph "Data Layer" EventStore[(이벤트 스토어)] UserDB[(사용자 DB)] ProductDB[(상품 DB)] OrderDB[(주문 DB)] end Web --> Gateway Mobile --> Gateway Gateway --> User Gateway --> Product Gateway --> Order Gateway --> Payment Order -.->|이벤트| EventStore Payment -.->|이벤트| EventStore Inventory -.->|이벤트| EventStore EventStore -.-> Notification EventStore -.-> Analytics User --> UserDB Product --> ProductDB Order --> OrderDB
워크플로우:
- 주문 생성: 고객이 주문 생성 → Order Service 가 주문 정보 저장
- 재고 확인: Order Service 가 ‘ORDER_CREATED’ 이벤트 발행 → Inventory Service 가 재고 확인
- 결제 처리: 재고 확인 완료 후 ‘INVENTORY_RESERVED’ 이벤트 → Payment Service 가 결제 처리
- 완료 처리: 결제 완료 후 ‘PAYMENT_COMPLETED’ 이벤트 → Order Service 가 주문 상태 업데이트
- 알림 발송: 각 단계별 이벤트 → Notification Service 가 고객에게 알림 발송
각 서비스의 역할:
- 높은 응집도: 각 서비스는 단일 도메인에 집중 (주문, 결제, 재고 등)
- 낮은 커플링: 서비스 간 직접 호출 대신 이벤트를 통한 비동기 통신
- 독립 배포: 각 서비스는 독립적으로 배포 및 확장 가능
실무에서 효과적으로 적용하기 위한 고려사항 및 주의할 점
고려사항 | 설명 | 권장사항 / 적용 전략 |
---|---|---|
기능별 모듈화 | 관련된 기능을 하나의 모듈로 묶어 높은 응집도 유지 | 단일 책임 원칙 (SRP), DDD 의 Bounded Context 기반 기능 분리 |
모듈 간 명확한 경계 | 모듈 간 책임과 범위를 명확히 하여 불필요한 의존 제거 | 모듈 인터페이스 설계 명확화, 외부 접근 제한 (private , internal , 패키지 분리 등) |
인터페이스 기반 설계 | 구현에 의존하지 않고 인터페이스에 의존하게 하여 결합도 완화 | Interface Segregation Principle(ISP), DIP 적용 |
의존성 관리 | 의존 대상 변경이 전체 시스템에 영향을 주지 않도록 관리 | 의존성 주입 (DI), 팩토리/어댑터 패턴, IoC 컨테이너 활용 |
계층 아키텍처 적용 | 관심사를 분리하여 설계의 응집도 확보 + 결합도 완화 | Presentation / Application / Domain / Infrastructure 계층 구분 |
테스트 가능성 확보 | 모듈이 독립적으로 테스트 가능한 구조로 설계 | 의존성 주입을 통한 Mock 대체 가능 구조, 유닛 테스트 우선 |
API 계약 명세화 | 모듈 간 API 및 데이터 계약이 변경되었을 때 오류 방지 | Swagger, OpenAPI, Pact (Contract Testing) 활용 |
레거시 시스템 대응 | 기존 모놀리식 시스템에서 점진적 개선이 가능하도록 적용 | 리팩토링 시 우선순위 지정, 모듈 단위 개선부터 시작 |
도메인 중심 구조 | 비즈니스 도메인을 중심으로 기능 및 책임 분리 → 설계 응집도 향상 | 도메인 주도 설계 (DDD), Aggregate / Entity / Value Object 구분 |
성능 고려 | 과도한 분리로 인해 발생하는 호출 비용이나 오버헤드에 주의 | 이벤트 기반 아키텍처, CQRS, 성능 병목 파악 후 선택적 최적화 |
개발자 커뮤니케이션 | 인터페이스나 경계 정의가 불명확하면 팀 간 협업 시 충돌 발생 | 공유 문서화, API 설계 리뷰, 도메인 공유 세션 운영 |
리팩토링 주기화 | 시간이 지나며 응집도는 낮아지고 결합도는 높아지는 구조적 부패 방지 | SonarQube, 코드 리뷰, 정적 분석, 리팩토링 스프린트 설정 |
결합도 감소 & 응집도 증가를 위한 리팩토링 단계별 가이드
1 단계: 현황 분석
항목 | 설명 |
---|---|
🔍 의존성 맵 작성 | 모듈 간 직접 참조, 순환 참조, 전역 변수 사용 등을 시각화 (Structure101, Dependency Cruiser, SonarQube 등 활용) |
📊 결합도/응집도 진단 | 각 클래스 또는 모듈별 책임의 집중도와 외부 호출 관계 분석 (CodeScene, JDepend 등) |
🔁 변경 이력 확인 | Git 로그/PR 에서 자주 변경되는 파일 조합을 분석 (Hotspot 분석) |
2 단계: 모듈 책임 재정의 및 리패키징
항목 | 설명 |
---|---|
📁 관심사 기준 리패키징 | 기능/도메인 중심으로 패키지나 디렉터리 구조를 재구성 (예: order/ , payment/ ) |
📌 SRP(Single Responsibility Principle) 적용 | 하나의 클래스/모듈이 하나의 책임만 가지도록 분리 |
📦 공통 책임 분리 | Utility, Helper, Validator 등 역할이 중복된 부분을 공용 모듈로 분리 |
✅ 결과 | 응집도 증가 (기능 밀접도↑), 클래스 간 관계 간결화 |
3 단계: 결합도 감소를 위한 의존성 전환
항목 | 설명 |
---|---|
🔁 직접 의존 → 인터페이스 추상화 | 구현체 직접 호출을 인터페이스로 대체하여 DIP 적용 |
🧩 의존성 주입 (DI) 적용 | 생성자, 팩토리, 프레임워크 (Dagger, Spring, NestJS 등) 를 통한 주입 구조 전환 |
✂ 순환 참조 제거 | 두 모듈이 서로를 호출하면 중간 인터페이스 또는 메시징을 도입 |
✅ 결과 | 테스트 용이성 확보, 변경 시 영향 범위 최소화 |
예시 (Python)
4 단계: 상호작용 구조 재설계 (느슨한 결합)
항목 | 설명 |
---|---|
📡 이벤트 기반 구조 도입 | Kafka, RabbitMQ, EventEmitter 등을 사용해 직접 호출 제거 |
🌉 Anti-Corruption Layer | 외부 시스템이나 레거시 모듈과 연결 시 중간 계층 도입 |
🎯 인터페이스 계약 명확화 | DTO(Data Transfer Object), OpenAPI 문서화 등으로 명확한 인터페이스 정의 |
✅ 결과 | 유연성 향상, 독립 배포 가능 구조 확보 |
5 단계: 응집도 극대화를 위한 도메인 중심 재구성
항목 | 설명 |
---|---|
🧠 도메인 모델 도입 | DDD (Domain-Driven Design) 방식으로 도메인마다 모델/서비스 구성 |
📌 Aggregate 중심 책임 통합 | 한 도메인 내에서 밀접한 기능은 하나의 서비스/클래스에서 관리 |
📁 모듈 단위 유틸 제거 | 도메인 외부에서만 사용하는 유틸리티 분리 (DRY 원칙 재점검) |
✅ 결과 | 기능 중심 구조 → 의미 기반 구조로 전환됨 (재사용성과 명확성↑) |
6 단계: 자동화 기반 리팩토링 품질 확보
항목 | 설명 |
---|---|
🧪 테스트 커버리지 강화 | 단위 테스트, 통합 테스트를 통해 변경 영향 최소화 |
🔍 정적 분석 도구 통합 | SonarQube, ESLint, PMD 등으로 구조/복잡도 지속 분석 |
🧪 테스트 기반 리팩토링 (TDD) | 변경 전에 테스트를 작성하여 의도된 동작을 보장 |
✅ 결과 | 리팩토링 품질 확보, 회귀 방지 |
최적화하기 위한 고려사항
고려사항 | 설명 | 권장사항 및 적용 전략 |
---|---|---|
모듈 간 통신 최적화 | 느슨한 결합으로 인한 과도한 원격 호출, 네트워크 비용 발생 | 모듈 간 직접 호출 대신 이벤트 기반 통신, 캐싱, 동기/비동기 조합 설계 |
데이터 일관성 유지 | 느슨한 결합 구조에서 데이터 동기화가 지연되거나 불일치 발생 가능 | 이벤트 소싱 (Event Sourcing), CQRS, Saga 패턴을 통해 일관성 지연 허용 및 복구 가능 구조 설계 |
과도한 추상화 오버헤드 | 계층 분리 또는 설계 유연성을 위해 도입한 추상화가 성능 병목으로 이어질 수 있음 | 필수적 계층만 유지, 경량화된 인터페이스 정의, 의존성 최소화 (특히 실시간 경로) |
모듈 크기 최적화 | 너무 작게 쪼개진 모듈은 호출 비용 증가, 너무 큰 모듈은 응집도 저하 | 도메인 중심으로 모듈 크기를 조정하여 응집도 높이고, 호출 경로 최소화 |
캐시 및 리소스 활용 | 재사용 가능한 계산 결과나 데이터에 대한 반복 요청 발생 시 성능 저하 | 응집도 높은 모듈 단위로 캐싱 적용 (예: 서비스 레벨 캐시, DAO 캐시), Redis/Memcached 활용 |
비동기/배치 처리 설계 | 실시간 처리가 불필요한 작업까지 동기 호출로 처리할 경우 응답 지연 | 모듈 간 결합을 이벤트/메시지 큐로 분리하여 비동기 처리, Kafka, RabbitMQ, AWS SQS 활용 |
DB 접근 최적화 | 응집도 낮은 DAO, Repository 가 무분별한 쿼리 호출 시 N+1 문제 발생 | 배치 로딩 (fetch join), 응집도 높은 단위로 트랜잭션 범위 최소화 설계 |
자원 관리 일관성 | 여러 모듈에서 동일 자원을 관리하며 충돌 발생 가능 | 자원에 대한 책임을 명확히 분리하여 단일 모듈에서 관리하도록 설계 |
장애 전파 방지 | 결합된 모듈 중 하나의 장애가 전체 서비스 중단으로 확산될 수 있음 | Circuit Breaker, Timeout, Fallback 설계 (Resilience4j, Hystrix) |
로깅 및 모니터링 | 성능 병목 지점이나 모듈간 통신 트래픽 파악이 어려운 경우 분석 불가 | 로그 상호연관 ID 설정 (trace ID), APM 도구 (New Relic, Datadog, Jaeger 등) 활용 |
하위 주제별 추가 학습 내용
카테고리 | 주제 | 설명 |
---|---|---|
고급 개념 | Connascence | 커플링의 더 정교한 분류 체계 |
의존성 그래프 분석 | 시스템 의존성 시각화 및 분석 | |
설계 방법론 | 도메인 주도 설계 (DDD) | 비즈니스 도메인 기반 모듈 분할 |
이벤트 스토밍 | 도메인 이벤트 식별 및 서비스 경계 정의 | |
구현 기술 | 의존성 주입 프레임워크 | Spring, Guice, Dagger 등 |
메시지 큐 시스템 | RabbitMQ, Apache Kafka, Amazon SQS | |
측정 및 분석 | 코드 메트릭 | 커플링과 응집도 측정 도구 |
아키텍처 테스트 | ArchUnit 을 통한 아키텍처 규칙 검증 | |
설계 패턴 | 디자인 패턴과 결합도/응집도 | 패턴별 결합도/응집도 최적화 전략 |
주제와 관련하여 추가적으로 학습해야 할 내용
카테고리 | 주제 | 간략한 설명 |
---|---|---|
설계 원칙 | SOLID 원칙 | 객체 지향 설계의 5 가지 원칙으로, 응집도와 결합도에 직접적인 영향을 미침 |
GRASP 패턴 | 객체 지향 설계에서 책임 할당에 대한 9 가지 원칙으로, 응집도와 결합도에 대한 지침을 제공 | |
정보 은닉 (Information Hiding) | 모듈의 내부 구현을 숨기고, 인터페이스를 통해 상호작용하여 결합도를 낮추는 설계 원칙 | |
캡슐화 (Encapsulation) | 데이터와 메서드를 하나의 단위로 묶어, 모듈의 응집도를 높이는 설계 원칙 | |
아키텍처 패턴 | 마이크로서비스, 레이어드 | 결합도/응집도 중심 아키텍처 패턴 |
도구 | 의존성 분석, 코드 시각화 | 결합도/응집도 진단 및 개선 도구 |
테스트 | 단위/통합 테스트 | 결합도/응집도와 테스트 전략 |
보안 | 모듈 경계 보안 | 결합도 완화와 보안성 강화 |
관련 분야별 학습 내용
관련 분야 | 주제 | 설명 |
---|---|---|
클라우드 컴퓨팅 | 서버리스 아키텍처 | Function as a Service 의 결합도 특성 |
컨테이너 오케스트레이션 | Kubernetes 의 마이크로서비스 관리 | |
데이터 엔지니어링 | 데이터 파이프라인 | ETL/ELT 프로세스의 모듈화 |
스트림 처리 | Apache Kafka Streams, Apache Flink | |
보안 | API 보안 | 마이크로서비스 간 인증/인가 |
제로 트러스트 | 분산 시스템 보안 모델 | |
DevOps | CI/CD 파이프라인 | 독립적인 서비스 배포 전략 |
모니터링 | 분산 추적, 로그 집계 |
용어 정리
용어 | 설명 |
---|---|
LCOM (Lack of Cohesion in Methods) | 클래스 메서드 간의 응집도를 측정하는 정량적 메트릭 |
CBO (Coupling Between Objects) | 클래스 간 결합도 수치를 나타내는 메트릭 |
모듈화 (Modularization) | 시스템을 독립적이고 재사용 가능한 구성 요소로 분할하는 설계 기법 |
의존성 주입 (Dependency Injection) | 객체의 의존성을 외부에서 주입받아 결합도를 낮추는 설계 패턴 |
단일 책임 원칙 (Single Responsibility Principle, SRP) | 클래스나 모듈은 하나의 책임만을 가져야 한다는 원칙 |
인터페이스 분리 원칙 (Interface Segregation Principle, ISP) | 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리하는 원칙 |
의존성 역전 원칙 (Dependency Inversion Principle, DIP) | 고수준 모듈이 저수준 모듈이 아닌 추상화에 의존하도록 설계 |
관심사 분리 (Separation of Concerns) | 기능과 책임을 분리하여 각 구성 요소의 응집도를 높이는 설계 원칙 |
정보 은닉 (Information Hiding) | 모듈의 내부 구현 세부사항을 외부에 숨기고, 인터페이스만 노출 |
이벤트 소싱 (Event Sourcing) | 상태 변경을 이벤트로 저장하여, 이를 기반으로 현재 상태를 재구성하는 패턴 |
CQRS (Command Query Responsibility Segregation) | 읽기 (조회) 와 쓰기 (명령) 모델을 명확히 분리하는 설계 방식 |
API 게이트웨이 (API Gateway) | 마이크로서비스 환경에서 클라이언트 요청을 단일 진입점으로 수렴하는 구성 요소 |
참고 및 출처
- GeeksforGeeks - Coupling and Cohesion
- GeeksforGeeks - Coupling and Cohesion in System Design
- Wikipedia - Coupling (computer programming)
- Wikipedia - Cohesion (computer science)
- Loose coupling - Wikipedia
- Interface Segregation Principle - Wikipedia
- Single Responsibility Principle - Wikipedia
- Separation of Concerns - Wikipedia
- Better Software Design With Coupling and Cohesion - OmbuLabs
- Coupling and Cohesion in Software Engineering - Hero Vired
- Coupling and Cohesion - Engati Glossary
- Coupling and Cohesion - Scaler Topics
- The Valuable Dev - Cohesion and Coupling Guide with Examples
- CodeOpinion - SOLID? Nope, just Coupling and Cohesion
- Medium - Difference Between Cohesion and Coupling with Real-Life Example
- LinkedIn - Software Engineering Principle: Coupling & Cohesion
- LinkedIn - The Role of Code Coupling and Cohesion in Developing Applications
- Coupling and Cohesion: The Two Principles for Effective Architecture - ByteByteGo
- GraphAI - Coupling vs Cohesion: 이해와 차이점 정리
- SOA Design Principle: Coupling, Cohesion, and Granularity - francescolelli.info
- Examining Software Coupling and Cohesion Patterns using Social Network Analysis - IJCA (PDF)
- Types of Cohesion and Coupling - Study Material (PDF)