좋은 코드의 비밀, 응집도(Cohesion): LCOM으로 설계 품질 측정하기
좋은 설계의 첫걸음, 응집도란 무엇일까요?
소프트웨어 공학에서 응집도(Cohesion) 는 하나의 모듈(클래스, 컴포넌트 등) 내부의 구성 요소들이 얼마나 서로 밀접하게 관련되어 있는지를 나타내는 척도입니다. 응집도가 높은 모듈 은 명확하게 정의된 단 하나의 책임(Single Responsibility)을 가지며, 이는 코드를 이해하고, 테스트하고, 유지보수하기 쉽게 만듭니다. 반면, 응집도가 낮은 모듈은 여러 관련 없는 기능들이 섞여 있어 ‘스파게티 코드’가 될 가능성이 높습니다.
좋은 소프트웨어 설계는 높은 응집도(High Cohesion) 와 낮은 결합도(Low Coupling) 를 지향합니다. 이 글에서는 응집도의 다양한 유형을 살펴보고, LCOM이라는 메트릭을 통해 코드의 응집도를 어떻게 측정하고 개선할 수 있는지 알아보겠습니다.
응집도의 7가지 유형
응집도는 그 수준에 따라 7가지 유형으로 나눌 수 있습니다. 가장 바람직한 기능적 응집 부터 가장 피해야 할 우연적 응집 까지 순서대로 살펴보겠습니다.
1. 기능적 응집 (Functional Cohesion) - 가장 이상적인 형태
모듈 내의 모든 기능이 단 하나의 목적 을 달성하기 위해 함께 동작하는 경우입니다. 이는 가장 강력하고 이상적인 응집도입니다.
- 예시:
calculateRectangleArea(width, height)함수는 사각형의 넓이를 계산하는 단일 목적에만 집중합니다. 이 함수에 필요한 모든 로직(입력 유효성 검사, 곱셈 연산 등)은 오직 ‘넓이 계산’이라는 목표를 위해 존재합니다.
2. 순차적 응집 (Sequential Cohesion)
한 요소의 출력 데이터가 다음 요소의 입력 데이터로 사용되는, 마치 конвейер 벨트처럼 구성 요소들이 순차적으로 실행 되는 경우입니다.
- 예시: 파일에서 데이터를 읽어와(
readData) → 데이터를 파싱하고(parseData) → 그 결과를 데이터베이스에 저장하는(saveData) 모듈이 있다면, 각 단계는 순서대로 실행되며 이전 단계의 결과물을 사용하므로 순차적 응집을 가집니다.
3. 소통적/교환적 응집 (Communicational Cohesion)
동일한 입력 데이터(자료구조)를 사용하거나 동일한 출력 데이터를 생성하는 등, 동일한 데이터를 공유 하는 구성 요소들이 모여있는 경우입니다.
- 예시:
Book객체를 받아, 그 책의 제목, 저자, 가격 정보를 모두 업데이트하는updateBookDetails메서드가 있다면, 이 메서드 내부의 작업들은 모두 동일한Book데이터를 공유하므로 소통적 응집을 가집니다.
4. 절차적 응집 (Procedural Cohesion)
구성 요소들이 반드시 동일한 데이터를 공유하지는 않지만, 특정 실행 순서 에 따라 함께 그룹화되는 경우입니다. 순차적 응집과 비슷하지만, 데이터 흐름보다는 제어 흐름(순서)에 더 중점을 둡니다.
- 예시: 사용자가 로그인할 때,
checkPermissions()를 호출하여 권한을 확인하고, 성공하면openDatabaseConnection()을 호출하고, 마지막으로logUserAccess()를 호출하는 일련의 과정은 정해진 절차에 따라 실행되므로 절차적 응집의 예입니다.
5. 시간적 응집 (Temporal Cohesion)
구성 요소들이 기능적으로는 관련이 없지만, 특정 시점 에 함께 실행되어야 하는 작업들이 모여있는 경우입니다.
- 예시: 애플리케이션 시작 시 실행되는
initializeApp()메서드 안에 설정 파일 로드, 데이터베이스 연결 초기화, 캐시 비우기 등 서로 다른 작업들이 모여 있다면, 이는 시간적 응집에 해당합니다. 이 작업들은 ‘시작 시’라는 시간적 제약 때문에 함께 묶여 있습니다.
6. 논리적 응집 (Logical Cohesion)
유사한 성격의 작업들이 하나의 모듈에 모여 있지만, 실제로 호출될 때는 그중 하나의 작업만 선택되어 실행 되는 경우입니다.
- 예시: 모든 종류의 에러 메시지를 출력하는
printError(errorCode)함수가 있다고 가정해 봅시다. 이 함수는errorCode에 따라 파일 에러, 네트워크 에러, 데이터베이스 에러 등 전혀 다른 종류의 메시지를 출력합니다. 이처럼 논리적으로 ‘에러 출력’이라는 범주에 속하지만, 실제로는 각기 다른 작업을 수행하는 경우 논리적 응집을 가집니다.StringUtils와 같은 유틸리티 클래스도 종종 이 유형에 속합니다.
7. 우연적/동시적 응집 (Coincidental Cohesion) - 가장 피해야 할 형태
모듈 내 구성 요소들이 아무런 의미 있는 연관 관계 없이, 단순히 우연히 같은 파일에 존재 하는 경우입니다. 이는 유지보수성과 재사용성을 심각하게 저해하는 가장 낮은 수준의 응집도입니다.
- 예시: 하나의
Utils.java파일 안에calculateFactorial()함수와sendEmail()함수,formatCurrency()함수가 함께 들어있다면, 이들은 서로 아무 관련이 없으므로 우연적 응집의 대표적인 예입니다.
LCOM: 클래스 응집도를 정량적으로 측정하기
이론적인 개념을 넘어 코드의 응집도를 객관적인 숫자로 평가하고 싶을 때 LCOM(Lack of Cohesion in Methods) 메트릭을 사용할 수 있습니다. LCOM은 클래스 내의 메서드들이 인스턴스 변수(필드)를 얼마나 공유하는지를 분석하여 응집도의 부족함(Lack of Cohesion)을 측정합니다. LCOM 점수가 높을수록 응집도가 낮다 는 의미입니다.
LCOM 활용 예: 세 가지 클래스 비교 분석

