사이클로매틱 복잡도

AI
gemma-4-31b
작성자
익명
작성일
2026.08.05
조회수
None
버전
v1

사이클로매틱 복잡도 (Cyclomatic Complexity)

1. 개요

사이클로매틱 복잡도(Cyclomatic Complexity)란 프로그램의 소스 코드 내에 존재하는 독립적인 실행 경로의 수를 측정하여 소프트웨어의 논리적 복잡성을 정량화한 지표이다. 1976년 토마스 맥케이브(Thomas J. McCabe)에 의해 제안되었으며, 소프트웨어 공학에서 코드의 유지보수성, 테스트 가능성, 그리고 잠재적 결함 가능성을 예측하는 핵심 지표로 활용된다.

복잡도가 높다는 것은 코드 내에 조건문과 반복문이 많아 실행 경로가 다양함을 의미하며, 이는 곧 개발자가 코드를 이해하는 데 더 많은 인지적 노력이 필요하고 테스트해야 할 케이스가 증가하여 버그 발생 확률이 높아짐을 시사한다.

2. 계산 원리 및 공식

사이클로매틱 복잡도는 프로그램의 논리 구조를 제어 흐름 그래프(Control Flow Graph, CFG)로 변환하여 계산한다. CFG는 프로그램의 실행 흐름을 노드(Node, 실행 단위)와 엣지(Edge, 흐름의 방향)로 표현한 방향 그래프이다.

2.1 수학적 공식

가장 일반적인 계산 공식은 다음과 같다.

$$M = E - N + 2P$$

변수 의미 설명 예시
$M$ 사이클로매틱 복잡도 최종적으로 계산된 복잡도 수치 $M = 5$
$E$ 엣지(Edges) 노드와 노드를 잇는 화살표의 수 (흐름의 수) 10개의 경로
$N$ 노드(Nodes) 처리 단계 또는 결정 지점의 수 7개의 실행 블록
$P$ 연결 성분(Connected Components) 분석 대상이 되는 모듈/함수의 수 (통상 1) 단일 함수 분석 시 $P=1$

간편 계산법: 실제 개발 현장에서는 그래프를 그리지 않고 조건부 연산자(Predicate)의 수에 1을 더하는 방식으로 빠르게 계산한다. - $\text{복잡도} = (\text{if, while, for, case, \&\&, || 등 조건부 연산자의 총 개수}) + 1$ - 주의: if (a && b)와 같이 하나의 조건문에 논리 연산자가 포함된 경우, 이는 논리적으로 두 개의 결정 지점을 가지므로 복잡도가 증가한다.

2.2 CFG 시각화 및 예시

다음은 간단한 조건문이 포함된 코드의 CFG 구조이다.

예시 코드:

function checkValue(x) {
    if (x > 0) {          // 결정 지점 1
        console.log("Positive");
    } else {
        console.log("Non-positive");
    }
    return x;
}

CFG 다이어그램 (개념도):

graph TD
    A((Start)) --> B{x > 0}
    B -- True --> C[print Positive]
    B -- False --> D[print Non-positive]
    C --> E((End))
    D --> E
- 분석: 엣지($E$) = 4, 노드($N$) = 4, 연결 성분($P$) = 1 - 계산: $M = 4 - 4 + 2(1) = 2$ - 간편법: 조건부 연산자(if) 1개 + 1 = 2

3. 복잡도 측정 예시

3.1 복잡도가 낮은 코드 (Low Complexity)

단순한 선형 구조의 코드는 경로가 하나뿐이므로 복잡도가 낮다.

function greet(name) {
    const message = "Hello, " + name;
    console.log(message);
    return message;
}
- 분석: 결정 지점 없음 $\rightarrow$ $0 + 1 = 1$ - 결과: $M = 1$ (매우 단순함)

3.2 복잡도가 높은 코드 (High Complexity)

중첩된 조건문과 반복문이 포함된 경우 경로가 기하급수적으로 증가한다.

function checkAccess(user, resource) {
    if (user !== null) {           // 결정 지점 1
        if (user.isAdmin) {        // 결정 지점 2
            return "Full Access";
        } else if (user.isEditor) { // 결정 지점 3
            if (resource.isLocked) { // 결정 지점 4
                return "Read Only";
            }
            return "Edit Access";
        } else {
            return "Guest Access"; // else는 결정 지점에 포함되지 않음
        }
    } else {
        return "No User";          // else는 결정 지점에 포함되지 않음
    }
}
- 분석: ifelse if 문이 총 4개 존재함. (else는 새로운 경로를 생성하는 결정 지점이 아니라 기존 결정의 반대 경로이므로 카운트하지 않음) - 결과: $4 + 1 = 5$ (적정 수준이나, 중첩도가 높아 가독성이 떨어짐)

4. 복잡도 수치별 해석 기준

일반적으로 업계에서 통용되는 복잡도 수치에 따른 위험도 평가 기준은 다음과 같다.

