BE 공부/클린 코드

[소프트웨어 설계] 커플링(Coupling)

꼬질꼬질두부 2025. 6. 28. 18:08
반응형

1. 공부하게 된 계기

프로젝트를 리팩토링하면서 작은 변경에도 연쇄적인 수정을 해야 하는 구조에 부딪혔습니다. 그 원인을 파고들다 보니 모듈 간의 높은 커플링이 문제였고, 이를 개선하기 위해 커플링의 개념과 종류를 깊이 있게 공부하게 되었습니다.


2. 핵심 요약

  • 커플링(Coupling)은 모듈 간 의존성을 나타내며, 느슨한 커플링(loose coupling)이 바람직합니다.
  • 커플링은 종류에 따라 낮은 커플링(데이터)부터 높은 커플링(콘텐츠)까지 여러 단계로 나뉩니다.
  • 설계 시 커플링을 의식적으로 낮추면 유지보수성, 재사용성, 확장성이 좋아집니다.

3. 개념 정리

✅ 커플링이란?

  • 하나의 모듈(또는 클래스)이 다른 모듈에 얼마나 의존하고 있는지를 나타내는 설계 개념입니다.
  • 커플링이 높을수록 변경에 취약하고, 시스템이 복잡해지기 쉬우며, 테스트와 유지보수도 어려워집니다.

✅ 커플링의 종류 (낮은 → 높은 순)

커플링 종류 설명 예시 특징
데이터(Data) 필요한 데이터만 전달 func(name, age) 이상적, 바람직
종류(Stamp) 구조체나 객체 전체 전달 func(User user) 불필요한 의존 가능성
제어(Control) 제어 흐름을 위한 값 전달 func(mode) 조건 분기에 따라 처리
외부(External) 외부 시스템에 의존 DB, 파일 시스템 외부 변경에 민감
공통(Common) 전역 변수 공유 global config 여러 모듈이 동시에 의존
콘텐츠(Content) 다른 모듈의 내부에 직접 접근 other._private_var 최악의 커플링

콘텐츠 커플링은 모듈의 캡슐화를 깨고, 하나의 수정이 여러 모듈에 영향을 줄 수 있어 가장 피해야 할 형태입니다.


4. 예제 코드

1) 데이터 커플링 (좋음)

function printUserInfo(name, age) {
  console.log(`${name} is ${age} years old.`);
}

printUserInfo("Alice", 30);

이 함수는 오직 필요한 정보(name, age)만 인자로 받습니다.
즉, 필요한 데이터만 외부에서 주입받고 내부적으로 사용하기 때문에 커플링이 매우 낮고 이상적입니다.

데이터 변경이나 구조 변경이 있어도, 함수와 독립적으로 관리 가능합니다.

2) 종류 커플링

const user = {
  name: "Bob",
  age: 28,
  email: "bob@example.com",
};

function printUser(user) {
  console.log(`${user.name} is ${user.age} years old.`);
}

printUser(user);

이 함수는 User 객체 전체를 인자로 받고 있지만, 내부에서는 name과 age만 사용하고 있습니다.
이처럼 필요하지 않은 데이터까지 함께 전달받으면,

  • User 클래스가 바뀔 때 함수에도 영향을 줄 수 있고,
  • 어떤 데이터가 쓰이는지 명확히 파악하기 어려워져 유지보수가 어려워집니다.
    → 이것이 종류 커플링입니다.

3) 제어 커플링

function process(mode) {
  if (mode === "dev") {
    console.log("개발 모드 실행");
  } else if (mode === "prod") {
    console.log("운영 모드 실행");
  }
}

process("dev");

여기서는 mode 값에 따라 다른 처리를 하도록 설계되어 있습니다.
이런 방식은 간단해 보이지만, 외부에서 내부 흐름을 결정한다는 점에서 위험합니다.

  • 기능이 많아질수록 조건 분기가 복잡해지고,
  • 호출자와 함수의 책임이 섞이게 됩니다.
    → 제어 커플링은 구조를 흐릴 수 있으므로 지양해야 합니다.

4) 공통 커플링

// 전역 설정 객체
const config = {
  env: "production",
};

function logEnv() {
  console.log(`현재 환경: ${config.env}`);
}

logEnv();

이 경우 함수는 전역 변수 config에 의존하고 있습니다.

  • 다른 모듈도 이 전역 설정을 사용한다면, 누가 언제 값을 바꿨는지 추적하기 어려워집니다.
  • 어느 한 쪽의 수정이 다른 쪽 동작을 깨뜨릴 수 있어 독립적인 동작이 어렵습니다.
    → 공통 커플링은 작은 프로젝트에선 편하지만, 규모가 커지면 큰 문제가 될 수 있습니다.

5) 콘텐츠 커플링 (Bad Example)

 

// db.js
class Database {
  constructor() {
    this._connection = "mysql://localhost";
  }

  _resetConnection() {
    console.log("DB 연결을 초기화합니다.");
  }
}

module.exports = Database;

// main.js
const Database = require("./db");

const db = new Database();
console.log(db._connection);       // 내부 변수에 직접 접근 (X)
db._resetConnection();             // 내부 메서드 직접 호출 (X)

이 예시는 Database 클래스의 내부 구현(밑줄로 시작하는 변수/메서드)에 외부 모듈이 직접 접근하고 있습니다.

  • _connection, _resetConnection()은 내부에서만 사용하라고 작성된 것인데,
  • 외부에서 사용하면 클래스의 구현이 조금만 바뀌어도 외부 코드가 함께 깨질 수 있습니다.
    → 이는 최악의 커플링 형태로, 반드시 피해야 합니다.

콘텐츠 커플링 개선 예시

// db.js
class Database {
  constructor() {
    this._connection = "mysql://localhost"; // 내부 연결 정보 (접근 제한)
  }

  getConnectionInfo() {
    return this._connection; // 안전한 접근 방식: 외부에 공개된 메서드로 전달
  }
}

module.exports = Database;

// main.js
const Database = require("./db");

const db = new Database();
console.log(db.getConnectionInfo()); // 안전하게 내부 정보를 가져옴

 

  • _connection 속성은 클래스 내부 변수로, 외부에서 직접 접근하지 않습니다.
  • 대신 getConnectionInfo()라는 공식적인 메서드(public method)를 통해 값을 제공합니다.
  • 이렇게 하면 나중에 내부 구현이 바뀌더라도 외부에서는 변경 없이 동일한 방식으로 사용 가능합니다.
  • 이는 콘텐츠 커플링을 피하면서, 캡슐화(encapsulation)를 잘 지킨 설계입니다.

 


5. 이해 포인트 or 실수했던 점

  • 처음엔 “커플링을 줄이면 그냥 복잡해지기만 하는 거 아닌가?”라고 생각했지만, 실제로는 코드 변경의 영향 범위를 줄이고 테스트를 쉽게 해주는 중요한 설계 원칙이었습니다.
  • 콘텐츠 커플링은 생각보다 무의식적으로 자주 발생할 수 있으니, 내부 구현에 직접 접근하지 않도록 신경 써야 합니다.

6. 정리 요약

커플링은 낮을수록 좋은 구조이며, 특히 콘텐츠 커플링은 피하도록 노력하자

 

반응형