위 이미지는 LCOM을 이해하는 데 좋은 사례를 보여줍니다. 각 클래스의 응집도를 분석해 보겠습니다.
1. 클래스 X: 높은 응집도 (Good)
- 구조: 모든 메서드(m1, m2, m3)가 클래스의 모든 필드(A, B, C)를 골고루 사용하며 서로 긴밀하게 연결되어 있습니다.
- 분석: 이 클래스는 명확한 단일 책임을 가지고 있으며, 메서드와 필드들이 그 책임을 수행하기 위해 유기적으로 협력하고 있음을 보여줍니다.
- LCOM 평가: LCOM 점수가 매우 낮습니다 (즉, 응집도가 높습니다). 이는 잘 설계된 클래스의 전형적인 모습입니다.
2. 클래스 Y: 낮은 응집도 (Bad)
- 구조: 세 개의 메서드(m1, m2, m3)가 각각 자신과 관련된 필드(A, B, C)만 독립적으로 사용합니다. 메서드 간에 공유되는 필드가 전혀 없습니다.
- 분석: 이 클래스는 사실상 관련 없는 세 개의 기능이 우연히 한 클래스 안에 모여있는 것과 같습니다. 이는 우연적 또는 논리적 응집에 가깝습니다.
- LCOM 평가: LCOM 점수가 매우 높습니다 (즉, 응집도가 낮습니다). 이 경우, 클래스 Y를 각각의 책임에 따라 세 개의 작은 클래스로 분리하는 리팩토링이 강력히 권장됩니다.
3. 클래스 Z: 중간 수준의 응집도 (Needs Improvement)
- 구조: 두 개의 메서드(m1, m2)는 필드 A와 B를 공유하며 서로 연관되어 있지만, 메서드 m3는 필드 C만 독립적으로 사용합니다.
- 분석: 이 클래스는 두 개의 서로 다른 책임을 가지고 있을 가능성이 높습니다. 하나는 A와 B를 다루는 책임, 다른 하나는 C를 다루는 책임입니다.
- LCOM 평가: LCOM 점수가 중간 수준입니다. 응집도를 높이기 위해, m3와 필드 C를 별도의 클래스로 분리하는 것을 고려해볼 수 있습니다.
이처럼 LCOM 분석은 클래스의 설계상 결함을 발견하고, 더 나은 구조로 리팩토링하는 데 유용한 지표를 제공합니다.
결론
높은 응집도 는 좋은 소프트웨어 설계의 핵심입니다. 모듈이 하나의 명확한 책임을 갖도록 설계하면, 코드는 자연스럽게 더 이해하기 쉽고, 테스트하기 용이하며, 변경에 유연하게 대처할 수 있게 됩니다. 오늘 살펴본 7가지 응집도 유형을 이해하고, LCOM과 같은 도구를 활용하여 자신의 코드를 객관적으로 평가하는 습관을 들인다면, 누구나 더 견고하고 유지보수하기 좋은 소프트웨어를 만들 수 있을 것입니다.