800===Dev Docs and License/소프트웨어 아키텍쳐

Myers가 구분한 응집도

블로글러 2025. 2. 9. 01:58

오늘은 소프트웨어 설계에서 매우 중요한 개념인 응집도(Cohesion), 그중에서도 Myers가 분류한 응집도 체계에 대해 알아보겠습니다. 객체지향 프로그래밍을 공부하거나 코드 구조를 고민해본 분이라면 한 번쯤은 들어보셨을 텐데요. 응집도는 모듈(클래스, 함수 등)이 얼마나 ‘단일 목적(single responsibility)’을 가지고 있는지를 나타내는 지표입니다. 응집도가 높을수록 코드 유지보수성이 높고 재사용성이 좋아집니다.


1. 응집도(Cohesion)란? 🤔

‘응집(cohesion)’이라는 말을 들으면 ‘잘 붙어 있다’라는 이미지를 떠올릴 수 있습니다. 소프트웨어 공학에서도 비슷한 의미로, 하나의 모듈이 얼마나 단일하고 밀접하게 연관된 기능들로 구성되어 있는지를 뜻합니다.

  • 🔹 개념 요약: 모듈(혹은 클래스, 메서드)이 수행하는 책임(기능)들이 서로 관련성이 높은 정도
  • 🔹 실생활 예시:
    • 예를 들어 ‘커피 머신’을 생각해봅시다. 커피 머신은 커피를 내리는 핵심 기능에 집중합니다. 온도 조절, 물 주입, 커피 가루 추출 등이 모두 ‘커피 추출’이라는 주된 기능과 연관이 높죠. 이것이 높은 응집도의 예시입니다.
    • 반대로 커피 머신에 ‘라면 끓이기’, ‘에어프라이어 기능’ 등이 추가되어 있다면, 여러 기능이 모듈 내부에서 뒤섞여 있다는 의미로 응집도가 낮다고 말할 수 있습니다.
  • 🔹 어떤 문제를 해결하는지?
    • 응집도가 낮으면 모듈의 내부 요소들이 서로 관련 없는 기능을 수행하므로, 수정할 때 어디를 건드려야 할지 판단하기 어렵고 사이드 이펙트도 많아집니다.
    • 응집도가 높으면 모듈 내부가 하나의 책임에 집중하므로, 변경이 필요할 때도 그 모듈 내부에서 해결할 수 있어 유지보수가 훨씬 쉬워집니다.

2. Myers가 구분한 응집도 🎬

Myers(정확히는 Stevens, Myers, Constantine)가 정의한 응집도 분류는 낮은 응집도에서 높은 응집도로 총 7단계로 구분됩니다. 각 단계별로 어떤 특징을 가지는지 살펴봅시다.

1) 우연적(Coincidental) 응집도

  • 서로 관련 없는 기능들이 ‘우연히’ 한 모듈에 모여 있는 상태입니다.
  • 예: Utility 라고 이름만 붙여놓고 아무 기능이나 다 넣어놓은 클래스.

2) 논리적(Logical) 응집도

  • 유사한 범주의 기능을 하나의 모듈에서 처리하지만, 실제로는 실행 시점에 따라 다르게 동작하는 형태입니다.
  • 예: UserInputHandler에서 키보드, 마우스, 음성 등 입력 형태에 따라 전혀 다른 로직을 처리하는 경우.

3) 시간적(Temporal) 응집도

  • 특정 시점(초기화, 종료 등)에 한 번에 실행해야 하는 기능들을 모아서 처리하는 형태입니다.
  • 예: 앱 초기화 시 로깅 설정, DB 연결 설정, 캐시 초기화 등을 모두 한 메서드에서 처리하는 경우.

