프로그램을 여러 모듈로 나누는 이유는 하나다. 한 번에 한 조각씩 이해하고 고치기 위해서다. 그런데 나누는 방식이 나쁘면 조각을 나눈 의미가 없다. 한 곳을 고치면 엉뚱한 곳이 터지고, 한 모듈만 떼어 보려 해도 딸려오는 게 너무 많다. 이 “잘 나눴는가”를 재는 두 잣대가 결합도와 응집도다. 소프트웨어공학의 오래된 원칙 한 줄로 요약하면 낮은 결합도(low coupling), 높은 응집도(high cohesion)를 지향한다.
결합도 — 모듈 사이가 얼마나 얽혀 있나
결합도(coupling)는 서로 다른 모듈이 얼마나 강하게 의존하는지를 잰다. 낮을수록 좋다. 한 모듈을 고쳐도 다른 모듈이 흔들리지 않아야 독립적으로 수정·재사용·테스트할 수 있기 때문이다.
결합도는 약한 것에서 강한 것 순으로 대략 이렇게 나뉜다.
- 자료 결합(data): 필요한 값만 매개변수로 주고받는다. 가장 느슨하고 이상적이다.
- 스탬프 결합(stamp): 구조체·객체를 통째로 넘긴다. 실제로 쓰는 필드는 일부인데 전체를 알게 된다.
- 제어 결합(control): 플래그를 넘겨 상대 모듈의 내부 흐름을 조종한다.
render(true)처럼 호출부가 상대 로직을 알아야 한다. - 공통 결합(common): 여러 모듈이 하나의 전역 데이터를 공유한다. 누가 언제 바꾸는지 추적이 어렵다.
- 내용 결합(content): 한 모듈이 다른 모듈의 내부를 직접 건드린다. 가장 강하고 나쁘다.
위로 갈수록 서로 “값”만 주고받고, 아래로 갈수록 상대의 “속사정”까지 알게 된다.
응집도 — 모듈 안이 얼마나 한 가지 일에 집중하나
응집도(cohesion)는 한 모듈 안의 요소들이 얼마나 밀접하게 하나의 목적을 향하는지를 잰다. 높을수록 좋다. 모듈이 “한 가지 일”만 하면 이름 붙이기 쉽고, 어디를 고쳐야 할지 분명하다.
응집도는 강한 것에서 약한 것 순으로 이렇다.
- 기능적(functional): 단 하나의 잘 정의된 일만 한다. 가장 이상적이다.
- 순차적(sequential): 한 요소의 출력이 다음 요소의 입력이 된다.
- 교환적/통신적(communicational): 같은 데이터를 대상으로 여러 일을 한다.
- 절차적(procedural): 정해진 순서대로 실행되지만 하는 일은 제각각이다.
- 시간적(temporal): “초기화”처럼 특정 시점에 함께 실행될 뿐 서로 관련은 없다.
- 논리적(logical): 비슷해 보이는 기능을 플래그로 묶었다.
- 우연적(coincidental): 아무 관련 없는 것들이 그냥 한데 있다. 가장 나쁘다.
왜 이 방향인가
결합도와 응집도는 사실 한 몸이다. 응집도를 높이면(모듈이 한 가지 일에 집중하면) 자연히 다른 모듈과 주고받을 것이 줄어 결합도가 낮아진다.
// 나쁜 예: 제어 결합 + 논리적 응집
function handle(data, type) {
if (type === "save") { /* 저장 */ }
else if (type === "send") { /* 전송 */ } // 서로 무관한 일이 플래그로 묶임
}
// 좋은 예: 자료 결합 + 기능적 응집
function save(record) { /* 저장만 */ }
function send(message) { /* 전송만 */ }
아래처럼 나누면 각 함수는 한 가지 일만 하고(높은 응집), 필요한 값만 받는다(낮은 결합). 그래서 따로 테스트하고 따로 고칠 수 있다. 시험에서는 결합도·응집도의 단계 순서와 “낮은 결합·높은 응집이 목표”라는 방향을 묻는 문제가 자주 나오니, 순서를 강한 쪽/약한 쪽으로 외워두면 든든하다.
이 글에 대한 의견은 아래 댓글로 남겨주세요 (GitHub 계정 필요). 로그인 없이 남기고 싶다면