haileyjpark

[소프트웨어 공학] 모듈화와 응집도/결합도 본문

소프트웨어 공학

[소프트웨어 공학] 모듈화와 응집도/결합도

개발하는 헤일리 2022. 6. 26. 23:55
반응형

모듈화란

모듈이란 프로그램을 구성하는 시스템을 기능 단위로 독립적인 부분으로 분리한 것이다.

단순히 규모가 큰 것을 작게 나눈 조각이 아니라, 하나 이상의 논리적인 기능을 수행하기 위한 명령어들의 집합이라고 할 수도 있다.

 

모듈의 특징

  • 모듈은 독립적인 프로그램 그 자체일 수도 있고, 함수나 메서드일 수도 있다.
  • 모듈이 되려면 다른 것들과 구분될 수 있는 독립적인 기능을 가져야 하고, 유일한 이름을 사용해야 한다.
  • 다른 프로그램이나 다른 모듈에서 호출하여 사용할 수도 있어야 한다.
  • 모듈 자체로서 재사용될 수 있고 독립적으로 컴파일이 가능해야 한다.
  • 모듈은 다양한 형태로 존재할 수 있는데, 용도가 비슷한 함수나 추상화된 자료, 객체, 메서드 등이 이에 해당한다.

 

모듈화의 장점

  • 프로그램의 복잡도가 줄어들고 이해하기 쉬워진다.
  • 변경하기가 쉬워지고, 모듈은 독립적이기 때문에 변경 시 다른 부분에 미치는 영향도 감소한다.
  • 오류가 줄어들기 때문에 오류로 인한 파급효과를 최소화할 수 있다.
  • 두 모듈 간의 관련성이 적을 때 각각의 모듈은 서로 독립적으로 존재하게 되어 유지보수의 효율성이 높아지고 프로그램을 효율적으로 관리할 수 있다.

 

어느 정도의 크기로 모듈을 분리할 것인가

모듈을 나눌 때는 작게 나누면 좋지만 무조건 작다고 해서 좋은 것은 아니다. 모듈이 작아질수록 모듈의 개수가 늘어나고 그 사이에서 통신 횟수가 증가하면 과부하로 인해 성능이 떨어지고 복잡도가 증가하게 된다.

따라서 모듈의 크기를 결정할 때는 문제의 특성이나 유형에 알맞게 결정해야 한다.

모듈 간의 결합은 느슨하게, 모듈 내 구성 요소들 간의 응집은 강하게 한다는 규칙을 지키면 좋은 설계가 될 수 있다. (응집도와 결합도는 모듈의 독립성을 측정하는 개념이다.)

모듈을 분리하여 설계할 때에 되도록이면 모듈 간의 관련성을 적게 하여 모듈 간에 미치는 영향을 최소화하여 유지보수를 쉽게 해야한다.

클린 소프트웨어 저자인 로버트 마틴에 따르면 모든 모듈은 제대로 실행되어야하고, 변경이 용이해야하고, 이해하기 쉬워야 한다고 한다.


 

결합도(Coupling)

모듈 간에 상호 의존하는 정도 또는 두 모듈 사이의 연관 관계(관련성)를 의미한다.

모듈 내부가 아닌 외부의 모듈과의 연관도 또는 모듈 간의 상호 의존성을 나타내는 정도이다.

유지보수를 위해 b라는 기능을 수정하기 위해 b기능이 모여있는 B모듈을 수정하는데, 다른 모듈들과 연관되어 수정하려면 다른 모듈의 소스도 확인하면서 해야한다. 필요 시 다른 모듈들까지 수정해야한다면 유지보수가 더욱 힘들 것이다. → 다른 모듈과의 결합도가 높은 상황

 

반대로 A 모듈이 다른 모듈들을 참조하는 부분이 거의 없어서 의존도가 낮은 상황이라면 유지보수가 편할 것이다. 이렇게 참조가 적은 상황을 ‘느슨하게 연결되었다'고 표현한다 → 다른 모듈과의 결합도가 낮은 상황

 

결합도가 높으면 변경하고 검토해야 하는 모듈 수가 많아지는 단점이 있다. → 결합도가 낮을수록 검토해야하는 소스의 수가 적어져서 코드를 수정하기가 쉬워진다.

 