4) 절차적(Procedural) 응집도

  • 절차(프로세스) 흐름에 따라 여러 기능을 순서대로 처리하지만, 각 기능 간의 직접적인 연관성은 낮습니다.
  • 예: “1) 데이터 읽기 -> 2) 파일 열기 -> 3) 로그 남기기”처럼, 로직의 순서는 있지만 각각이 밀접하게 연결되어 있지는 않은 경우.

5) 통신적(Communicational) 응집도

  • 같은 입력이나 데이터를 공유하기 때문에 하나의 모듈에서 처리하는 경우입니다.
  • 예: 동일한 데이터 구조(예: 같은 DTO나 VO)를 읽고 쓰는 여러 기능을 묶어놓은 경우.

6) 순차적(Sequential) 응집도

  • 한 기능의 출력이 다음 기능의 입력으로 이어지는 형태입니다.
  • 예: “트랜잭션 처리 -> 그 결과를 바탕으로 로그 저장 -> 저장한 로그를 리포팅”처럼 순차적으로 연결되어 있는 경우.

7) 기능적(Functional) 응집도

  • 오직 하나의 기능만을 수행하며, 모듈 내부의 모든 요소가 그 기능 수행에 필수적입니다. 가장 이상적인 형태의 응집도입니다.
  • 예: ‘결제 처리’라는 단일 책임을 가진 클래스가 내부적으로 카드 결제, 계좌 이체 등을 지원하더라도 결국 ‘결제’라는 단일 기능에 집중하는 경우.

3. 응집도가 동작하는 원리와 주요 장점 🌟

1) 동작 원리

  1. 모듈 단일 책임(Single Responsibility Principle, SRP): 하나의 모듈이 하나의 책임(기능)만 맡도록 설계하여, 변경이 일어날 때 모듈 내부에서만 수정이 가능하도록 만든다.
  2. 의존성 최소화: 모듈 내 요소들은 밀접하게 연결되지만, 외부와의 의존성은 최소화하여 모듈이 독립적으로 동작하도록 한다.
  3. 정보 은닉(Information Hiding): 모듈 내부의 상세 구현을 감춰, 외부에서 불필요한 접근과 수정을 막는다.

2) 주요 장점

  1. 유지보수성 향상: 응집도가 높으면 기능 변경 시 모듈 자체에서만 수정이 이뤄지므로 유지보수가 용이하다.
  2. 가독성 향상: 모듈이 단일 기능을 수행하므로, 코드 리뷰나 디버깅 시에도 어느 부분을 보면 되는지 쉽게 파악할 수 있다.
  3. 재사용성 증가: 하나의 책임에 집중한 모듈은 다른 프로젝트나 다른 맥락에서도 같은 기능이 필요할 때 쉽게 가져다 쓸 수 있다.

4. 주의할 점 ⚠️

  1. 과도한 세분화: 응집도를 높인다고 해서 무조건 모듈을 지나치게 쪼개면, 오히려 클래스나 메서드가 너무 많아져 복잡도가 증가할 수 있습니다.
  2. 조기 최적화 주의: 처음부터 모든 모듈을 완벽히 응집도 높게 만들려다 보면 오히려 개발 속도가 느려질 수 있습니다. 어느 정도 프로토타입 단계를 거치면서 리팩토링으로 응집도를 높여가는 전략이 중요합니다.
  3. 팀 내 기준 합의: 응집도의 정도는 팀에서 합의된 코딩 스타일이나 아키텍처 원칙에 따라 달라질 수 있습니다. ‘적절한’ 응집도에 대한 논의가 선행되어야 합니다.

5. 실제 사용 예시 📱

아래는 Java 예시 코드입니다. 먼저 응집도가 낮은 경우를 살펴봅시다:

public class UtilityClass {
    // 우연적 응집도의 예시

    // 1) 파일에서 데이터를 읽어오는 메서드
    public List<String> readFile(String filePath) {
        // 파일 읽기 로직...
        return new ArrayList<>();
    }

    // 2) DB에 사용자 정보 저장 (전혀 다른 기능)
    public void saveUserToDB(User user) {
        // DB Insert 로직...
    }