복잡도 수치 ($M$) 위험도 해석 및 권장 조치
$1 \sim 10$ 낮음 (Low) 단순하고 테스트하기 쉬운 코드. 유지보수 가능.
$11 \sim 20$ 보통 (Moderate) 복잡도가 다소 높음. 리팩토링을 고려할 수 있음.
$21 \sim 50$ 높음 (High) 매우 복잡함. 테스트가 어려우며 버그 발생 가능성 높음. 반드시 리팩토링 필요.
$50$ 초과 매우 높음 (Very High) 테스트 불가능 수준. 코드의 전면적인 재작성 권장.

5. 복잡도 감소 전략 (리팩토링)

5.1 가드 절(Guard Clause) 도입

중첩된 if-else 구조를 제거하여 코드의 깊이를 줄이는 방법이다.

리팩토링 전 (중첩 구조):

function process(data) {
    if (data != null) {
        if (data.isValid) {
            // 핵심 로직 수행
            return true;
        }
    }
    return false;
}

리팩토링 후 (가드 절 적용):

function process(data) {
    if (data == null) return false; // 조기 리턴
    if (!data.isValid) return false; // 조기 리턴
    
    // 핵심 로직 수행
    return true;
}
- 효과: 논리적 복잡도(Cyclomatic Complexity) 수치는 동일하게 유지될 수 있으나, 인지 복잡도(Cognitive Complexity)와 시각적 깊이가 감소하여 유지보수성이 향상된다.

5.2 함수 추출 및 전략 패턴

거대한 switch 문이나 복잡한 조건 분기를 별도의 함수나 클래스로 분리한다. - 함수 추출: 특정 조건 분기 로직을 isValidUser()와 같은 명명된 함수로 분리. - 전략 패턴(Strategy Pattern): 조건에 따라 실행할 로직을 객체화하여 if-else를 제거하고 다형성을 활용.

6. 정적 분석 도구 활용법

수동으로 복잡도를 계산하는 것은 비효율적이므로, 현대적인 개발 환경에서는 정적 분석 도구(Static Analysis Tool)를 사용하여 자동 측정한다.

  • 주요 도구:
    • SonarQube: 전사적 코드 품질 관리 플랫폼으로, 사이클로매틱 복잡도를 자동으로 계산하여 대시보드에 표시한다.
    • ESLint (JavaScript): complexity 규칙을 설정하여 특정 수치(예: 10)를 초과하는 함수에 경고를 표시할 수 있다.
    • JaCoCo (Java): 테스트 커버리지와 함께 복잡도를 분석하여 테스트가 부족한 복잡한 구간을 찾아낸다.
    • CodeClimate: 코드의 유지보수성 지수(Maintainability Index)와 함께 복잡도를 측정하여 리포트를 제공한다.
    • ** Lizard (Multi-language):** C/C++, Java, Python 등 다양한 언어를 지원하는 가벼운 복잡도 측정 도구이다.
  • 활용 프로세스: CI/CD 파이프라인 $\rightarrow$ 정적 분석 단계 $\rightarrow$ 복잡도 임계치 초과 시 빌드 실패(Fail) $\rightarrow$ 개발자 리팩토링 수행.

7. 추가 고려 사항

7.1 테스트 케이스 최소 개수와의 관계

사이클로매틱 복잡도는 기초 경로 테스트(Basis Path Testing)의 이론적 근거가 된다. $M$의 수치는 해당 함수 내의 모든 실행 경로를 최소 한 번씩 실행하기 위해 필요한 최소 테스트 케이스의 개수와 일치한다. - 예: 복잡도가 5인 함수는 최소 5개의 독립적인 테스트 케이스가 있어야 모든 분기점을 커버할 수 있다.

7.2 언어별 복잡도 계산 차이점

언어의 문법적 특성에 따라 결정 지점을 산정하는 기준이 조금씩 다르다. - Java/C#: switch-case 문에서 각 case를 하나의 결정 지점으로 계산한다. - Python: 리스트 컴프리헨션(List Comprehension) 내의 if 문이나 lambda 식의 조건문을 복잡도에 포함시키는지 도구마다 설정이 다르다. - 함수형 언어: 재귀 호출이나 고차 함수(map, filter, reduce)를 사용할 경우, 전통적인 CFG 기반의 복잡도 측정 방식으로는 실제 논리적 복잡성을 충분히 반영하지 못하는 경향이 있다.

8. 한계점 및 보완 지표

사이클로매틱 복잡도는 '경로의 수'라는 구조적 측면에만 집중하기 때문에 다음과 같은 한계가 있다. 1. 논리적 난이도 무시: 단순한 if 문 10개와 매우 복잡한 단일 조건식(AND/OR 결합) 1개를 동일하게 취급하는 경향이 있다. 2. 데이터 흐름 간과: 변수의 상태 변화나 재귀 호출로 인한 복잡성을 측정하지 못한다.

이를 보완하기 위해 인지 복잡도(Cognitive Complexity) 지표가 제안되었다. 인지 복잡도는 단순히 경로의 수를 세는 것이 아니라, 중첩 수준(Nesting Level)에 가중치를 부여하여 "사람이 코드를 읽을 때 얼마나 어렵게 느끼는가"를 측정하는 데 중점을 둔다.

AI 생성 콘텐츠 안내

이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.

주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.

이 AI 생성 콘텐츠가 도움이 되었나요?