소프트웨어 설계의 결합 균형 (2주차 · 5~9장)

결합도(Coupling)를 코드·컴파일러·도메인·소유권의 4가지 렌즈로 다시 보고, "지식을 어떻게 캡슐화해 공개하느냐"가 결합을 결정한다는 하나의 원리로 꿰어, AI 시대의 모듈 구조까지 설계하라.


0. 사전 필수 용어 (선행지식)

  1. 결합도(Coupling) — 두 모듈이 서로 의존하는 정도입니다. 높을수록 한쪽 변경이 다른 쪽으로 번집니다. 비유하면 "두 사람이 얼마나 많은 비밀을 공유하느냐"입니다. §1·§3 전체에서 쓰입니다.
  2. 캡슐화(Encapsulation) — 내부 구현을 감추고 약속된 통로(인터페이스)로만 쓰게 막는 것입니다. 이 강의의 결론은 "결합도는 캡슐화 수준이 결정한다"입니다. §6 핵심 원리.
  3. 암묵지 vs 명시지(Implicit vs Explicit knowledge) — 코드에 적히지 않고 사람끼리만 아는 약속이 암묵지, 이름·타입처럼 코드에 적힌 약속이 명시지입니다. 공생성 모델(§3-B)의 핵심 구분입니다.
  4. 정적 분석 / 컴파일러 — 코드를 실행하지 않고 타입·문법만 검사하는 도구입니다. "컴파일 통과 = 안전"이 아니라는 게 §3-B의 출발점입니다.
  5. 싱글톤 / 의존성 주입(DI·IoC) — 하나만 존재하는 객체가 싱글톤, 그 객체를 직접 만들지 않고 프레임워크가 넣어 주는 게 DI입니다. 외부·제어·Identity 결합 개선의 도구입니다(§4 Step 1).
  6. 도메인 / 서브도메인 — 소프트웨어가 푸는 현실 문제 영역이 도메인이고, 그 안의 작은 영역들이 서브도메인입니다. §3-C 도메인 결합 모델의 단위입니다.

📚 참고: 결합도 6분류는 구조적 설계(Structured Design, Stevens·Myers·Constantine, 1974)에서, 도메인·서브도메인 구분은 DDD(Eric Evans)에서 비롯합니다. 본 강의는 이 고전 모델들을 현대(프레임워크·AI) 관점으로 재해석합니다.


1. 주제 정의

이 강의(뉴런데브 "바이브코딩 의존성관리 #2 — 결합도의 이해", 5~9장)는 소프트웨어 결합도를 네 개의 결합 모델로 분해한다.

  1. 모듈 결합 모델 — 코드의 구조·내용을 정적 분석한다.
  2. 공생성(Consequential) 결합 모델 — 컴파일러가 잡아내지 못하는 결합을 본다.
  3. 도메인 결합 모델 — 코드가 도메인 지식을 어떻게 나눠 갖는지 본다.
  4. 코드 소유권(Ownership) — 사람·AI가 코드를 어떻게 분담·소유하는지 본다.

먼저 토대가 되는 공학(Engineering) 개념을 잡는다. 공학은 현실의 문제를 실체로 확인하고, 정량적으로 측정·정의할 수 있는 모델을 마련해, 인과관계가 명확한 논리적 해법을 탐색하는 것이다. 핵심은 "설명 가능성"이다. 소프트웨어 공학은 이 접근을 소프트웨어의 전 과정(기획·개발·출시 전후)에 적용하려는 시도다.

핵심 아이디어: 네 모델은 결국 하나로 수렴한다 — "공유할 수밖에 없는 지식을 얼마나 잘 캡슐화해 공개하느냐"가 결합의 강도를 만든다.


2. 풀려는 문제

  • 문제 1 — 코드 변경의 파급: 한 모듈을 고치면 어디까지 깨지는지 예측할 수 없다. 결합도를 정량적으로 표현해야 통제할 수 있다.
  • 문제 2 — "컴파일 통과 = 안전"의 착각: 정적 모델로 아무리 정교하게 설계하고 컴파일을 통과해도 런타임에 박살난다. 왜인지 모델이 필요하다.
  • 문제 3 — 의존성 방향: 어떤 모듈이 어떤 모듈을 의존해야 하는가? 무엇을 기준으로 정하는가?
  • 문제 4 — 소유권과 커뮤니케이션 비용: 코드가 많아져 사람을 늘리면 소통 비용이 폭증한다. AI 시대에도 같은 문제가 재현된다.