    // 3) 문자열 포맷팅(또 다른 기능)
    public String formatText(String input) {
        // 포맷팅 로직...
        return input.trim().toUpperCase();
    }

    // 4) 쿠폰 코드 생성 (완전히 별개 기능)
    public String generateCouponCode() {
        // 쿠폰 코드 생성...
        return "COUPON-1234";
    }
}

이 클래스는 파일 읽기, DB 저장, 문자열 포맷팅, 쿠폰 코드 생성 등 서로 연관성이 거의 없는 기능들을 한데 모아둔 전형적인 ‘우연적 응집도’ 예시입니다.


이제 응집도가 높은 경우를 예시로 들어보겠습니다. 단일 책임 원칙에 따라, “사용자 관리(User Management)”라는 하나의 책임에 집중한 클래스를 구성해봅시다.

public class UserService {
    private final UserRepository userRepository;
    private final Validator<User> userValidator;

    public UserService(UserRepository repository, Validator<User> validator) {
        this.userRepository = repository;
        this.userValidator = validator;
    }

    // 1) 사용자 등록 (Create)
    public void registerUser(User user) {
        if (userValidator.isValid(user)) {
            userRepository.save(user);
        } else {
            throw new IllegalArgumentException("Invalid user data");
        }
    }

    // 2) 사용자 조회 (Read)
    public User findUserById(Long id) {
        return userRepository.findById(id);
    }

    // 3) 사용자 정보 수정 (Update)
    public void updateUser(User user) {
        if (userValidator.isValid(user)) {
            userRepository.update(user);
        } else {
            throw new IllegalArgumentException("Invalid user data");
        }
    }

    // 4) 사용자 삭제 (Delete)
    public void deleteUser(Long id) {
        userRepository.delete(id);
    }
}

이 예시에서는 “사용자 정보 관리”라는 하나의 책임에만 집중했습니다.

  • 등록, 조회, 수정, 삭제는 모두 “사용자”라는 공통 주제를 다루므로 기능 간의 응집도가 높습니다.
  • 모든 메서드는 UserRepositoryValidator<User>를 사용하고, 모듈 내부 요소들이 ‘사용자’를 다룬다는 목적에 집중하고 있습니다.

6. 마치며 🎁

응집도(Cohesion)는 소프트웨어 모듈이 단일한 책임을 얼마나 밀도 있게 수행하고 있는지를 보여주는 중요한 지표입니다. Myers가 구분한 7단계의 응집도를 통해, 우리는 모듈이 얼마나 체계적으로 설계되어 있는지 스스로 판단해볼 수 있습니다.

  • 만약 코드가 여러 기능을 뒤섞어놓고 있다면, 리팩토링을 통해 “하나의 명확한 책임”을 부여해보세요.
  • 응집도를 높임으로써 유지보수성이 향상되고, 가독성이 좋아지며, 궁극적으로는 개발 효율을 높일 수 있습니다.

실무에서는 완벽한 ‘기능적 응집도’를 추구하기보다는, 팀의 프로젝트 규모와 요구사항에 맞춰 적절한 수준의 응집도를 확보하고, 상황에 맞게 리팩토링을 반복해 나가는 것이 핵심입니다.

이 기술을 사용하면, 모듈화가 잘 이뤄져서 규모가 커져도 코드가 무너지지 않고 효율적인 유지보수가 가능합니다!


참고 자료 및 출처

  • Stevens, Myers, and Constantine (1974). “Structured Design”
  • Glenford J. Myers (1975). “Reliable Software Through Composite Design”
  • Robert C. Martin, “Clean Code”
  • Steve McConnell, “Code Complete”

궁금증이나 다른 의견이 있으시다면 언제든지 댓글로 남겨주세요!
함께 배우고 성장하는 개발 커뮤니티를 만들어가요. 감사합니다.

728x90