
모듈연관성 없음: 결합도가 낮으면 각 모듈이 독립적으로 동작할 수 있다. 다른 모듈의 내부 로직을 몰라도 인터페이스만 알면 사용 가능하다.
인터페이스 의존성 : 내부 구현이 아닌 정의된 인터페이스(입출력 규약)만 사용되 연결된다. 덕분에 수정시 다른 모듈에 영향이 거의 없다.
복잡성 감소 : 모듈 간 얽힘이 줄어드니 시스템 전체 구조가 단순해진다. 테스트 수정,확장이 쉬워진다.
파급효과 최소화 : 한 모듈을 변경해도 다른 모듈에 영향이 거의 없다. 즉, 유지보수 시 리스크가 적다.
결합도의 유형은 내용>공통>외부>제어>스탬프>자료로 결합도가 낮아진다.
정의
예시(나쁨)
class OrderRepository {
List<Order> data = new ArrayList<>(); // 내부 컬렉션을 외부에 노출 (package/private로 가정)
}
class ReportService {
void make() {
OrderRepository repo = new OrderRepository();
repo.data.sort(...); // 내부 컬렉션을 직접 조작 → 내부 변경에 취약
}
}
문제점
개선
class OrderRepository {
public List<Order> findAllSortedBy(...){ ... } // 내부 숨기기
}
정의
예시(나쁨)
class Globals { public static String CURRENCY = "KRW"; }
class A { void foo(){ if (Globals.CURRENCY.equals("USD")) ... } }
class B { void bar(){ Globals.CURRENCY = "EUR"; } } // A의 동작이 갑자기 바뀜
문제점
개선
정의
예시(나쁨)
// 외부 API가 "yyyy-MM-dd" 형식 문자열을 강제
interface ExternalApi { String getDate(); }
class Client {
LocalDate fetch() {
String s = api.getDate(); // 외부 규격에 의존
return LocalDate.parse(s, DateTimeFormatter.ISO_DATE);
}
}
문제점
개선
정의
예시(나쁨)
void render(boolean asPdf) { // 제어 플래그
if (asPdf) renderPdf(); else renderHtml();
}
문제점
개선
interface Renderer { void render(); }
class PdfRenderer implements Renderer { ... }
class HtmlRenderer implements Renderer { ... }
// 호출자는 적절한 구현체만 선택
정의
예시(나쁨)
class Order { Long id; Customer customer; List<Item> items; ... }
void notifyShipping(Order order) { // 사실 필요한 건 id, 주소뿐
send(order.customer.getAddress(), order.getId());
}
문제점
개선
record ShippingInfo(Long orderId, Address address){}
void notifyShipping(ShippingInfo info){ ... }
정의
Money convert(Money amount, Currency to) { ... } // 부작용 없음, 필요한 데이터만
장점
전역(static) 상태를 읽거나 바꾸는가? → 공통 결합
boolean/enum 플래그로 분기시키는가? → 제어 결합
거대 엔티티/DTO를 통째로 넘기는가? → 스탬프 결합
외부 포맷/프로토콜이 코드 전반에 퍼졌는가? → 외부 결합 → 어댑터로 격리
private 자료구조를 외부가 건드리나? → 내용 결합 → 캡슐화
이런 조건들을 숙지하고 있으면 실무에서 결합도를 의식하며 더 견고한 코드를 작성할 수 있다.