⚠️ 주의: 알려진 정량 모델(CMMI 등)은 대규모 SI·국방 프로젝트 바닥에서 만들어져, 일반 개발 환경에는 잘 들어맞지 않는다. 그래서 현실의 많은 문제가 공학이 아닌 방법(인력 충원·팀장 교체 같은 조직·소통·경영 이론)으로 해결되곤 한다. 본 강의는 이를 공학적으로 다시 본다.


3. 핵심 개념·구조

A. 모듈 결합 모델 — 코드의 구조·내용 (6장)

코드를 정적 분석해 결합도를 수치화한다. 소프트웨어 여명기(코드가 곧 OS이던 시절) 모델이라 현대의 추상화 계층(React·Spring·클라우드 인프라 위에서 도는 코드)은 온전히 반영하지 못한다. 강한 결합 → 약한 결합 순서:

결합(강→약) 특성 개선 방향
Content(콘텐츠) 다른 코드의 본문(Body)을 직접 참조 — 가장 강함 바디 대신 인터페이스 공개
Common(공통) 단일 자원(구조체·전역변수·DB)의 전체 스펙을 공유 API·DTO로 통제, MSA에서 DB 분리
External(외부) 단일 자원 중 필요한 만큼만 공유(멤버만/결제만) static 대신 인스턴스화
Control(제어) 플래그·인자로 다른 모듈의 내부 흐름을 제어 캡슐화 강화, 전략 객체·제어 역전
Stamp(스탬프) 약속된 인터페이스지만 필요 이상의 구조 노출 ISP(인터페이스 분리), 연쇄 참조 차단
Data(데이터) 필요한 정보만 노출(프리미티브·중립 타입) — 가장 약함 이상적이나 중립 타입은 비용 ↑

리팩터링은 항상 위에서 아래로 낮춘다: 콘텐츠→공통→외부→제어→스탬프→데이터.

💡 실무 노하우: 이 6개 용어는 팀·AI와 코드를 논의하는 공용 언어(DSL)다. "이거 너무 스탬프한데 인터페이스 분리하자", "이 메서드 시그니처 컨트롤 결합 위험 있어, 캡슐화해 봐" — 모든 코딩 에이전트가 이 모델을 학습하고 있어 그대로 알아듣는다.

B. 공생성(Consequential) 결합 모델 — 컴파일러 기준 (7장)

모듈 결합이 코드에 맞춘 모델이라면, 공생성은 컴파일러 통과 여부에 맞춘 모델이다. 컴파일러는 정적 타입·문법만 검사한다. 컴파일을 통과해도 런타임에 깨지는 이유는 공유 지식이 "암묵적"이기 때문이다. (지식 공유량이 아니라 암묵성이 문제다.)

위로 갈수록 컴파일러가 걸러주고, 아래로 갈수록 암묵적이라 위험하다:

  • Name(이름) — 가장 안전. 바꾸면 즉시 컴파일 실패.
  • Type(타입) — 두 번째 안전. 단, 타입 안의 메서드·필드 변경은 통과할 수 있다.
  • Meaning(의미) — 매직넘버. 값을 바꿔도 컴파일 통과 → 여기서부터 위험. (개선: 상수·Enum으로 올림)
  • Algorithm(알고리즘) — 양쪽이 같은 체크섬·암호화 키 등을 암묵 공유.
  • Position(위치) — 인자 순서·컬렉션 위치·CLI argv 순서. divide(분자, 분모)처럼 순서가 바뀌면 깨진다.
  • Execution Order(실행 순서) — 라이브러리 호출 순서(init→A→B). 오픈소스에 매우 흔하다.
  • Timing(타이밍) — 타임아웃·세션 만료. 런타임 실험으로 조정. 컴파일에 안 걸리는 현대 개발의 난점.
  • Value/State(값·상태) — 런타임 상태가 서로 영향(트랜잭션). 경계로 묶어 처리.
  • Identity(정체성) — 같은 레퍼런스(포인터)를 공유해야만 동작. 싱글톤 상황의 기본.

