Architecture & Design/Software Architecture
Principles - 08. 복잡성의 원천
흙은 없앨 수 없지만 잡초는 뽑을 수 있다 — 복잡성의 원천
정원을 가꾼다고 하자. 흙이 있다 — 식물이 자라려면 흙이 필요하고, 흙의 종류·배수성·영양분이 복잡하다. 이건 정원의 본질이다, 없앨 수 없다. 하지만 잡초도 있다 — 우리가 심지 않았는데 자라난 것, 영양분을 빼앗고 정원을 어지럽힌다. 이건 뽑을 수 있다. "정원이 복잡하다"고만 말하면 흙과 잡초를 구분 못 한다 — 무엇이 본질이고 무엇이 우리가 만든 것인가를 가려야 한다.
소프트웨어 복잡성도 두 종류다. Fred Brooks는
Ousterhout의 복잡성 정의
John Ousterhout는 에서 복잡성을 "시스템을 이해하고 수정하기 어ulse 만드는 모든 것"이라 정의하고, 세 가지 형태로 나눴다.
| 형태 | 설명 | 정원 비유 |
|---|---|---|
| 변경 증폭(change amplification) | 한 변경이 여러 곳에 영향 | 잡초가 번식하면 정원 전체를 덮음 |
| 인지 부하(cognitive load) | 개발자가 알아야 할 것이 너무 많음 | 흙의 성분을 전부 외워야 하는 것 |
| 알 수 없는 미지(unknown unknowns) | 무엇을 변경해야 하는지조차 모름 | 어디에 잡초가 있는지 모르는 것 |
이 중 "알 수 없는 미지"가 가장 위험하다 — 변경 증폭과 인지 부하는 적어도 "무엇을 고쳐야 하는지"는 안다. 미지는 "무엇을 모르는지조차 모르는" 상태라, 변경이 의도치 않은 곳에서 부작용을 낸다.
본질적 복잡성 — 줄일 수 없다
본질적 복잡성은 문제 자체의 어려움이다. 결제 시스템에서 "같은 카드로 동시에 두 번 결제되는 것을 막아야 한다"는 규칙은, 아키텍처를 어떻게 짜든 존재한다 — 동시성 제어가 필요하고, 그건 본질적으로 어렵다. 이 복잡성을 "단순화"하려 하면 문제 자체를 단순화하는 것(동시성 포기)이 되어 비즈니스 요구를 충족 못 한다.
본질적 복잡성에 대처하는 방법은 "줄이는 것"이 아니라 "잘 격리하는 것"이다 — 복잡한 규칙을 한 모듈(애그리거트) 안에 가두어, 시스템의 나머지 부분이 그 복잡성에 노출되지 않게 한다.
우발적 복잡성 — 뽑을 수 있다
우발적 복잡성은 우리가 만든 것이다. 그래서 제거할 수 있다.
// 우발적 복잡성 1: 과도한 간접층
class OrderController {
OrderFacade facade; // Facade
// Facade가 Service를 호출하고, Service가 UseCase를 호출하고,
// UseCase가 DomainService를 호출하고... → 한 변경이 4계층에 영향
}
// 우발적 복잡성 2: 설정 지옥
new OrderService(
new OrderValidator(config.getRules()),
new OrderProcessor(mapper, converter, formatter),
new OrderNotifier(emailConfig, smsConfig, pushConfig)
); // 10개 매개변수 — 무엇을 넣어야 하는지 파악만 30분
// 우발적 복잡성 3: 불필요한 동시성
class OrderService {
synchronized void place(Order order) { // 모든 주문에 동기화
repo.save(order); // 실제로는 경합이 없는데 lock 비용만 발생
}
}
설계 사례 — 우발적 복잡성 제거
flowchart LR
subgraph Before["Before: 우발적 복잡성"]
B1["OrderController"] --> B2["OrderFacade"] --> B3["OrderService"] --> B4["OrderUseCase"] --> B5["OrderDomain"]
Note1["4계층 간접 → 한 변경이 4곳에 영향 (변경 증폭)"]
end
subgraph After["After: 본질만 남김"]
A1["OrderController"] --> A2["OrderService"] --> A3["Order (도메인)"]
Note2["2계층 → 변경 영향 최소화"]
end
// Before — 우발적 복잡성: 4계층 통과 (변경 증폭)
// "주문에 할인 적용"을 추가하려면:
orderFacade.applyDiscount(orderId, discountRate); // Facade
orderService.applyDiscount(orderId, discountRate); // Service
orderUseCase.execute(new ApplyDiscountCommand(...)); // UseCase
order.applyDiscount(discountRate); // Domain (실제 로직)
// 같은 변경이 4곳을 건드림 — 변경 증폭
// After — 본질만 남김: 2계층 (변경 국소화)
// "주문에 할인 적용":
orderService.applyDiscount(orderId, discountRate);
// → 내부에서 order.applyDiscount(discountRate) 호출
// 한 곳만 변경 — 변경 증폭 없음
Before에서 "할인 적용"이라는 변경이 Facade → Service → UseCase → Domain 4곳을 건드린다. 각 계층은 "구조를 위한 구조"였다 — 비즈니스 가치를 더하지 않고 통과만 시킨다. 이 계층들이 존재하는 이유가 "아키텍처 원칙"이라면, 그건 원칙을 위한 원칙이다 — 우발적 복잡성이다. After에서 불필요한 계층을 제거하면, 변경이 한 곳에 머문다.
복잡성을 측정하는 지표
"복잡하다"는 감각을 수치로 잡으면, 퇴보를 감지할 수 있다.
| 지표 | 측정 방법 | 위험 임계치 | 복잡성 종류 |
|---|---|---|---|
| 순환 복잡도 (cyclomatic complexity) | 분기(if/for/while) 수 | 메서드당 10 초과 | 인지 부하 |
| 응집 결여 (LCOM) | 클래스 내 메서드 간 필드 공유도 | 높을수록 낮은 응집 | 인지 부하 |
| 들어오는 의존성 수 (fan-in) | 나를 import하는 파일 수 | 너무 많으면 변경 시 파급 | 변경 증폭 |
| 나가는 의존성 수 (fan-out) | 내가 import하는 파일 수 | 20 초과 시 주의 | 인지 부하 |
| 동일 변경에 닿는 파일 수 | "기능 X 추가" 시 수정되는 파일 수 | 5개 초과 시 주의 | 변경 증폭 |
마지막 지표("동일 변경에 닿는 파일 수")가 Ousterhout이 말한 변경 증폭의 직접적 측정이다. "할인 추가"에 1개 파일만 닿으면 낮은 복잡성, 5개 파일이 닿으면 높은 복잡성이다. 이 수치를 배포 주기별로 추적하면, 복잡성이 자라는 시점을 감지할 수 있다.
복잡성 줄이기 — 구체적 기법
| 기법 | 복잡성 형태 | 실천 |
|---|---|---|
| 깊은 모듈 (02번) | 인지 부하 | 인터페이스를 작게, 구현을 풍부하게 |
| 주석 (설계 메모) | 미지(unknown unknowns) | "왜 이 구조인가"를 코드 옆에 기록 |
| 불필요한 계층 제거 | 변경 증폭 | pass-through 계층(Facade/Wrapper) 제거 |
| 전역 상태 제거 | 미지 | 전역 변수 → 주입(의존성 역전) |
| 작은 애그리거트 | 변경 증폭 | 큰 클래스 → 작은 애그리거트로 분리 (05-ddd 03번) |
주석이 복잡성 도구라는 점이 의외일 수 있다. Ousterhout은 "주석이 없는 코드는 복잡하다"고 본다 — "왜 이렇게 했는가"를 모르면, 변경할 때 미지(unknown unknowns)에 부딪힌다. 주석은 구현을 설명하는 게 아니라, 설계 결정의 이유와 제약을 기록한다. "여기서 분산 락을 쓰는 이유: 동시 주문 시 재고 음수 방지 (ADR-003 참조)" — 이 한 줄이 다음 개발자가 안전하게 변경할 수 있게 한다.
흙과 잡초를 구분하는 습관
"이 복잡성이 문제에서 온 것인가, 우리가 만든 것인가"를 묻는 습관이 중요하다. 동시성 제어는 흙이다 — 뽑을 수 없다. 하지만 그 동시성 제어를 4계층의 간접층으로 감싼 건 잡초다 — 뽑을 수 있다. 매 코드 리뷰에서 "이 코드는 본질적 복잡성을 다루고 있는가, 우발적 복잡성을 만들고 있는가"를 묻는 것 — 그것이 복잡성을 관리하는 출발점이다.
참고
- Brooks, Fred — "No Silver Bullet: Essence and Accidents of Software Engineering" (IEEE Computer, 1986) — 접근 2026-07-15
- Ousterhout, John — (2nd ed, 2021), Ch.1-2 (복잡성 정의, 세 형태) — 접근 2026-07-15
'Architecture & Design > Software Architecture' 카테고리의 다른 글
| Principles - 07. 오류 처리 전략 (0) | 2026.07.16 |
|---|---|
| Principles - 06. 계약 설계와 API (0) | 2026.07.16 |
| Principles - 05. 캡슐화와 정보 은닉 (0) | 2026.07.16 |
| Principles - 04. 의존성 규칙 (0) | 2026.07.16 |
| Principles - 03. 패러다임의 함의 (0) | 2026.07.16 |