Vlad Khononov의 ‘Balanced Coupling’ 프레임워크(강도·거리·변동성)를 Rust 프로젝트에 적용해 결합도를 측정·점수화·시각화하는 도구 cargo-coupling을 소개하고, 사용법과 CI/AI 연동까지 다룬다.
URL: https://syu-m-5151.hatenablog.com/entry/2025/12/21/152559
Title: cargo-coupling: Visualizing Coupling in Rust Projects

cargo-coupling Web UI - 셀프 진단 뷰
"이 모듈은 정말 손대고 싶지 않다…"
소프트웨어 개발을 오래 해왔다면, 이런 감정을 알고 있을 겁니다. 무언가를 바꾸면 다른 무언가가 깨집니다. 테스트는 작성하기 괴롭습니다. 코드가 대체 무슨 일을 하는지 이해하는 것조차 불가능하게 느껴집니다. 이런 증상들은 공통된 원인을 공유합니다. 바로 모듈들이 서로에게 지나치게 의존하는—결합도(coupling) 문제입니다.
결합도 문제는 교묘합니다. 코드를 작성하는 동안에는 알아차리기 어렵고, 나중에 “왜 이렇게 변경이 어려운 거지?”라고 자문할 때 비로소 모습을 드러냅니다. 더 나쁜 점은, “결합이 너무 빡빡하다”는 걸 알아도 정확히 어디에서 어떻게 그런지, 어디부터 고쳐야 하는지 보기가 어렵다는 것입니다.
돌이켜보면 저는 결합도에 대한 이해가 꽤 피상적이었습니다. “왠지 촘촘하게 엮여 있는 것 같다”거나 “느슨한 결합이 좋다던데” 같은 막연한 감정에 기반해 판단했고, 정작 왜 그런지 말로 설명하려 하면 명확히 설명할 수 없었습니다.
이런 가시성 부족을 해결하려면 결합도를 측정할 수 있어야 합니다. 하지만 전통적인 “강하다 vs 약하다”라는 단일 축만으로는 충분하지 않습니다. 똑같이 “강한 결합”이라도, 어디에서 어떤 맥락으로 발생하느냐에 따라 의미가 달라지기 때문입니다.
여기서 Vlad Khononov의 “Balanced Coupling(균형 잡힌 결합)” 개념이 등장합니다. 이는 결합도를 강도(strength), 거리(distance), **변동성(volatility)**의 세 차원으로 평가하고, 그 균형을 살피는 프레임워크입니다. cargo-coupling은 이 프레임워크를 Rust 프로젝트에서 실용적으로 사용할 수 있도록 제가 개발한 도구입니다.
AI가 더 많은 코드를 작성하게 되는 시대일수록, 이 결합도 지표는 더욱 중요해집니다. 누가(혹은 무엇이) 코드를 작성하든, 사람은 여전히 그 코드를 이해하고 유지보수하며 확장해야 합니다. 오히려 AI가 코드를 생성하기 때문에, 구조를 객관적으로 평가할 수 있는 측정 지표가 필요합니다.
먼저 도구 개요를 살펴보고, 그 바탕이 되는 개념을 설명한 뒤, 실제 사용 방법을 보겠습니다.
cargo-coupling은 제가 Rust 프로젝트를 위해 개발한 결합도 분석 도구입니다.
영감은 Vlad Khononov의 책 “Balancing Coupling in Software Design”에서 왔습니다. 제가 막연하게 느끼던 결합도 설계의 어려움이 이 책에서 체계적으로 정리되어 있었습니다. 강도·거리·변동성이라는 세 차원으로 결합도를 포착하는 프레임워크에 감탄했고, 이를 Rust 프로젝트에서 실용적으로 만들고 싶어 도구를 만들었습니다. 이 책은 꼭 읽어 보시길 추천합니다.
도구는 GitHub에서 공개되어 있습니다. 유용하다고 느끼신다면 스타를 눌러주시면 감사하겠습니다!
crates.io: https://crates.io/crates/cargo-coupling
이제 흔한 전제를 하나 짚고 넘어가 봅시다.
“결합도는 최소화해야 한다” — 여러분도 그렇게 믿고 있지 않나요?
이 도구의 목표는 “결합도를 줄이는 것”이 아닙니다. 목표는 **“결합을 적절하게 설계하는 것”**입니다. 왜냐하면 결합도 자체가 본질적으로 나쁜 것이 아니기 때문입니다. 관련 기능이 서로 긴밀히 협력하는 것은 자연스럽습니다. 문제는 “부적절한 곳에서의 강한 결합” 혹은 “멀리 떨어진 모듈 사이의 촘촘한 결합”입니다. 이 관점 전환이 이 도구의 핵심입니다.
bashcargo install cargo-coupling cargo coupling ./src
그렇다면 “적절한 결합”은 정확히 무엇일까요?
전통적인 결합도 분석은 보통 단일한 “강/약” 축으로 생각합니다. 하지만 잠깐 생각해 보세요. 인접 모듈과의 강한 결합과, 멀리 떨어진 외부 라이브러리와의 강한 결합은 같은 의미일까요? 5년 동안 바뀌지 않은 코드와의 결합과, 매주 수정되는 코드와의 결합은 같은 위험을 갖고 있을까요?
단일 축으로는 이런 차이를 담아낼 수 없습니다. cargo-coupling은 결합도를 서로 독립적인 세 차원에서 측정합니다.
첫 번째 차원은 “결합 강도” — 모듈이 서로의 내부 구현을 얼마나 알고 있는가입니다.
user.password_hash처럼 구조체 필드를 직접 접근하는 코드를 본 적이 있나요? 이는 가장 강한 형태의 결합입니다. 반면 impl Trait를 통해 상호작용하는 코드는 상대의 내부를 몰라도 동작합니다. 이런 차이를 점수로 정량화합니다.
| 레벨 | 점수 | 설명 | Rust 예시 |
|---|---|---|---|
| Intrusive | 1.00 | 내부 구현에 직접 의존 | struct.field 직접 access |
| Functional | 0.75 | 함수 시그니처에 의존 | 메서드 호출 |
| Model | 0.50 | 데이터 구조에 의존 | 타입 정의, 타입 파라미터 |
| Contract | 0.25 | 인터페이스/트레이트에만 의존 | impl Trait |
두 번째 차원은 “거리” — 결합된 모듈들이 코드의 스코프 계층에서 얼마나 떨어져 있는가입니다.
같은 파일 안에서 함수들이 긴밀히 협력하는 것은 자연스럽습니다. 하지만 src/auth/login.rs가 src/billing/invoice.rs를 직접 참조한다면 어떨까요? 더 나쁘게는 외부 크레이트의 내부 구조에 의존한다면요? 거리가 멀수록 그 결합은 더 “무겁게” 됩니다.
| 레벨 | 점수 | 설명 |
|---|---|---|
| SameModule | 0.25 | 같은 파일/모듈 내부 |
| DifferentModule | 0.50 | 같은 크레이트 내 다른 모듈 |
| DifferentCrate | 1.00 | 외부 크레이트 의존 |
세 번째 차원은 “변동성” — 코드가 얼마나 자주 바뀌는가입니다.
여러분 프로젝트에도 1년 넘게 손대지 않은 안정적인 모듈과, 매주 수정되는 모듈이 공존할 겁니다. 안정적인 코드에 의존하는 것과 자주 바뀌는 코드에 의존하는 것은 위험도가 다릅니다. cargo-coupling은 Git history로부터 이 변동성을 자동으로 계산합니다.
| 레벨 | 점수 | 6개월 Git history에서의 변경 횟수 |
|---|---|---|
| Low | 0.00 | 0-2회 변경 |
| Medium | 0.50 | 3-10회 변경 |
| High | 1.00 | 11회 이상 변경 |
세 차원을 살펴봤습니다. 그런데 “강도 0.75”, “거리 0.50”, “변동성 중간”이라고 들으면, 이 결합이 실제로 좋은지 나쁜지 어떻게 판단해야 할까요?
cargo-coupling은 이 세 차원을 **균형 점수(balance score)**로 결합합니다. 세 숫자를 하나의 점수로 통합하면, 결합이 적절한지 직관적으로 판단할 수 있습니다.
개념은 단순합니다. “강도-거리 정렬”에 “변동성 위험”을 곱합니다.
textALIGNMENT = 1.0 - |STRENGTH - (1.0 - DISTANCE)| VOLATILITY_IMPACT = 1.0 - (VOLATILITY × STRENGTH) BALANCE_SCORE = ALIGNMENT × VOLATILITY_IMPACT
첫 번째 식은 강도와 거리가 비례하는지 측정합니다. 가까운 거리는 강한 결합을 어느 정도 허용할 수 있지만, 먼 거리는 약한 결합이어야 합니다. 두 번째 식은 변경 빈도와 결합 강도의 결합 위험을 측정합니다. 자주 바뀌는 코드와의 강한 결합은, 변경 때마다 영향을 받을 위험이 더 큽니다.
이 공식이 이끄는 결론은 다음과 같습니다.
이론을 이해했으니, 실제 프로젝트에 적용해 봅시다. cargo-coupling은 필요에 따라 여러 출력 형식을 제공합니다.
bashcargo coupling --summary ./src
출력 예:
textCoupling Analysis Summary: Health Grade: B (Good) Files: 14 Modules: 14 Couplings: 389 Balance Score: 0.83 Issues: Medium: 2 Top Priority: - [Medium] cargo-coupling::main → 21 dependencies - [Medium] 21 dependents → cargo-coupling::cargo_coupling Breakdown: Internal: 33 External: 356 Balanced: 33 Needs Review: 0 Needs Refactoring: 0 Connascence: Total: 807 (avg strength: 0.23) High-strength: Position=2, Algorithm=2 APOSD Metrics: Pass-Through Methods: 12 (simple delegation) High Cognitive Load: 2 modules Avg Module Depth: 7.9
리팩터링 우선순위가 높은 모듈을 식별합니다.
bashcargo coupling --hotspots ./src
text#1 my-project::main (Score: 55) 🟡 Medium: High Efferent Coupling 💡 What it means: This module depends on too many other modules ⚠️ Why it's a problem: • Changes elsewhere may break this module • Testing requires many mocks/stubs • Hard to understand in isolation 🔧 How to fix: Split into smaller modules with clear responsibilities e.g., Split main.rs into cli.rs, config.rs, runner.rs
특정 모듈을 변경했을 때의 영향 범위를 확인합니다.
bashcargo coupling --impact metrics ./src
인터랙티브 그래프로 결합 관계를 시각화합니다.
bashcargo coupling --web ./src
브라우저가 자동으로 열리며 Cytoscape.js를 이용한 인터랙티브 그래프가 표시됩니다. 노드를 클릭하면 상세 정보가 나오고, 문제가 있는 모듈은 색으로 구분됩니다.
수동 분석뿐 아니라, 품질을 지속적으로 모니터링할 수도 있습니다. cargo-coupling을 품질 게이트로 통합하면 결합 설계의 악화를 조기에 감지할 수 있습니다.
bashcargo coupling --check \ --min-grade=B \ --max-circular=0 \ ./src
GitHub Actions 예시:
yaml- name: Check coupling health run: | cargo coupling --check \ --min-grade=B \ --max-critical=0 \ ./src
등급이 임계값 아래로 내려가면 종료 코드 1을 반환하므로, CI 파이프라인에 쉽게 통합할 수 있습니다.
Claude Code나 GitHub Copilot과 함께 사용할 때는 --ai 옵션이 편리합니다.
bashcargo coupling --ai ./src
출력이 AI 친화적인 형태로 포맷되므로, 그대로 AI 도구에 붙여 넣어 리팩터링 제안을 받을 수 있습니다.
사용법을 살펴봤으니, 어떤 문제가 탐지되는지 궁금할 수 있습니다. cargo-coupling이 경고하는 대표 패턴은 다음과 같습니다.
함수/타입/impl이 너무 많은 모듈.
의존하는 대상이 너무 많은 모듈. 기본 임계값은 의존성 20+개입니다.
너무 많은 모듈이 의존하는 모듈. 기본 임계값은 피의존(의존자) 30+개입니다.
침투적 결합(intrusive coupling)과 높은 변동성이 결합된 상태. 변경이 넓은 범위로 전파되는 위험한 상태입니다.
탐지 결과는 최종적으로 프로젝트 전체의 건강도를 나타내는 단일 등급으로 집계됩니다.
| 등급 | 설명 |
|---|---|
| S | 과도하게 최적화됨. 리팩터링이 지나쳤을 수 있음 |
| A | 균형이 좋음. 이상적인 상태 |
| B | 건강함. 관리 가능한 상태 |
| C | 개선 여지 있음 |
| D | 주의 필요 |
| F | 즉각적인 조치 필요 |
흥미롭게도 **S 등급은 “과하다”**고 평가됩니다. 왜일까요?
결합도를 지나치게 줄이면 코드가 과도하게 쪼개져 전체 그림을 보기 어려워집니다. 하나의 동작을 추적하려고 파일 10개를 열어야 한다거나, 추상화 계층이 너무 깊어 “이게 대체 뭘 하는 거지?”라고 느껴본 적 있지 않나요?
결합도는 단순히 “적을수록 좋다”가 아닙니다. 핵심은 균형입니다.
CLI 도구뿐 아니라, 여러분의 도구에 내장할 수도 있습니다. cargo-coupling은 라이브러리로도 배포되어 있어 분석 함수를 코드에서 직접 호출할 수 있습니다.
rustuse cargo_coupling::{ analyze_workspace, analyze_project_balance_with_thresholds, IssueThresholds, VolatilityAnalyzer, }; fn main() -> Result<(), Box<dyn std::error::Error>> { let mut metrics = analyze_workspace(Path::new("./src"))?; let mut volatility = VolatilityAnalyzer::new(6); volatility.analyze(Path::new("./src"))?; metrics.file_changes = volatility.file_changes; metrics.update_volatility_from_git(); let report = analyze_project_balance_with_thresholds( &metrics, &IssueThresholds::default() ); println!("Grade: {}", report.health_grade); Ok(()) }
cargo-coupling은 대규모 프로젝트에서도 빠르게 동작하도록 설계되었습니다.
더 빠르게 실행하려면 Git 분석을 건너뛰는 --no-git 옵션을 사용하세요.
유용하긴 하지만, 이 도구가 만능은 아닙니다. 사용 전에 다음 제한을 알아두세요.
cargo-coupling은 “결합은 나쁘다”라는 단순한 관점 대신, **“적절한 결합을 선택한다”**는 실용적 접근을 제공합니다.
완벽한 설계는 필요 없습니다. “80% 개선이면 충분하다”는 실용적인 태도로 프로젝트 건강도를 조금씩 improve해 나가면 됩니다.
bashcargo install cargo-coupling cargo coupling --summary ./src
결합도 문제를 보이게 만드는 것이 더 나은 설계를 향한 첫걸음입니다.
다음에 “이 모듈은 정말 손대고 싶지 않다…”고 느낄 때, 그것은 더 이상 막연한 불안이 아닙니다. 강도·거리·변동성이라는 세 차원에서 분석할 수 있고, 구체적인 개선 행동으로 번역할 수 있는, 다룰 수 있는 과제가 됩니다. 그 감정은 두려워할 것이 아니라 개선의 출발점입니다.
관련 개념으로 John Ousterhout의 “A Philosophy of Software Design”에 등장하는 “Complexity(복잡도)”도 있습니다. 또 다른 유용한 관점을 제공하니 읽어볼 가치가 충분합니다.