핵심 처방: 암묵지를 이름·타입으로 "위로" 올려라. 그래야 컴파일러가 변경을 자동으로 걸러낸다.

⚠️ 주의: 사람이 결합을 암묵지로 두는 진짜 이유는 "귀찮음"이다. 강사의 표현대로 "녹차 티백 50원짜리를 페트병에 담으면 8,000원에 사 먹는다 — 귀찮은 건 못 이긴다." 코드도 똑같이 귀찮아서 캡슐화를 빼먹는다. AI(바이브 코딩)도 같은 약점이 있으니, 검수 시 "암묵지로 처리한 것 / 캡슐화 덜 돼 내부 지식이 노출된 것"을 먼저 찾게 시켜라.

C. 도메인 결합 모델 + DDD Distillation (8장)

코드가 결합하는 본질적 이유는 도메인에 있다. 도메인이 너무 복잡해 한 코드에 다 담지 못하니 지식을 나눠 코드로 만들고, 그 코드들이 상호작용하려면 지식 일부를 공개할 수밖에 없다. 이 공개를 얼마나 잘 캡슐화하느냐가 결합도를 결정한다. "침투적(Intrusive)" = 콘텐츠 = 바디를 다 까서 캡슐화·은닉을 안 한 것이다.

DDD Distillation(Eric Evans의 전략적 설계 패턴)으로 서브도메인을 분류하면 의존성 방향이 보인다. 각 서브도메인의 성격은 Cynefin 프레임워크(Clear·Complicated·Complex·Chaotic)와 매핑된다:

서브도메인 Cynefin 성격 설계 함의
Core(핵심) Complex 이상 돈 버는 핵심 경쟁력, 미친 듯이 바뀜 Core가 타 모듈을 의존해야(역방향 금지)
Generic(일반) Complicated 굳건한 인프라성(트래픽·DB·세션) 안정적, 단방향 의존의 종착점
Supporting(지원) Clear 부가 업무, 명백한 해법 존재 직접 개발 X → 솔루션 구매

💡 실무 노하우: 개발자가 가장 선호하는 곳은 Generic(인프라)이지만, 회사가 CTO를 뽑는 이유는 인력을 Core로 옮겨 돈을 벌게 하기 위해서다. Core 코드는 시장에 없는 노하우라 침투적·실험적 코드가 흔할 수밖에 없다.

D. 코드 소유권 + Bounded Context (9장)

Bounded ContextUbiquitous Language(유비쿼터스 언어)의 경계면이다. 같은 단어가 컨텍스트마다 다른 의미를 갖는다 — "냉장고"가 인테리어 업자에겐 부피, 전기 업자에겐 전력 소비량이다. 경계를 나누는 실무적 이유는 팀·회사가 다르고 말이 안 통하기 때문이다(SI 협업에서 유래). 소통 비용이 코드 병합 비용보다 커지면 무조건 나눈다.

코드 오너십: 과거엔 사람이 코드를 소유했고, 코드가 늘면 사람을 늘렸지만 커뮤니케이션 비용이 폭증했다(N명이면 N(N-1)/2 대화쌍). 소프트웨어 공학은 이 소통 비용을 패턴·규칙으로 줄이는 기법이다.


4. 실습·구현 가이드

Step 1 — 모듈 결합을 한 단계씩 낮춘다

콘텐츠(바디 노출)면 인터페이스를 공개해 공통/외부로, 정적 자원이면 인스턴스화로, 제어면 캡슐화·전략 객체로, 스탬프면 인터페이스 분리(ISP)로 내린다. 한 번에 데이터까지 가려 하지 말고 지금 코드의 한 칸 아래를 목표로 한다.

Step 2 — 암묵지를 이름·타입으로 "위로" 올린다

매직넘버 → 상수·Enum(Meaning→Name/Type). 문자열 파싱 약속 → 전용 타입(예: 그냥 String이 아니라 JsonString 타입을 받게). 실행 순서 약속 → DSL·메서드 체이닝 제한. 목표는 "컴파일이 실패하게 만들어" 약속 위반을 자동 검출하는 것.