정적/동적 타입 언어의 결합도

정적 타입 언어는 import, use, include와 같은 타입 선언문을 사용하도록 강제한다. 따라서 타입 선언문으로 인한 의존성이 발생하고, 의존성이 있는 파일의 변경에 따른 재컴파일 또는 재배포가 강제된다. 하지만 동적 타입 언어는 소스 코드에 타입 선언문이 존재하지 않고, 대신 런타임에서 추론이 발생한다. 따라서 소스 코드의 의존성이 없으며, 재컴파일과 재배포도 필요하지 않다.

→ 동적 타입 언어는 그 자체로 느슨한 결합도를 갖는다.

 

결합도의 종류

자료 결합도 (Data Coupling) 어떤 모듈이 다른 모듈을 호출하면서 매개 변수나 인수로 데이터를 넘겨주고, 호출받은 모듈은 받은 데이터에 대한 처리 결과를 다시 돌려주는 방식
스탬프 결합도 (Stamp Coupling) 두 모듈이 동일한 자료 구조를 조회하는 경우의 결합도이며, 자료 구조의 어떠한 변화, 즉 포맷이나 구조의 변화는 그것을 조회하는 모든 모듈 및 변화되는 필드를 실제로 조회하지 않는 모듈에까지도 영향을 미친다.
제어 결합도 (Control Coupling) 한 모듈이 다른 모듈의 상세한 처리 절차를 알고 있어 이를 통제하는 경우나 처리 기능이 두 모듈에 분리되어 설계된 경우에 발생한다.
외부 결합도 (External Coupling) 어떤 모듈에서 선언한 데이터(변수)를 외부의 다른 모듈에서 참조할 때의 결합도이다.
공통(공유) 결합도 (Common Coupling) 공유되는 공통 데이터 영역을 여러 모듈이 사용할 때의 결합도이다.
내용 결합도(Content Coupling) 한 모듈이 다른 모듈의 내부 기능 및 그 내부 자료를 직접 참조하거나 수정할 때의 결합도이다.

 

결합도를 낮춘 사례

아래 코드는 클린코드 책의 예시 - 결합도를 낮춘 사례이다.

public interface StockExchange {
	
    Money currentPrice(String symbol);
}

public class Portfolio {
	
    private StockExchange exchange;
    
    public Portfolio(StockExchange exchange) {
    	this.exchange = exchange
    }
    
    // ...
}

Portfolio 클래스는 한국, 미국, 일본 등 외부 stockExchange API를 활용해서 포트폴리오 값을 계산해야하지만 이 예제에서는 StockExchange라는 인터페이스를 이용해서 어떤 외부 API든 다 구현이 가능하도록 설계했다.


 

응집도(Cohesion)

응집도는 모듈에 포함된 내부 요소들이 하나의 책임/목적을 위해 연결되어 있는 연관된 정도이다. 한 모듈 내부의 기능적인 응집 정도를 나타낸다.

a라는 기능을 수정하기 위해 a 기능이 모여있는 A모듈만 찾아서 수정할 수 있게 설계가 되었다면 유지보수가 편리해진다.

하지만 A모듈이 아닌 곳에 a기능이 흩어져 있다던가 또는 A모듈에 a기능 외에 b,c,d 기능도 섞여서 복잡하게 구현되어 있다면 수정이 힘들 것이다.

A모듈에 a기능을 위한 소스들이 모여있다면 그 모듈은 높은 응집도를 가진다고 할 수 있다.

반대로 서로 다른 목적을 가지고 있거나 흩어져 있다면 낮은 응집도를 가진다고 할 수 있다.

 

응집도가 높으면, 변경 대상과 범위가 명확해지는 장점이 있어서 코드를 수정하기가 쉬워진다.

 

응집도의 종류

