코드는 분명 돌아가는데, 기능 하나 고치려고 파일을 열면 왜 이렇게 여기저기 손봐야 할까요.
버튼 색 하나 바꿨는데 결제 로직이 터지는 경험, 다들 한 번쯤 있으시죠.
그 원인을 딱 두 단어로 설명하는 개념이 바로 결합도와 응집도입니다.
결합도는 낮을수록 좋고, 응집도는 높을수록 좋습니다. 좋은 설계의 8할이 이 한 문장에서 출발해요.
이 글에서는 두 개념이 정확히 뭔지, 왜 소프트웨어 설계 원칙의 뿌리로 불리는지, 실제 코드에 어떻게 적용하는지까지 제 경험을 얹어 풀어볼게요.
핵심부터 먼저 정리하고 갈게요.
- 결합도(Coupling): 모듈끼리 얼마나 얽혀 있나 → 낮을수록 좋음
- 응집도(Cohesion): 한 모듈 안의 코드가 얼마나 한 가지 일에 집중하나 → 높을수록 좋음
- 이 두 가지가 SOLID, 디자인 패턴 같은 유명한 원칙들의 밑바탕입니다
- 목표는 결국 하나, “고치기 쉽고 갈아끼우기 쉬운 코드”
결합도와 응집도, 도대체 뭔가요?
두 개념을 한 문장으로 먼저 정리하면 이렇습니다.
결합도는 모듈과 모듈 사이의 관계를 말하고, 응집도는 한 모듈 안의 내부 결속을 말해요.
방향이 정반대라 헷갈리기 쉬운데, 저는 이렇게 외웠어요.
결합도는 “바깥과의 거리”, 응집도는 “안쪽의 뭉침”이라고요.
결합도가 높으면 A를 고칠 때 B, C까지 줄줄이 딸려옵니다. 도미노처럼요.
응집도가 낮으면 한 클래스 안에 결제 로직, 이메일 발송, 로그 기록이 뒤섞여 있어요. 이름만 봐서는 뭐 하는 클래스인지 알 수가 없죠.
좋은 설계는 이 둘을 반대로 밀어붙입니다. 밖과는 느슨하게, 안은 단단하게요.
왜 이게 설계 원칙의 ’뿌리’일까요?
결합도와 응집도는 1970년대 구조적 설계 이론에서 처음 정리된 개념이에요. 래리 콘스탄틴이 제안한 걸로 알려져 있죠.
반세기 가까이 지났는데도 여전히 통하는 이유는, 이게 특정 언어나 유행을 타지 않는 본질이기 때문이에요.
우리가 아는 유명한 원칙들을 뜯어보면 결국 여기로 수렴합니다.
| 원칙·패턴 | 결국 말하려는 것 |
|---|---|
| 단일 책임 원칙(SRP) | 응집도를 높여라 |
| 의존성 역전(DIP) | 결합도를 낮춰라 |
| 인터페이스 분리(ISP) | 불필요한 결합을 끊어라 |
| 대부분의 디자인 패턴 | 낮은 결합 + 높은 응집 |
보이시죠. 이름은 달라도 뿌리는 하나예요.
그래서 저는 새 원칙이나 패턴을 배울 때, “이건 결합도를 낮추려는 거야, 응집도를 높이려는 거야?“부터 물어봐요. 그러면 절반은 이해가 됩니다.
결합도 낮추는 방법, 코드로 보면?
말로만 하면 막연하니 짧은 예시를 볼게요.
아래는 결합도가 높은 코드예요. 주문 클래스가 특정 결제 회사를 직접 알고 있습니다.
class Order {
let pay = KakaoPay() // 카카오페이에 딱 붙어버림
func checkout() {
pay.send() // 다른 결제로 바꾸려면 여기를 뜯어야 함
}
}
결제사를 토스로 바꾸는 순간 Order를 열어서 수정해야 하죠. 이게 높은 결합도의 전형입니다.
이번엔 프로토콜로 사이를 벌려볼게요.
class Order {
let pay: Payment // '결제'라는 약속에만 의존
init(pay: Payment) { self.pay = pay }
func checkout() { pay.send() } // 무엇이 오든 상관 없음
}
이제 Order는 어떤 결제사가 오든 신경 쓰지 않아요. 갈아끼우기만 하면 됩니다.
제가 실무에서 이렇게 바꿔놓고 나니, 결제사 추가 요청이 와도 기존 코드를 거의 건드리지 않게 되더라고요. 이게 낮은 결합도의 힘이에요.
응집도 높이는 건 어떻게 하나요?
응집도는 “이 모듈이 딱 한 가지 일만 하는가”로 판단해요.
제 기준은 단순해요. 클래스 이름을 말했을 때, 그 안의 메서드들이 전부 자연스럽게 떠오르면 응집도가 높은 거예요.
예를 들어 UserService 안에 이런 게 섞여 있으면 위험 신호입니다.
- 회원가입 처리 (본업)
- 마케팅 이메일 발송 (남의 일)
- CSV 리포트 생성 (또 남의 일)
서로 상관없는 일들이 한 집에 세 들어 사는 셈이죠.
이럴 땐 이메일은 EmailSender로, 리포트는 ReportGenerator로 각자 방을 내주면 됩니다.
그러면 나중에 이메일 로직을 고칠 때 UserService를 열 일이 없어져요. 변경의 파장이 작아지는 거죠.
낮은 결합도와 높은 응집도는 결국 한 방향을 봅니다. 변경이 퍼지지 않게 막는 것.
자주 묻는 질문 (Q&A)
Q. 결합도와 응집도, 뭐부터 신경 써야 하나요?
저는 응집도부터 잡으라고 권해요. 한 모듈이 한 가지 일에 집중하도록 쪼개다 보면, 자연스럽게 결합도도 정리되는 경우가 많거든요.
Q. 결합도는 무조건 0이 좋은가요?
아니에요. 모듈끼리 아예 안 엮이면 프로그램이 돌아가질 않죠. 목표는 제로가 아니라 “꼭 필요한 만큼만, 느슨하게”입니다.
Q. 처음부터 완벽하게 설계해야 하나요?
그럴 필요 없어요. 저도 일단 동작하게 만든 뒤, 고치기 아플 때마다 조금씩 결합을 끊고 응집을 모아요. 리팩터링은 원래 반복하는 겁니다.
결합도와 응집도는 화려한 신기술은 아니지만, 오래 살아남는 코드를 쓰는 사람들의 공통 습관이에요.
오늘 짠 코드에서 “이거 고치면 어디까지 영향 갈까?“를 한 번만 떠올려 보세요. 그 질문이 쌓이면 설계 감각이 확 자랍니다. 응원할게요!
