"간단한 웹사이트인데 왜 6개월이나 걸려요?"
클라이언트의 당황스러운 질문입니다.
겉으로 보기엔 간단합니다.
로그인, 게시판, 결제. 끝.
그런데 실제로는?
소셜 로그인 5종, 다국어 지원, 실시간 알림, 외부 API 10개 연동, GDPR 준수, 접근성 기준 충족...
처음엔 간단해 보였던 프로젝트인데,
막상 진행하니 복잡도가 기하급수적으로 증가합니다.
"이것도 해야 하고, 저것도 해야 하고" 하며 계획이 늘어납니다.
프로젝트 성공률 통계가 충격적입니다:
- 간단한 웹사이트: 성공률 85%
- 중간 규모 시스템: 성공률 45%
- 복잡한 엔터프라이즈: 성공률 15%
왜 복잡도가 높아질수록 실패율이 기하급수적으로 증가할까요?
복잡도 이론(Complexity Theory)이 답을 제공합니다.
복잡도는 단순히 "어렵다"가 아닙니다.
시스템의 상태 공간이 폭발적으로 증가하여 예측과 제어가 불가능해지는 현상입니다.
복잡도의 4가지 차원
1. 구조적 복잡도 (Structural Complexity)
컴포넌트 수와 그들 간의 연결을 측정합니다.
복잡도 = 노드 수 × 엣지 수²
10개 모듈이 서로 완전히 연결되면:
- 연결 수: 10 × 9 / 2 = 45개
- 가능한 상호작용: 2^45 = 35조 가지
한 모듈 수정이 어디에 영향을 줄지 예측 불가능합니다.
2. 인지적 복잡도 (Cognitive Complexity)
인간이 한 번에 이해할 수 있는 정보량은 제한적입니다. Miller의 법칙에 따르면 7±2개가 한계입니다.
# 인지적 복잡도가 높은 코드
if user.age > 18 and user.country == 'KR' and \
(user.subscription == 'premium' or user.trial_end > now) and \
not user.blocked and user.email_verified and \
(user.payment_method or user.corporate_account):
# 이 조건이 무엇을 의미하는지 한 번에 파악 불가능
process_order()
# 인지적 복잡도를 낮춘 코드
is_adult = user.age > 18
is_korean = user.country == 'KR'
has_valid_subscription = user.subscription == 'premium' or user.trial_end > now
is_active = not user.blocked and user.email_verified
has_payment = user.payment_method or user.corporate_account
if is_adult and is_korean and has_valid_subscription and is_active and has_payment:
process_order()
3. 동적 복잡도 (Dynamic Complexity)
시간에 따른 변화와 피드백 루프를 의미합니다.
예시: 기술 부채의 복리 효과
- 1개월차: 작은 해킹 하나
- 3개월차: 해킹 위에 해킹 5개
- 6개월차: 더 이상 수정 불가능한 스파게티 코드
복잡도가 시간에 따라 지수적으로 증가합니다.
4. 조직적 복잡도 (Organizational Complexity)
콘웨이의 법칙: "시스템 구조는 조직 구조를 따라간다"
팀 구조:
Frontend팀 - Backend팀 - DB팀 - DevOps팀
결과:
→ 4개의 분리된 시스템
→ 12개의 통신 채널
→ 16개의 회의
→ 64개의 문서
팀이 늘어날수록 통신 오버헤드가 n²으로 증가합니다.
복잡도 측정 방법
1. 순환 복잡도 (Cyclomatic Complexity)
코드의 분기 수를 측정합니다:
def calculate_price(user, product, coupon=None):
price = product.price
if user.is_premium: # +1
price *= 0.8
if product.is_on_sale: # +1
price *= 0.9
if coupon: # +1
if coupon.is_valid(): # +1
price -= coupon.amount
if price < 0: # +1
price = 0
return price
# 순환 복잡도 = 6 (분기 수 + 1)
기준:
- 1-10: 단순, 이해하기 쉬움
- 11-20: 복잡, 주의 필요
- 21+: 매우 복잡, 리팩토링 필수
2. 결합도와 응집도
결합도 (Coupling): 모듈 간 의존성
낮은 결합도 (좋음): A → Interface ← B
높은 결합도 (나쁨): A ↔ B ↔ C ↔ D
응집도 (Cohesion): 모듈 내 요소들의 관련성
높은 응집도 (좋음): UserService (로그인, 회원가입, 프로필)
낮은 응집도 (나쁨): UtilService (로그인, 이메일, 파일업로드)
3. 네트워크 밀도
밀도 = 실제 연결 수 / 가능한 최대 연결 수
10개 모듈, 20개 연결:
밀도 = 20 / 45 = 0.44 (44%)
밀도 > 50%면 과도하게 연결된 시스템
복잡도 관리 전략
1. 분할 정복 (Divide and Conquer)
큰 시스템을 작은 독립적 모듈로 분해합니다:
모놀리식 (복잡도: 1000)
↓
마이크로서비스 5개 (각 복잡도: 50)
총 복잡도: 250 (75% 감소)
2. 추상화 계층 도입
복잡한 세부사항을 숨깁니다:
// 복잡한 직접 호출
const result = await fetch('/api/v1/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
'X-Request-ID': generateId(),
},
body: JSON.stringify(userData),
});
// 추상화된 호출
const result = await api.createUser(userData);
3. 표준화와 패턴
반복되는 문제에 검증된 해결책을 적용합니다:
- 디자인 패턴: Singleton, Factory, Observer
- 아키텍처 패턴: MVC, MVP, MVVM
- 코딩 컨벤션: 일관된 스타일 가이드
4. 복잡도 예산 설정
프로젝트 시작 시 복잡도 한계를 정합니다:
complexity_budget:
cyclomatic_complexity: 15
coupling_limit: 0.3
max_file_lines: 300
max_function_lines: 50
max_parameters: 4
한계를 넘으면 리팩토링을 강제합니다.
복잡도와 프로젝트 실패의 관계
실패 확률 공식
실패 확률 = 1 - (1 / e^(복잡도 지수))
복잡도 지수 1: 실패 확률 63%
복잡도 지수 2: 실패 확률 86%
복잡도 지수 3: 실패 확률 95%
복잡도가 선형적으로 증가하면, 실패 확률은 지수적으로 증가합니다.
복잡도 폭발 시나리오
- 초기 (1개월): "간단한 기능 추가"
- 중기 (3개월): "예상보다 연결이 많네"
- 후기 (6개월): "어디를 건드려도 다른 곳이 깨짐"
- 붕괴 (9개월): "처음부터 다시 만들자"
실전 복잡도 관리 체크리스트
설계 단계
- 모듈 수를 최소화했는가?
- 의존성을 단방향으로 설계했는가?
- 순환 의존성이 없는가?
- 각 모듈의 책임이 명확한가?
구현 단계
- 함수가 한 가지 일만 하는가?
- 중첩 깊이가 3단계 이하인가?
- 조건문이 5개 이하인가?
- 파라미터가 4개 이하인가?
유지보수 단계
- 복잡도 메트릭을 모니터링하는가?
- 정기적으로 리팩토링하는가?
- 기술 부채를 관리하는가?
- 문서를 최신으로 유지하는가?
핵심 정리
"복잡한 시스템은 실패하도록 설계되어 있다" - 찰스 페로
모든 프로젝트는 복잡해집니다.
피할 수 없습니다.
하지만 관리할 수는 있습니다.
복잡도를 측정하고, 한계를 설정하고, 지속적으로 단순화하세요.
완벽하게 단순한 시스템은 없지만, 관리 가능한 수준의 복잡도는 유지할 수 있습니다.
기억하세요.
복잡도와의 싸움은 한 번에 끝나는 것이 아닙니다.
매일, 매 코드 리뷰, 매 설계 결정에서 복잡도를 의식하고 관리해야 합니다.
다음 프로젝트부터 복잡도를 측정하는 것부터 시작해보세요.
작은 노력이 큰 차이를 만듭니다.
복잡한 프로젝트를 체계적으로 관리하고 싶으신가요? Plexo를 확인해보세요.
댓글 없음:
댓글 쓰기