기능적 응집도(Functional Cohesion) 모듈 내부의 모든 기능 요소들이 단일 문제와 연관되어 수행될 경우의 응집도
순차적 응집도(Sequential Cohesion) 모듈 내 하나의 활동으로부터 나온 출력 데이터를 그다음 활동의 입력 데이터로 사용할 경우의 응집도
교환(통신)적 응집도(Communication Cohesion) 동일한 입력과 출력을 사용하여 서로 다른 기능을 수행하는 구성 요소들이 모였을 경우의 응집도
절차적 응집도(Procedural Cohesion) 모듈이 다수의 관련 기능을 가질 때 모듈 안의 구성 요소들이 그 기능을 순차적으로 수행할 경우의 응집도
시간적 응집도(Temporal Cohesion) 특정 시간에 처리되는 몇 개의 기능을 모아 하나의 모듈로 작성할 경우의 응집도
논리적 응집도(Logical Cohesion) 유사한 성격을 갖거나 특정 형태로 분류되는 처리 요소들로 하나의 모듈이 형성되는 경우의 응집도
우연적 응집도(Coincidental Cohesion) 모듈 내부의 각 구성 요소들이 서로 관련 없는 요소로만 구성된 경우의 응집도

 

현재 프로젝트의 모듈

프로젝트를 진행하면서, 초기부터 모듈을 만들어서 진행했던 JWT, 인가, 암호화 등의 모듈도 있었지만, 기능을 만들면서 필요에 의해 생겨난 연체 체크 모듈, 페이지네이션 모듈도 있었고, 에러 핸들링이라는 부분을 배우면서 새롭게 추가한 에러 핸들링 모듈도 있었다.

  • 인가 모듈
  • JWT 모듈
  • 에러 핸들링 모듈
  • 암호화 모듈
  • 페이지네이션 모듈
  • 연체 체크 모듈

 

응집도

현 프로젝트의 Common 모듈에 있는 소스들은 대부분 기능적 응집도가 높은 편이다. 만약 사용자 개인정보를 암호화하여 저장하는 로직을 추가한다고 했을 때, 나는 <암호화 모듈>에 사용자 정보를 암호화하여 저장하게 될 것이다. 사용자의 비밀번호, 전화번호, 주소의 암호화/복호화 로직이 하나의 <암호화 모듈>에 있다면, 조금 더 디테일한 기능적 응집도를 위해서는 비밀번호 암호화 모듈 / 주소 암호화 모듈 / 전화번호 암호화 모듈로 나눌 수도 있을 것이다.

하지만 모듈을 지나치게 잘게 나누는 것은 프로그램의 복잡도를 증가시킬 수 있기 때문에 적절한 분리가 중요하다. 이런 경우에는 <암호화 모듈>로 통합해서 하나의 모듈로 운영해도 암호화를 위한 모듈이라고 할 수 있기 때문에 기능적 응집도가 높은 편일 것이라고 생각한다. 만약, 굉장히 많은 정보들을 암호화해서 저장하는 서비스가 있다고 가정한다면, 암호화 모듈도 세분화하여 나눌 수가 있을 것 같다.

 

결합도

현재 common 모듈 들의 경우, 모듈 간에 명확히 정의된 파라미터로 데이터를 교환하는 자료 결합도 방식으로 이루어져 있다.

회원가입하는 레포지토리 모듈에서 비밀번호 암호화 모듈의 입력으로 비밀번호 데이터를 넘겨주면 출력으로 암호화된 데이터를 받는다는 점만 알고있다. 회원가입 레포지토리 모듈의 입장에서는 비밀번호 암호화 모듈이 어떤 방식으로 암호화를 하는지에 대한 논리를 알 필요가 없다. 즉, 암호화 모듈의 논리 변경 때문에 회원가입 레포지토리 모듈이 영향을 받을 일이 없다.

인가 모듈의 경우, 일반 유저와 어드민 유저가 분리되어 있었고, 각 유저의 정보가 저장된 테이블도 분리되어 있었다. 그래서 인가 모듈에 아래와 같은 로직이 있었다.

if (userType === 'ADMIN') {
    const user = await AdminUserRepository.getById(userId);
  }
if (userType === 'USER') {
    const user = await UserRepository.getById(userId);
  }

이렇게 되면, userId를 전달하는 모듈이 인가 모듈의 내부에 관여하게 된다. 이처럼 한 모듈이 다른 모듈의 논리적 흐름을 제어하는 요소를 전달하는 경우를 제어 결합(Control Coupling)이라고 한다.

그래서 아래와 같이 관리자 권한 부여 모듈과 사용자& 관리자 권한 부여 모듈을 분리해 결합을 낮추었다. (변경된 코드에서는 피드백을 받아 인가 로직에서 DB 조회 부분을 삭제했다.)