Step 3 — Distillation으로 의존성 방향을 정한다

서브도메인을 Core/Generic/Supporting으로 라벨링한다. 자주 바뀌는 Core가 안정적인 Generic을 향해 단방향으로 의존하게 배치한다. Supporting은 만들지 말고 솔루션을 산다.

Step 4 — AI 친화적 그래프 구조로 코드·문서를 분리한다

거대 단일 파일/문서를 금지한다. 진입점(README)은 요약 + 하위로 파고드는 링크만 주고, 훅·도구·초기화·컴팩트 로직을 파일 단위로 분리단방향 그래프로 잇는다. AI가 세션 컨텍스트에 필요한 만큼만 타고 들어가 로딩하게 한다.

확인: ① 내 코드의 가장 강한 결합 유형을 한 단계 낮췄는가? ② 매직넘버·순서 약속을 타입/이름으로 올려 컴파일러가 잡게 했는가? ③ Core가 다른 모듈에 의존받고 있지는 않은가? ④ 한 모듈을 고칠 때 옆 모듈을 안 깨고 격리해 로딩할 수 있는가?


5. 사례·적용

  • Spring DI / 제어 역전 — 플래그 대신 전략 객체를 DI로 주입해 제어를 프레임워크로 역전(IoC)한다. 스프링 컨트롤러는 전부 싱글톤이므로 내부에 가변 상태를 두면 동시성 문제가 터진다(초급 개발자 단골 실수). 참고: Spring Framework.
  • MSA의 DB 분리 — 단일 DB를 ATEAM이 공유(Common)하면 인덱스 하나 바꿔도 옆 팀 쿼리가 느려진다. DB를 쪼개 단일 자원 공유 자체를 없앤다. "DB를 쪼개지 않는 MSA는 MSA가 아니다."
  • 불변 노출(record class) — 스탬프 결합의 현대적 완화. 자바 record·코틀린 data class처럼 불변이면 "건드릴 수 없으니 많이 알아도 된다"는 관점. 단, 2·3차 연쇄 참조는 여전히 차단해야 한다.
  • Supporting 도메인 = 솔루션 구매 — 그룹웨어/문서 협업은 만들지 말고 구글 스프레드시트로 대체한다. MS Office 풀세트가 월 9,500원, Adobe 풀세트가 월 4만 원으로 개발자 인건비와 비교가 안 된다.
  • AI 에이전트 코드 구조 — 에이전트 서버를 툴 루프·전처리·도구별 파일·훅 처리기·초기화·컴팩트 로직으로 잘게 분리해, 한 부분을 고칠 때 나머지에 영향이 안 가게 한다(강사의 코딩 에이전트 실제 구조).

📚 참고: 모듈 결합 6분류의 원전은 구조적 설계(Stevens·Myers·Constantine, 1974), 의존성 방향과 서브도메인은 DDD(Evans, 2003), 도메인 난이도 분류는 Cynefin(Snowden)이다. "깊은 모듈 vs 얕은 모듈"은 John Ousterhout, A Philosophy of Software Design과 통한다.


6. 핵심 원리

원리 하나로 수렴한다: 결합도는 "공개 지식을 얼마나 잘 캡슐화했느냐"가 결정한다.

  • 모듈 결합의 콘텐츠/제어/스탬프는 모두 캡슐화 실패의 다른 이름이다.
  • 공생성 결합은 "암묵지가 명시지(이름·타입)로 캡슐화되지 않았다"는 문제다.
  • 도메인 결합은 "도메인 지식을 공개할 때 캡슐화 방식"이 강·약을 만든다.
  • 얕은 모듈(shallow) vs 깊은 모듈(deep) — 모듈의 깊이는 내용의 양이 아니라 캡슐화 수준이 결정한다. 인터페이스가 구현을 충분히 추상화하지 못하면(얕은 모듈) 추상화의 의미가 없다. "에이전시 사장에게 대충 말해도 작품이 나오면" 그게 깊은 모듈이다.