// 사용자 & 관리자 권한 부여
const userAdminAuthorized = async (ctx, next) => {
  // const refreshToken = await
  const decodedToken = await authorize(ctx);
  ctx.state.userId = decodedToken.id;
  ctx.state.role = decodedToken.role;
  return next();
};

// 관리자 권한 부여
const adminAuthorized = async (ctx, next) => {
  const decodedToken = await authorize(ctx);
  if (decodedToken.role !== 'ADMIN') {
    throw new Error(401, 'You don\\'t have permission to access.');
  }
  ctx.state.userId = decodedToken.id;
  ctx.state.role = decodedToken.role;
  return next();
};

서비스 소스들의 경우에는 초기에 각 레이어 모듈 간 대부분 스탬프 결합으로 이루어져 있어 배열, 구조체 등을 매개변수로 전달하는 경우가 있었고, 그래서 만약 이 매개변수들의 자료구조의 형태가 바뀌게 되면 수정이 필요했다. 그래서 모듈 간 매개변수를 전달해줄 때 항상 객체에 매개변수들을 담아서 전달하여 data로 받는 형식으로 변경했고, 데이터를 전달해주는 상위 모듈에서 매개 변수의 자료 구조가 (email, userId)에서 (email, [userId,adminId]) 등으로 변경이 되어도 하위 모듈에서 수정할 필요가 없도록 변경했다. (이 부분은 사실 너무 간단한 변경 사항이라 결합도를 낮춘 사례라고 보기에는 애매한 측면이 있을 수 있다.)

 

  • 결합도를 낮춘 컨트롤러-서비스-레포지토리 소스
// 변경 전
const getOne = async (email, userId) => {
  try {
    return User.findOne({ where: {email, userId} });
  } catch (err) {
    throw new Error(err.message);
  }
};

// 변경 후
const getOne = async (data) => {
  const where = {};
  if (data.email) { where.email = data.email; }
  if (data.userId) { where.id = data.userId; }
  try {
    return User.findOne({ where });
  } catch (err) {
    throw new Error(err.message);
  }
};

 


 

마치며

초기에 탄탄한 설계를 거치면 초기부터 어느정도 모듈화를 할 수 있겠지만, 코드를 작성하면서 모듈이 생성되는 것이 아무래도 일반적일 것 같다. 나의 경우에는 프로그래밍이 아직 익숙하지 않아서 나중에 추가된 모듈이 많은데, 이러한 부분은 조금씩 더 프로그래밍이 익숙해지면 자연스럽게 필요하게 될 모듈이 무엇인지 감을 잡을 수 있을 것이라고 생각한다. 아름다운(?) 모듈화를 위해 모든 모듈들을 구현 초기부터 고려할 필요는 없을 것 같고, 필요한 시기에 적절하고 유연하게 모듈화를 하면 될 것 같다.

 

이번 주제에 대해 공부하면서 객체지향 SOLID 원칙, ISP, 멱등성, 의존성 주입 등 굉장히 많은 파생개념들이 함께 나왔는데, 시간이 없어 다루지 않았다. 이렇게 접하게 되는 지식 중 프로그래밍 “원칙"들은 무조건 따라야 하는 것은 아니지만 꼭 알아두면 필요에 따라 유용하게 사용하고 프로그래밍을 이해하는 데에 더 도움이 되는 것 같다.

 

결합도와 응집도에 대한 부분도 많이 들어본 용어였지만 실제로 의미하는 바가 무엇인지를 자세하고 구체적으로 알아볼 시간은 없었는데 이번 기회를 통해 자세하게 다룰 수 있어서 좋았다. 

 

 

참고자료

https://iwuooh.com/entry/모듈Module과-모듈화Modularization에-대한-정의와-이해

https://medium.com/@jang.wangsu/설계-용어-응집도와-결합도-b5e2b7b210ff

https://devuna.tistory.com/66

https://m.blog.naver.com/gluestuck/221899977072

https://stackoverflow.com/questions/42667848/javascript-interfaces-loose-coupling

https://share-factory.tistory.com/14

https://frontierdev.tistory.com/118

https://devkingdom.tistory.com/300