💡 실무 노하우(시너지): 같은 "이벤트 시스템"을 써도 약결합이 보장되지 않는다. 이벤트 메시지 바디에 내부 지식을 가득 실으면 이벤트로 분리해도 여전히 강결합이다. 결합은 시스템 종류가 아니라 메시지의 캡슐화 수준이 결정한다.


7. 한계·트레이드오프

  • 데이터 결합(중립 타입)의 비용 — 결합도는 낮아지지만 인터페이스·변환 객체가 폭증해 유지보수 비용이 오른다. 포맷↔도메인 변화율이 매우 크고 코드 오너십이 여러 회사로 분산된 SI에서만 정당하다. 토이·소규모 프로젝트에 적용하면 망한다.
  • 여명기 모델의 한계 — 모듈 결합 모델은 코드가 곧 OS이던 시절 모델이라, 프레임워크가 외부·제어 결합을 레이어링으로 커버하는 현대엔 콘텐츠·공통의 위협이 줄었다. 코드 차원에서 제어·스탬프가 가장 위협적이다.
  • 타이밍 결합은 제거 불가 — 타임아웃·세션 만료는 런타임 실험으로만 정해지고 컴파일·정적 분석에 안 걸린다. 관리 대상으로 끌어안아야 한다.
  • Generic(인프라) 팀의 딜레마 — 개발자가 선호하지만 돈을 못 벌어, 인수합병·새 CEO가 오면 "고인물"로 정리 대상이 되기 쉽다.

⚠️ 주의(트레이드오프 박스): 결합도를 무조건 낮추는 게 정답이 아니다. 변화율 대비 관리비용으로 판단하라. 변화가 적은 곳에 과한 분리·중립 타입을 넣으면 비용만 늘고 이득이 없다.


8. 다른 접근과 비교

관점 보는 단위 잡아내는 결합 못 잡는 것
모듈 결합 모델 코드 구조·내용(정적) 콘텐츠·공통·외부·제어·스탬프·데이터 컴파일 통과 후 런타임 결합
공생성 모델 컴파일러 통과 여부 의미·알고리즘·순서·타이밍·상태·정체성 도메인 차원의 의존 방향
도메인 결합 모델 도메인 지식 공유 서브도메인 간 의존 방향·강도 사람·세션 단위 소유권
코드 오너십 누가/무엇이 소유하나 커뮤니케이션 비용, 모듈 경계 (위 셋의 종합 적용 대상)
  • AI 이전 vs AI 이후 오너십 — 둘 다 "소유 가능한 코드량의 한계 → 분담 → 소통 비용 폭증"이라는 같은 구조다. 사람은 머릿속 한계, AI는 컨텍스트 한계. 그래서 옛 공학 기법이 멀티에이전트에도 그대로 유효하다.
  • 거대 문서(공유 메모리) vs 그래프 구조 — "모든 멀티에이전트가 하나의 거대 MD를 공유"하는 발상은 비동기 회의를 동기 회의로 망치는 격이다. 작은 조각의 단방향 그래프가 최적이다.

9. 메타인지 자기평가

스스로 점검하라. 막히면 해당 섹션으로 돌아간다.

  1. 모듈 결합 6종을 강→약으로 외워서 말할 수 있는가? (§3-A) — 강사 왈 "이해한 걸 외워야 한다."
  2. "컴파일이 통과했는데 왜 런타임에 깨지는가?"를 암묵지로 설명할 수 있는가? (§3-B)
  3. 내 최근 코드에서 매직넘버·인자 순서 의존을 찾아 이름/타입으로 올릴 수 있는가? (§4 Step 2)
  4. 우리 제품의 Core / Generic / Supporting 서브도메인을 각각 한 개씩 댈 수 있는가? (§3-C)
  5. Bounded Context를 "유비쿼터스 언어의 경계면"으로, 그리고 팀이 다르기 때문이라는 실무적 이유로 둘 다 설명할 수 있는가? (§3-D)
  6. 내 코드베이스를 AI가 "필요한 만큼만 로딩"하도록 그래프로 재설계할 첫 한 걸음을 적을 수 있는가? (§4 Step 4)

확인: 6문항 중 4개 이상을 막힘 없이 답하면 본 장의 핵심을 잡은 것이다. 1·2번이 막히면 §3을, 3~6번이 막히면 §4를 다시 본다.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플