[pl] 현대 소프트웨어 개발에서의 진정한 코드 복잡성 분석 및 관리 전략

@codemaru · April 13, 2025 · 85 min read

진정한 코드 복잡성이 뭔지 고민이 많다는 친구의 말에 심심해서 돌려본 딥리서치 자료. 현업에서 일하는 실무자의 관점에서는 사실 이건 포르노랑 비슷한 측면이 있다. 정의해서 측정하기는 어렵겠지만 보면 알 수 있다. 이게 복잡한 코드인지 아닌지?!

I shall not today attempt further to define the kinds of material I understand to be embraced within that shorthand description ['hard-core pornography']; and perhaps I could never succeed in intelligibly doing so. But I know it when I see it...

나는 오늘 그 약칭 표현 [‘하드코어 포르노’]에 포함된다고 여겨지는 자료의 종류를 더 이상 정의하려고 시도하지 않을 것이다. 아마도 나는 그것을 명확하게 정의하는 데 결코 성공하지 못할지도 모른다. 하지만 직접 보면 안다.

— Justice Potter Stewart, concurring opinion in Jacobellis v. Ohio, 378 U.S. 184 (1964)


서론

소프트웨어 공학 분야에서 코드 복잡성을 이해하고 관리하는 것은 지속적인 과제이다. 특히 30년 이상 전에 정의된 전통적인 복잡성 측정 지표들이 현대 소프트웨어 개발 환경의 복잡다단한 현실을 제대로 반영하지 못한다는 인식이 확산되고 있다.1 새로운 프로그래밍 패러다임, 언어의 발전, 분산 아키텍처의 보편화 등은 코드의 구조적 측면뿐만 아니라 인지적 부하, 유지보수성, 시스템 전체의 상호작용 등 다양한 차원에서 복잡성을 야기한다.2

본 보고서는 이러한 문제의식에 기반하여 '진정한' 코드 복잡성의 다면적 본질을 심층적으로 분석하고자 한다. 먼저 순환 복잡도(Cyclomatic Complexity)와 코드 라인 수(Lines of Code)와 같은 전통적인 측정 지표의 정의와 한계를 비판적으로 검토한다. 이어서 인지 복잡성(Cognitive Complexity), 유지보수성(Maintainability), 가독성(Readability), 테스트 용이성(Testability) 등 현대적인 관점에서 복잡성을 재조명한다. 또한, 마이크로서비스와 모놀리식 아키텍처, 디자인 패턴, 코드 의존성 및 결합도와 같은 시스템 수준의 요인들이 복잡성에 미치는 영향을 분석한다. 나아가 문제 영역 자체의 본질적 복잡성(Essential Complexity)과 개발 과정에서 발생하는 우발적 복잡성(Accidental Complexity)을 구분하고, 이러한 다양한 관점을 종합하여 오늘날 코드 복잡성을 구성하는 요소들에 대한 다각적인 이해를 구축한다. 최종적으로, 현대 소프트웨어 개발 환경에서 코드 복잡성을 효과적으로 관리하기 위한 실용적인 전략과 모범 사례를 제시하는 것을 목표로 한다. 이는 단순히 과거 지표의 부적절함을 지적하는 것을 넘어, 현대 소프트웨어의 특성을 반영하는 복잡성에 대한 깊이 있는 통찰과 실질적인 관리 방안을 모색하는 과정이 될 것이다.

I. 전통적 관점: 순환 복잡도와 코드 라인 수

소프트웨어 복잡성을 정량화하려는 초기 시도는 주로 코드의 구조적 특성에 초점을 맞추었다. 대표적인 지표인 순환 복잡도와 코드 라인 수는 수십 년간 널리 사용되어 왔으며, 특정 측면에서 유용성을 인정받았다.

가. 순환 복잡도(Cyclomatic Complexity, CC): 기원, 원리, 목적

순환 복잡도는 1976년 Thomas J. McCabe, Sr.에 의해 개발된 정량적 소프트웨어 메트릭이다.4 이는 프로그램 소스 코드를 통한 선형적으로 독립적인 경로의 수를 측정하여 프로그램의 복잡성을 나타낸다.4 McCabe는 모듈화의 정량적 기반을 제공하고 테스트하거나 유지보수하기 어려운 소프트웨어 모듈을 식별할 수 있는 수학적 기법의 필요성을 인식했다.7

순환 복잡도는 제어 흐름 그래프(Control Flow Graph)를 사용하여 시각화될 수 있으며, 여기서 노드는 명령어 블록을, 엣지는 제어 흐름을 나타낸다.6 계산 공식은 일반적으로 M = E - N + 2P (E: 엣지 수, N: 노드 수, P: 연결된 컴포넌트 수)로 표현되거나, 단일 진입점과 단일 탈출점을 가진 구조적 프로그램의 경우 M = 결정 지점(Decision Points) 수 + 1로 단순화될 수 있다.4 예를 들어, 단일 조건의 if 문은 두 개의 경로(참/거짓)를 생성하므로 복잡도는 2가 되며, 중첩된 if 문이나 두 개의 조건을 가진 if 문은 복잡도 3을 생성한다.4 McCabe는 원래 1-10은 단순하고 위험이 적음, 11-20은 더 복잡하고 중간 정도 위험, 21-50은 복잡하고 높은 위험, 50 초과는 테스트 불가능하고 매우 높은 위험으로 분류했으며, 이 임계값은 여전히 참조된다.4

McCabe의 주된 목적은 테스트 및 유지보수가 어려운 모듈을 식별하는 것이었다.4 특히 순환 복잡도는 필요한 최소 테스트 케이스 수, 즉 모든 실행 경로를 최소 한 번 이상 커버하기 위해 필요한 테스트 수를 나타내는 지표로 강하게 연관되어 테스트 노력과 밀접한 관련이 있다.5

나. 코드 라인 수(Lines of Code, LOC): 크기의 척도, 반드시 복잡성은 아님

코드 라인 수(LOC 또는 SLOC)는 소스 텍스트의 라인 수를 세어 소프트웨어의 크기를 측정하는 가장 기본적인 방법이다.10 주석이나 빈 줄을 포함하는 물리적 LOC와 실제 실행 가능한 구문만 계산하는 논리적 LOC로 구분될 수 있으며, 프로그래밍 언어, 코딩 스타일, 계산 방식에 따라 값이 크게 달라질 수 있다.11 예를 들어, 동일한 기능을 구현하더라도 Python은 Java나 C++보다 훨씬 적은 LOC를 사용할 수 있다.12

역사적으로 LOC는 COCOMO와 같은 노력 추정 모델의 입력 값이나 유사한 프로젝트 간의 크기 비교 기준으로 사용되었다.10 또한, 테스트 커버리지 밀도(테스트된 LOC / 전체 LOC)를 계산하는 데 활용되기도 한다.10

흥미롭게도, McCabe는 순환 복잡도를 제안할 당시 LOC를 복잡성 측정 지표로 사용하는 것을 명시적으로 거부했다. 그는 코드 길이와 모듈의 제어 흐름 복잡성 사이에 명확한 관계가 없다고 보았다.7

다. 초기 가치 인식 및 활용

이러한 전통적인 메트릭들은 초기에 상당한 가치를 인정받았다. 순환 복잡도는 위험 평가 도구로 빠르게 채택되었다. 높은 CC 값은 높은 결함 밀도와 상관관계가 있는 것으로 나타났으며 9, 코드 검토와 같은 추가적인 품질 보증 활동이 필요한 위험한 모듈을 식별하는 데 사용되었다.9

개발 지침으로서, McCabe는 개발 중에 모듈의 CC를 특정 임계값(종종 10 또는 15) 이하로 제한하여 복잡한 함수를 더 작고 관리하기 쉬운 단위로 분할하도록 권장했다.4 이는 일부 개발 방법론에서 수용된 관행이 되었다.4

테스팅 영역에서 CC는 기반 경로 테스팅(basis path testing)에 필요한 최소 테스트 케이스 수를 직접적으로 알려주는 중요한 지표였다.5 이는 테스트 범위를 정량화하고 테스트 노력을 계획하는 데 도움을 주었다.6

LOC 역시 복잡성 측정에는 한계가 있지만, 유사한 환경에서 구축된 애플리케이션 간의 상대적 크기를 가늠하거나 10 전체 코드베이스 대비 테스트 커버리지 비율을 파악하는 데 여전히 유용하게 사용된다.10

이러한 전통적인 메트릭들은 구조적 프로그래밍이 지배적이던 시대에 테스트, 유지보수 노력, 위험 관리와 같은 소프트웨어 개발 프로세스의 특정 측면을 정량화하려는 요구에서 비롯되었다. 그 기반은 주로 코드의 구조적 특성과 정량적 계산에 있으며 4, 이는 당시 중요하게 여겨졌던 절차적 코드 구조의 복잡성을 통제하는 데 어느 정도 기여했다. 하지만 초기부터 이러한 메트릭들이 완벽한 지표가 아니라는 암묵적인 이해가 존재했다. 예를 들어, McCabe 자신이 switch 문이 개념적 어려움을 반드시 증가시키지 않으면서도 CC를 부풀릴 수 있음을 인정하고 예외를 제안한 것은 9 순전히 구조적인 관점의 한계를 시사하며 후대의 비판을 예고하는 것이었다.

II. 기반의 균열: 전통적 메트릭의 한계

수십 년간 활용되어 온 순환 복잡도(CC)와 코드 라인 수(LOC)는 현대 소프트웨어 개발의 복잡성을 포착하는 데 점차 한계를 드러내고 있다. 이는 메트릭 자체의 내재적 문제와 더불어 소프트웨어 개발 환경의 급격한 변화에서 기인한다.

가. 메트릭과 현실의 간극: 인지 부하, 중첩, 구조 무시

순환 복잡도의 맹점: CC의 가장 큰 비판점 중 하나는 인간의 코드 이해 과정, 즉 인지적 부하를 제대로 반영하지 못한다는 것이다. 동일한 CC 값을 가진 두 함수가 이해도 측면에서는 현저히 다를 수 있다.1 대표적인 예로, 단순한 switch-case 문과 복잡하게 중첩된 논리(예: 버블 정렬 알고리즘)가 비슷한 CC 점수를 받을 수 있지만, 후자가 훨씬 이해하기 어렵다.1 특히 CC는 코드의 중첩 깊이(nesting depth)를 적절히 반영하지 못하는데 1, 깊은 중첩은 인간의 작업 기억(working memory)에 큰 부담을 주어 코드 추적을 어렵게 만들고 오류 발생 가능성을 높인다.1 또한, CC는 코드 구조 개선 노력이나 goto와 같은 비구조적 기법 사용 여부에 둔감하다.7

LOC의 피상성: LOC는 복잡성이나 생산성을 측정하는 데 매우 부적절한 지표로 널리 인식된다.10 언어 간의 표현력 차이로 인해 동일 기능도 LOC가 크게 달라질 수 있으며 (예: Python vs. Java) 11, 숙련된 개발자는 종종 더 적은 코드로 더 많은 기능을 효율적으로 구현한다 ('양보다 질').12 오히려 리팩토링을 통해 LOC를 줄이는 것이 코드 품질을 향상시키는 긍정적인 활동인 경우가 많다.13

예측력 부족: 여러 연구와 경험적 증거들은 CC나 LOC가 실제 인지되는 복잡성, 유지보수 문제, 심지어 결함 예측력 측면에서도 다른 요인이나 새로운 메트릭에 비해 상관관계가 약하거나 부족함을 시사한다.1 CodeScene의 머신러닝 알고리즘이 CC의 예측 가치를 낮게 평가한 사례나 1, 일부 연구에서 결함 예측에 LOC가 CC보다 더 나은 성능을 보였다는 결과는 7 이러한 한계를 뒷받침한다.

나. 현대적 맥락의 도전: 패러다임, 언어, 규모

새로운 패러다임: 함수형 프로그래밍(작고 조합 가능한 함수), 비동기 프로그래밍(콜백, Promise, async/await), 반응형 프로그래밍, 이벤트 기반 아키텍처 등 현대적인 프로그래밍 패러다임은 단순한 경로 계산이나 라인 수 계산으로는 포착하기 어려운 종류의 복잡성을 도입한다.3 재귀적이거나 멀티스레딩 코드의 복잡성은 눈에 보이는 구조보다는 실행의 재귀적 또는 병렬적 특성에 있다.2

언어의 진화: 현대 언어들은 리스트 컴프리헨션, 람다 표현식 등 강력하고 표현력 있는 기능을 제공한다. 이는 LOC를 줄일 수 있지만, 복잡한 로직을 몇 줄에 응축시켜 전통적인 메트릭이 시사하는 것과는 다른 방식으로 가독성에 영향을 미칠 수 있다.12 또한, 현대 언어의 다양한 구문으로 인해 코드를 제어 흐름 그래프로 명확하게 매핑하는 것이 모호해질 수 있다.7

규모와 상호연결성: 현대 소프트웨어 시스템은 방대한 코드베이스를 가지며 2, 마이크로서비스와 같이 분산된 컴포넌트 간의 복잡한 상호작용을 포함하는 경우가 많다.3 이러한 시스템의 전체 복잡성은 개별 모듈의 내부 복잡성(CC나 LOC로 측정되는)보다는 모듈 간의 의존성, 인터페이스, 통신 방식에서 더 크게 비롯된다.19

프레임워크와 라이브러리: 프레임워크와 외부 라이브러리에 대한 높은 의존성은 애플리케이션 자체의 CC나 LOC에는 반영되지 않는 또 다른 차원의 복잡성을 추가한다. 프레임워크의 동작 방식을 이해하고, 설정을 관리하며, 의존성 충돌을 해결하는 등의 작업은 상당한 노력을 요구한다.20

다. 오용과 오해의 위험

생산성 착각: LOC를 개발자 생산성 측정에 사용하는 것은 매우 잘못된 접근이라는 강력한 공감대가 형성되어 있다.10 이는 버그 수정처럼 적은 라인 변경에 많은 시간이 소요되는 복잡한 작업의 가치를 무시하며 10, 단순히 코드 라인 수를 늘리려는 부정적인 행동을 조장할 수 있다.16

메트릭 게임: LOC나 심지어 CC 같은 메트릭을 성과 평가와 직접 연동하면, 개발자들은 메트릭 점수를 높이기 위해 코드를 불필요하게 장황하게 작성하거나, 리팩토링을 피하거나, 의미 없는 변경을 자주 하는 등의 행동을 할 수 있다.16 이는 오히려 코드 복잡성을 증가시키고 기술 부채를 쌓는 결과를 초래할 수 있다.16

과도한 단순화: CC나 LOC의 임계값을 코드 품질의 절대적인 기준으로 삼는 것은 위험하다. 이는 코드의 가독성, 유지보수성, 설계의 적절성, 프로젝트의 특정 맥락 등 중요한 질적 측면을 간과하게 만든다.8 메트릭은 판단을 돕는 도구일 뿐, 판단 자체를 대체해서는 안 된다.8

전통적인 메트릭의 핵심 한계는 코드의 *구문적 구조(syntactic structure)*에 초점을 맞추고, 코드의 *의미(semantic meaning)*나 이를 이해하는 데 필요한 *인지적 노력(cognitive effort)*을 간과한다는 점에서 비롯된다. 이러한 불일치는 다양한 형태의 추상화와 조합을 우선시하는 현대 프로그래밍 관행 하에서 더욱 두드러진다. CC는 제어 구조(IF, LOOP 등)에 기반한 경로를 계산하고 1, LOC는 단순히 라인 수를 센다.10 둘 다 구문적이다. 반면, 이들에 대한 비판은 꾸준히 '이해도'를 제대로 측정하지 못한다는 점을 지적하는데 1, 이는 인지적, 의미적 문제이다. 예를 들어 함수형 프로그래밍은 함수당 CC는 낮을 수 있지만, 그 조합을 이해하기 위해서는 다른 종류의 인지 능력이 요구될 수 있다.17 현대 소프트웨어가 목표를 달성하는 방식 자체가 CC가 설계되었던 시대의 단순한 절차적 제어 흐름과 달라지면서, 이들 메트릭의 부적절성이 커지는 것이다.

더 나아가, LOC와 같은 단순한 메트릭을 생산성 측정에 오용하는 현상은 소프트웨어 공학의 근본적인 도전 과제, 즉 '가치 창출'을 측정하는 어려움을 반영한다. 소프트웨어 개발은 본질적으로 창의적인 문제 해결 활동이므로, 자동화하기 쉽지만 피상적인 지표로 결과물을 정량화하려는 시도는 종종 역효과를 낳는다. 잘못된 행동을 유도하고 잠재적으로 품질을 저해하는 것이다.16 이는 쉽게 측정 가능한 결과물(타이핑된 라인 수)과 실제 목표(문제를 효과적이고 유지보수 가능하게 해결하는 것) 사이의 괴리를 보여준다. 따라서 의미 있는 평가는 정량화하기는 더 어렵지만 더 관련성 높은 결과(요구사항 충족 22, 전달된 가치 23)와 질적 측면(유지보수성, 설계)을 고려해야 함을 시사한다.

결국 CC와 LOC의 한계는 단순히 기술적인 문제를 넘어, 업계가 중요하게 여기는 가치의 역사적 변화를 반영한다. 초기에는 절차적 복잡성을 제어하고 기본적인 테스트 용이성을 확보하는 데 중점을 두었다. 현대에는 대규모 시스템 관리, 다양한 팀을 위한 인지 부하 감소, 빠른 진화에 대한 요구가 커지면서 다른 종류의 측정 기준과 관리 방식이 필요하게 되었다.

III. 코드 복잡성에 대한 현대적 관점

전통적인 메트릭의 한계가 명확해짐에 따라, 코드 복잡성을 평가하는 새로운 관점들이 주목받고 있다. 이들은 단순히 코드 구조를 넘어 인간의 인지 과정, 유지보수의 용이성, 효과적인 테스트 가능성 등을 복잡성의 핵심 요소로 간주한다.

가. 인지 복잡성(Cognitive Complexity): 이해도 정량화 시도

동기: 인지 복잡성은 SonarSource에 의해 개발되었으며, 순환 복잡도(CC)가 코드의 '이해도'를 측정하는 데 실패한다는 단점을 직접적으로 해결하기 위해 등장했다.25 이 메트릭의 목표는 인간이 특정 코드 조각을 읽고 이해하는 데 필요한 정신적 노력을 정량화하는 것이다.25

측정 규칙: 인지 복잡도 계산의 핵심 원칙은 다음과 같다:

  1. 가독성 있는 축약 무시: 여러 구문을 가독성 있게 하나로 압축하는 구조(예: 단순 메서드 호출)는 복잡도를 증가시키지 않는다.26
  2. 선형 흐름 중단 시 증가: 코드의 선형적 흐름을 깨는 구조(예: 루프, 조건문, switch, 예외 처리, goto, 재귀, &&나 || 같은 다중 논리 연산자)마다 복잡도가 증가한다.25
  3. 중첩 페널티: 흐름을 깨는 구조가 다른 구조 내부에 중첩될 때 추가적인 페널티가 부과된다.25 이는 CC의 주요 약점 중 하나였던 중첩 깊이 문제를 직접적으로 다룬다.

CC와의 비교: 인지 복잡도는 CC와 명확히 대비된다. CC는 테스트 용이성(최소 테스트 경로 수)에 초점을 맞추는 반면, 인지 복잡도는 가독성 및 이해도(인지적 노력)에 초점을 맞춘다.25 예를 들어, 단순한 switch 문은 CC는 높지만 인지 복잡도는 낮을 수 있으며, 반대로 기능을 작은 함수들로 분해하는 리팩토링은 CC를 증가시킬 수 있지만 인지 복잡도는 감소시킬 수 있다.1 다음 표는 두 메트릭의 주요 차이점을 요약한다.

표 1: 순환 복잡도와 인지 복잡도 비교

특징 순환 복잡도 (Cyclomatic Complexity) 인지 복잡도 (Cognitive Complexity)
주요 목표 테스트 용이성 (Testability) 이해 용이성 (Understandability)
측정 초점 제어 흐름 경로 수 (Number of control flow paths) 인지적 노력 (Cognitive effort)
주요 페널티 요인 분기 (Branches: if, loop, switch case 등) 흐름 중단 (Flow breaks) + 중첩 (Nesting)
단순 구조 처리 switch 등 단순 구조도 경로 수에 따라 높게 측정될 수 있음 1 가독성을 해치지 않는 선형 흐름 중단은 낮게 평가, 중첩에 민감 26
주요 활용 사례 테스트 계획 (최소 테스트 케이스 수 결정) 5 코드 가독성 및 유지보수성 평가, 리팩토링 대상 식별 17
참고 자료 1 25

한계: 인지 복잡도 역시 하나의 메트릭이며, 인간 인지의 모든 미묘함이나 특정 언어 기능의 어려움(예: 포인터 연산, 고급 타입 시스템)까지 포착하지는 못한다.25 여전히 구조 기반의 측정이며, 최종적인 판단에는 인간의 경험과 맥락 이해가 필수적이다.25

나. 유지보수성과 가독성을 핵심 관심사로

정의 및 중요성: 유지보수성은 코드를 수정하고 업데이트하기 쉬운 정도를, 가독성은 코드를 읽고 이해하기 쉬운 정도를 의미한다.8 이 두 요소는 소프트웨어 생명주기 전반에 걸쳐 비용, 시간, 결함률에 지대한 영향을 미치는 핵심 품질 속성이다.20 낮은 가독성과 유지보수성은 복잡성을 인지하는 주요 원인이 된다.8

복잡성과의 관계: 일반적으로 복잡성(구조적이든 인지적이든)과 가독성/유지보수성 사이에는 강한 음의 상관관계가 관찰된다. 즉, 복잡한 코드는 이해하고 안전하게 수정하며 디버깅하기 어렵다.15 연구 결과에서도 가독성과 복잡성 간의 높은 음의 상관관계(-0.974)가 보고된 바 있으며 30, 이는 복잡한 컴포넌트가 유지보수 및 이해하기 훨씬 어렵다는 것을 확인시켜 준다.30

영향 요인: 단순한 메트릭 점수 외에도 명확한 이름 규칙, 일관된 코드 스타일, 적절한 주석, 모듈화된 설계, 표준 준수, 낮은 결합도, 높은 응집도 등 다양한 요인이 가독성과 유지보수성에 영향을 미친다.8 가독성은 문제 영역 자체의 복잡성(본질적 복잡성)과 달리 엔지니어가 통제할 수 있는 '우발적 속성'으로 간주되기도 한다.30

다. 테스트 용이성: 경로 수를 넘어서

현대적 관점: 테스트 용이성은 단순히 CC가 나타내는 경로 커버리지 이상의 의미를 갖는다. 코드가 얼마나 쉽게 테스트 환경을 구성하고, 단위(unit)를 격리하여 테스트할 수 있는지에 대한 개념으로 확장된다.8

테스트 용이성을 위한 설계: 설계 결정은 테스트 용이성에 큰 영향을 미친다. 의존성 주입(Dependency Injection, DI)은 컴포넌트를 분리하고 테스트 중에 실제 의존성을 목(Mock) 객체나 스텁(Stub)으로 쉽게 교체할 수 있게 함으로써 테스트 용이성을 높이는 핵심 디자인 패턴이다.32 이는 테스트 대상 단위를 효과적으로 격리시킨다.32

모킹(Mocking): 모킹은 실제 의존성의 동작을 시뮬레이션하는 테스트 대역(test double)을 만드는 기술이다.32 DI는 이러한 목 객체를 쉽게 주입할 수 있도록 하여 테스트 코드 작성을 용이하게 한다.32

복잡성과 테스트 용이성 연관성: 복잡한 코드(높은 CC, 높은 인지 복잡도, 강한 결합, 숨겨진 의존성)는 본질적으로 철저하고 안정적으로 테스트하기 어렵다.2 테스트하기 어려운 코드는 종종 근본적인 설계 문제를 내포하고 있다는 신호일 수 있다.36

인지 복잡도의 등장은 소프트웨어 개발에서 초점이 단순히 기계가 실행 가능한 경로(CC)에서 해당 경로와 코드의 의도를 이해하는 데 필요한 인간의 인지적 노력으로 이동하고 있음을 보여주는 중요한 변화이다. 이는 코드베이스가 커지고 팀이 분산됨에 따라 코드를 이해하는 비용이 전체 개발 비용과 위험의 주요 동인이 되고 있다는 인식이 커지고 있음을 반영한다.17

또한, 테스트 용이성을 위해 설계하는 것(예: DI 사용)이 종종 더 모듈화되고, 덜 결합되며, 잠재적으로 전반적으로 덜 복잡한 설계로 이어진다는 점은 주목할 만하다. 이는 긍정적인 피드백 루프를 생성한다. 테스트하기 어렵다는 것은 종종 높은 결합도나 낮은 응집도와 같은 설계 문제의 증상이다.33 따라서 테스트 용이성을 향상시키는 관행(DI, 명확한 인터페이스)은 복잡성의 일반적인 원인(결합, 불분명한 책임)을 직접적으로 해결하는 경향이 있다.

하지만 인지 복잡도가 CC보다 이해도를 더 잘 반영하더라도 여전히 구조적 메트릭이라는 점을 인지해야 한다. 실제 인지 부하는 도메인 지식, 이름 명명의 질, 주석의 유용성, 코드의 일관성, 개별 개발자의 경험과 같은 비구조적 요인에 의해서도 영향을 받는다.2 현재의 자동화된 메트릭들은 이러한 측면을 완전히 포착하기 어렵다. 따라서 인지 복잡도와 같은 메트릭은 유용한 지표이지만, 이러한 비구조적 측면을 다루는 질적 평가(예: 코드 리뷰)와 좋은 개발 관행으로 보완되어야 한다.

IV. 더 넓은 시야: 아키텍처, 디자인, 의존성

코드 복잡성은 개별 함수나 클래스 수준을 넘어 시스템 전체의 구조와 상호작용 방식에 의해 크게 영향을 받는다. 아키텍처 선택, 디자인 패턴 적용, 그리고 코드 간의 의존성 관리는 복잡성의 성격과 위치를 근본적으로 결정짓는 요소들이다.

가. 아키텍처의 영향: 모놀리스 대 마이크로서비스

모놀리식 아키텍처(Monolithic Architecture): 단일 코드베이스와 배포 단위를 특징으로 하는 전통적인 구조이다.20 초기에는 개발, 배포, 테스트, 디버깅이 상대적으로 단순하다는 장점이 있다.38 그러나 시스템 규모가 커짐에 따라 내부 복잡성이 증가하고, 컴포넌트 간의 강한 결합으로 인해 작은 변경이 시스템 전체에 예기치 않은 영향을 미치는 파급 효과(ripple effect)가 발생하기 쉽다.38 이는 개발 속도를 저하시키고, 특정 부분만 독립적으로 확장하기 어렵게 만들며, 새로운 기술 도입에 장벽이 되기도 한다.20

마이크로서비스 아키텍처(Microservices Architecture): 애플리케이션을 작고 독립적으로 배포 가능한 서비스들의 집합으로 구성하는 접근 방식이다.37 각 서비스는 자체적인 비즈니스 로직과 데이터 저장소를 가질 수 있으며, 네트워크를 통해 잘 정의된 인터페이스로 통신한다.37 주요 장점으로는 서비스별 독립적인 확장, 배포, 기술 선택의 유연성, 높은 장애 격리(fault isolation), 자율적인 팀 구성 지원 등이 있다.19 각 서비스 내부의 코드베이스는 상대적으로 단순해질 수 있다.38

복잡성 트레이드오프(Complexity Trade-offs): 중요한 점은 마이크로서비스가 복잡성을 제거하는 것이 아니라, 복잡성의 형태를 바꾸고 이동시킨다는 것이다.38 개별 서비스의 내부 코드 복잡성은 줄어들 수 있지만, 전체 시스템 수준의 복잡성은 분산 시스템 고유의 문제들로 인해 증가한다. 여기에는 네트워크 지연 및 신뢰성 문제, 서비스 간 통신 및 데이터 일관성 유지의 어려움, 배포 및 모니터링의 운영 오버헤드 증가, 분산 트랜잭션 테스트의 복잡성, 서비스 검색(discoverability) 문제 등이 포함된다.19 따라서 마이크로서비스 전환은 단순히 기술적 유행을 따르기보다 명확한 기술적, 조직적 요구에 기반해야 하며 19, 때로는 잘 구조화된 "모듈형 모놀리스(modular monolith)"가 더 적합한 대안이 될 수도 있다.19

표 2: 아키텍처 복잡성 트레이드오프: 모놀리스 vs. 마이크로서비스

측면 모놀리식 아키텍처 (Monolithic Architecture) 마이크로서비스 아키텍처 (Microservices Architecture)
내부 코드 복잡성 높거나 증가하는 경향 20 서비스별로는 낮음 38
시스템/상호작용 복잡성 낮음 (내부 호출) 높음 (네트워크 통신, 분산 시스템 문제) 19
운영 복잡성 상대적으로 낮음 (단일 배포/모니터링 대상) 38 높음 (다수 서비스 관리, 인프라 복잡성 증가) 39
배포 복잡성 단순한 단위 38 조정된 서비스 배포 필요 39
확장성 전체 단위 확장 (Coarse-grained) 38 서비스별 독립 확장 (Fine-grained) 38
기술 유연성 낮음 (단일 기술 스택 제약) 38 높음 (서비스별 최적 기술 선택 가능) 39
팀 자율성 낮음 (코드베이스 공유 및 의존성) 높음 (독립적인 개발 및 배포 가능) 38
참고 자료 20 19

나. 디자인 패턴: 복잡성 관리 도구인가, 또 다른 복잡성인가?

잠재적 이점: 잘 알려진 디자인 패턴은 일반적인 문제에 대한 표준적이고 검증된 해결책을 제공함으로써 복잡성을 관리하는 데 도움을 줄 수 있다.8 패턴은 코드 구조를 개선하고, 재사용성을 높이며, 특정 유형의 복잡한 로직(예: 상태 패턴이나 전략 패턴을 이용한 복잡한 조건문 처리 6)을 단순화할 수 있다. 또한, 패턴은 개발자 간의 의사소통을 위한 공통 어휘를 제공한다.8

잠재적 단점: 그러나 디자인 패턴의 무분별한 적용은 오히려 복잡성을 증가시킬 수 있다. 패턴은 종종 추가적인 추상화 계층, 더 많은 클래스나 인터페이스, 간접 호출 등을 도입하여 코드 이해를 어렵게 만들 수 있다.21 필요하지 않은 곳에 패턴을 적용하는 오버 엔지니어링은 우발적 복잡성을 낳는다.21 또한, 패턴 자체를 이해하고 나중에 제거하는 것도 복잡한 작업이 될 수 있다.44 SOLID 원칙과 같은 설계 원칙들도 잘못 적용될 경우 불필요한 복잡성을 추가할 수 있다는 비판이 제기된다.21

맥락의 중요성: 패턴의 가치는 특정 문제와 맥락에 따라 크게 달라진다. 패턴이 제공하는 유연성이나 구조가 현재 및 예상되는 미래의 요구사항에 정말로 필요한지를 신중하게 평가해야 한다.44

다. 의존성의 무게: 결합도와 응집도

정의: 결합도(Coupling)는 소프트웨어 모듈 간의 상호 의존성 정도를 나타낸다. 즉, 한 모듈의 변경이 다른 모듈의 변경을 요구하는 정도이다.46 응집도(Cohesion)는 단일 모듈 내의 요소들이 얼마나 서로 관련되어 있고 단일 목적을 위해 집중되어 있는지를 나타낸다.46 일반적인 설계 목표는 낮은 결합도와 높은 응집도(Low Coupling, High Cohesion)를 추구하는 것이다.46

복잡성에 미치는 영향: 높은 결합도는 시스템 복잡성을 크게 증가시킨다. 변경 사항이 시스템 전체로 퍼져나가 예측하기 어렵고, 이해, 수정, 테스트를 어렵게 만든다.46 강하게 결합된 시스템은 변경에 취약하다. 반면, 낮은 응집도는 모듈이 서로 관련 없는 너무 많은 책임을 가지고 있음을 시사하며, 이는 모듈의 이해, 재사용, 유지보수를 어렵게 만든다.46 Feature Envy(다른 클래스의 데이터나 메서드를 과도하게 사용하는 것)나 Message Chains(객체가 다른 객체를 통해 연쇄적으로 메시지를 보내는 것)와 같은 코드 스멜(code smell)은 종종 결합도 및 응집도 문제와 관련이 있다.48

시스템 전반의 효과: 내부 모듈 간의 의존성뿐만 아니라 외부 라이브러리, 프레임워크, 다른 서비스와의 의존성도 전체 시스템 복잡성에 기여한다.21 이러한 의존성을 관리하고, 그 영향을 파악하며, 호환성을 보장하는 것은 현대 소프트웨어 개발의 주요 과제 중 하나이다.8

결합도 관리: 직접적인 결합도를 줄이기 위해 인터페이스 기반 설계, 콜백, 의존성 역전 원칙(Dependency Inversion Principle), 이벤트 기반 통신과 같은 기법들이 사용된다.34 Martin Fowler는 특히 시스템의 상위 수준 모듈 간의 결합도를 관리하는 것이 중요하다고 강조한다.47

코드 복잡성을 개별 코드 조각만으로는 완전히 이해할 수 없다는 점이 분명해진다. 아키텍처 선택(모놀리스 vs. 마이크로서비스)과 시스템 내 의존성 네트워크는 복잡성의 성격위치를 근본적으로 형성한다. 모놀리스는 복잡성을 내부로 집중시키는 경향이 있고 20, 마이크로서비스는 복잡성을 서비스 간의 상호작용과 운영 환경으로 분산시킨다.38 아키텍처와 관계없이 모듈 간의 높은 결합도는 시스템 전체의 취약성을 야기한다.46 이는 복잡성이 단순히 코드 구조의 문제가 아니라 거시적 설계 결정에 크게 영향을 받는 시스템적 속성임을 보여준다.

또한, 복잡성을 관리하기 위한 많은 기법들(마이크로서비스, 디자인 패턴, 디커플링)은 종종 트레이드오프를 수반한다. 이들은 한 종류의 복잡성(예: 내부 코드 복잡성)을 해결하는 대신 다른 종류의 복잡성(예: 운영 복잡성, 추상화 오버헤드)을 도입하는 경우가 많다.38 보편적으로 '최선'인 접근 방식은 없으며, 선택은 항상 특정 문제, 팀 역량, 장기 목표 등 맥락에 따라 이루어져야 한다. 이는 유행하는 기술을 맹목적으로 적용하기보다 신중한 분석이 필요함을 시사한다.

결합도 관리는 클래스 설계(DI, 인터페이스 34)부터 상위 모듈(Fowler 47), 아키텍처 스타일(마이크로서비스 38)에 이르기까지 다양한 추상화 수준에서 핵심 주제로 반복적으로 등장한다. 이는 의존성을 제어하는 것이 전체 시스템의 복잡성과 유지보수성을 관리하는 데 가장 중요한 요소 중 하나임을 시사한다. 이해하기 쉽고 적응 가능한 시스템을 구축하는 데 있어 의존성 관리는 근본적인 중요성을 갖는다.

V. 본질적 복잡성 대 우발적 복잡성: 노력 집중하기

소프트웨어 복잡성을 효과적으로 관리하기 위해서는 복잡성의 근원을 이해하는 것이 중요하다. Fred Brooks가 제시한 '본질적 복잡성'과 '우발적 복잡성'의 구분은 복잡성의 어떤 측면에 노력을 집중해야 하는지에 대한 전략적 틀을 제공한다.

가. Brooks의 이분법: 내재된 복잡성 대 부가된 복잡성

기원: 이 개념은 컴퓨터 아키텍트이자 튜링상 수상자인 Fred Brooks가 1986년에 발표한 영향력 있는 논문 "No Silver Bullet—Essence and Accident in Software Engineering"에서 비롯되었다.49 Brooks는 하드웨어 공학에 비해 소프트웨어 공학의 생산성 향상이 더딘 이유를 분석하면서 이 구분을 제시했다.

본질적 복잡성(Essential Complexity): 문제 영역 자체의 고유한 어려움에서 비롯되는 복잡성이다.45 이는 사용자가 요구하는 기능, 즉 소프트웨어가 반드시 수행해야 하는 작업들의 복잡성이다.51 이 복잡성은 문제를 변경하지 않는 한 제거할 수 없다.45 예를 들어, 복잡한 비즈니스 규칙, 정교한 과학적 계산, 특정 문제의 본질적인 상태 관리 요구 등이 본질적 복잡성에 해당한다.30

우발적 복잡성(Accidental Complexity): 개발 과정에서 사용되는 도구, 기술, 설계 결정 등으로 인해 부가적으로 발생하는 복잡성이다.49 이는 문제 자체에 내재된 것이 아니라, 개발자들이 해결 과정에서 의도치 않게 만들어내는 복잡성이다.49 따라서 이 복잡성은 엔지니어들이 잠재적으로 수정하거나 피할 수 있다.50 예를 들어, 과도하게 복잡한 추상화, 잘못된 설계 선택, 다루기 힘든 프레임워크, 복잡한 빌드/배포 프로세스, 불명확한 코드, 불필요한 의존성 등이 우발적 복잡성을 증가시킨다.20

나. 우발적 복잡성 인식 및 감소

Brooks의 주장: Brooks는 고급 프로그래밍 언어가 어셈블리 언어의 세부 사항을 추상화하는 등 우발적 복잡성을 줄이는 데 상당한 진전이 있었으며, 따라서 남은 과제는 주로 본질적 복잡성에 있다고 주장했다. 이 때문에 소프트웨어 생산성에서 획기적인(order-of-magnitude) 향상을 기대하기는 어렵다고 보았다.50

현대적 관련성: Brooks의 지적에도 불구하고, 현대 소프트웨어 개발에서도 여전히 상당한 우발적 복잡성이 발생하고 있다.20 부적절한 도구나 프레임워크 선택 20, 잘못된 아키텍처 결정 20, 불필요한 디커플링 21, 과도한 의존성 21, 코드의 명확성 부족 20, 시기상조의 최적화나 일반화 21, 특정 기술이나 방법론을 맹목적으로 따르는 '카고 컬트(cargo culting)' 현상 53 등이 그 원인이 될 수 있다.

노력 집중: 본질적 복잡성은 좋은 설계와 명확한 이해를 통해 관리되어야 하는 반면, 우발적 복잡성은 적극적으로 최소화해야 한다.18 이것이 엔지니어들이 유지보수성, 이해도, 생산성을 향상시키기 위해 가장 큰 영향력을 발휘할 수 있는 영역이다. 전략에는 단순화 추구, 적절한 도구 선택, 명확성 강조, 불필요한 요소 제거 등이 포함된다.21

본질적/우발적 복잡성 구분은 복잡성 관리에 중요한 전략적 틀을 제공한다. 이는 개발 과정에서 스스로 만들어낸 어려움(우발적)을 제거하는 데 노력을 기울이고, 피할 수 없는 어려움(본질적)은 효과적으로 모델링하고 관리하는 데 집중하도록 방향을 제시한다. Brooks는 대부분의 생산성 향상이 우발적 복잡성 감소에서 왔다고 주장했지만 50, 현대 개발에서도 여전히 최적이 아닌 도구, 프로세스, 설계로 인해 상당한 우발적 복잡성이 발생한다.20 따라서 본질적 복잡성을 다루는 것이 어렵다 하더라도, 부주의하게 도입된 우발적 복잡성을 부지런히 식별하고 제거하는 것만으로도 상당한 가치를 창출할 수 있다. 이것이 개선을 위한 가장 실질적인 경로이다.

하지만 본질적 복잡성과 우발적 복잡성 사이의 경계는 때때로 모호하고 맥락에 따라 달라질 수 있다. 예를 들어, 특정 복잡한 프레임워크 기능이 필요하다고 여겨지는 것이 본질적 복잡성처럼 보일 수 있다. 그러나 요구사항을 더 간단하게 충족할 수 있거나 해당 프레임워크 자체가 작업에 비해 불필요하게 복잡하다면, 이는 우발적 복잡성으로 드러날 수 있다. 이는 요구사항과 솔루션 선택을 비판적으로 평가하여 우발적 복잡성을 본질적 복잡성으로 오인하지 않도록 주의해야 함을 시사한다.

VI. 오늘날의 '진정한' 코드 복잡성 정의

전통적인 메트릭의 한계와 현대 소프트웨어 개발의 다면성을 고려할 때, '진정한' 코드 복잡성은 단일 지표로 정의될 수 없다. 이는 코드 자체의 구조적 특성뿐만 아니라, 이를 이해하고 다루는 인간의 인지 과정, 시스템 전체의 상호작용, 그리고 해결하려는 문제의 본질까지 아우르는 다차원적인 개념이다.

가. 다차원적 구성 요소: 요인 종합

단일 메트릭을 넘어서: 순환 복잡도, LOC, 심지어 인지 복잡도와 같은 어떤 단일 숫자도 '진정한' 복잡성의 전체 그림을 포착할 수는 없다.8 진정한 복잡성은 여러 요소가 복합적으로 작용한 결과이다.

종합적 정의: 현대적 관점에서 코드 복잡성을 구성하는 주요 차원들은 다음과 같이 종합될 수 있다:

  1. 구조적 복잡성 (Structural Complexity): 제어 흐름의 얽힘, 코드 구성 방식의 복잡성. 순환 복잡도, 중첩 깊이(Nesting Depth) 등이 부분적으로 측정한다.4
  2. 인지 복잡성 (Cognitive Complexity): 코드의 로직, 흐름, 의도를 파악하는 데 필요한 정신적 노력. 인지 복잡도 메트릭과 가독성 관련 요인(명명 규칙, 일관성 등)에 의해 영향을 받는다.25
  3. 상태 공간 복잡성 (State Space Complexity): 시스템 또는 컴포넌트가 가질 수 있는 가능한 상태의 수와 이를 추론하는 어려움. 복잡한 로직의 테스트 및 디버깅 논의에서 암시적으로 나타난다.15
  4. 의존성 복잡성 (Dependency Complexity): 내부 및 외부 의존성의 수, 성격, 결합 방식에서 발생하는 복잡성.8
  5. 아키텍처 복잡성 (Architectural Complexity): 선택된 아키텍처 스타일(예: 마이크로서비스의 분산 시스템 문제)에 내재된 복잡성.19
  6. 도메인 복잡성 (Domain Complexity / Essential): 해결하려는 문제 자체의 본질적인 복잡성.30
  7. 도구/환경 복잡성 (Tooling/Environment Complexity / Accidental): 빌드 시스템, 배포 파이프라인, 프레임워크 등 개발 환경에서 비롯되는 부가적인 복잡성.49

나. 현대 복잡성 평가의 핵심 요소

전체론적 관점: 복잡성을 평가할 때는 단순히 코드 구조뿐만 아니라 위에서 언급된 다양한 차원을 종합적으로 고려해야 한다.
맥락의 중요성: 복잡성의 영향은 팀의 규모와 경험, 프로젝트 목표, 예상 수명 등 맥락에 따라 달라진다.8 주니어 개발자에게 복잡한 것이 전문가에게는 단순할 수 있다.25
질적 판단: 정량적 메트릭과 함께 인간의 판단, 코드 리뷰, 도메인 전문 지식의 중요성을 간과해서는 안 된다.8
결국 '진정한' 코드 복잡성은 코드 자체의 속성이라기보다는, 코드, 코드가 속한 시스템, 코드가 해결하는 문제, 그리고 이를 개발하고 유지보수하는 인간 사이의 상호작용에서 나타나는(emergent) 속성으로 이해하는 것이 가장 적절하다. 분석 과정에서 복잡성이 코드 구조(CC), 인간의 이해(인지 복잡성), 시스템 상호작용(결합도, 아키텍처), 문제 영역(본질적 복잡성) 등 다양한 원천에서 발생함을 확인했다. 메트릭만으로는 충분하지 않으며 8, 인지 부하나 팀 구조와 같은 인간적 요소가 결정적이다.25 따라서 복잡성에 대한 정의는 이러한 구조적, 시스템적, 인지적, 맥락적 요소를 통합해야 한다.

복잡성이 다차원적이고 맥락 의존적이라는 사실은 효과적인 평가를 위해서는 단일 '궁극의' 복잡성 메트릭을 찾기보다는, 다양한 지표(CC, 인지 복잡도, 결합도 메트릭, 테스트 커버리지 등)들의 대시보드와 질적 실천(코드 리뷰, 아키텍처 리뷰)을 결합해야 함을 시사한다. 이는 다른 분야에서 복잡한 시스템을 모니터링하는 방식과 유사하다. 다양한 정량적, 질적 정보를 종합해야만 복잡성에 대한 미묘하고 정확한 이해에 도달할 수 있다.

VII. 효과적인 복잡성 관리 전략

현대 소프트웨어 개발에서 코드 복잡성을 효과적으로 관리하는 것은 단순히 좋은 코드를 작성하는 것을 넘어, 설계, 프로세스, 팀 문화 전반에 걸친 지속적인 노력을 요구한다. 복잡성의 다면적 본질을 이해했다면, 이를 제어하고 완화하기 위한 구체적인 전략을 적용하는 것이 중요하다.

가. 선제적 설계 및 아키텍처

모듈화 (Modularization): 크고 복잡한 시스템을 작고, 응집력 있으며, 느슨하게 결합된 모듈이나 서비스로 나누는 것이 기본이다.42 관심사의 분리(Separation of Concerns, SoC) 원칙을 적용하고 54, 명확한 인터페이스와 API를 설계하여 모듈 간의 상호작용을 정의해야 한다.34 이는 마이크로서비스나 모듈형 모놀리스 아키텍처와 직접적으로 연결된다.40 모듈화는 코드 재사용을 촉진하고 가독성을 높이며, 각 컴포넌트를 더 쉽게 이해하고 유지보수할 수 있게 한다.42
추상화 (Abstraction): 구현 세부 사항을 감추고 필수적인 기능만 노출하는 추상화를 효과적으로 사용해야 한다.42 캡슐화(Encapsulation)는 이를 달성하는 핵심 기법이다.54 잘 설계된 추상화는 개발자가 세부 사항에 얽매이지 않고 상위 수준에서 시스템과 상호작용할 수 있게 하여 인지 부하를 줄인다.42
적절한 패턴 사용 (Appropriate Patterns): 디자인 패턴은 그것이 실제로 문제를 단순화하고 명확하게 할 때 유용하다.8 패턴 사용 자체가 목적이 되어서는 안 되며, 불필요한 패턴 적용은 오버 엔지니어링으로 이어져 우발적 복잡성을 증가시킬 수 있다.21 패턴의 장단점을 이해하고 맥락에 맞게 신중하게 선택해야 한다.44
단순성 우선 (Simplicity Focus): 처음부터 단순한 설계를 지향하고, 불필요한 기능이나 미래에 대한 과도한 예측에 기반한 일반화를 피해야 한다.21 YAGNI("You Ain't Gonna Need It") 원칙을 따르는 것이 도움이 될 수 있다.58 꼭 필요한 경우가 아니라면 의존성 추가를 최소화해야 한다.21

나. 부지런한 코딩 및 리팩토링 관행

클린 코드 (Clean Code): 가독성 높은 코드는 복잡성 관리의 핵심이다. 의미 있는 변수 및 함수 이름 사용, 일관된 코딩 스타일 유지, 명확한 코드 구조 설계, 함수 및 메서드를 작게 유지하는 것 등이 중요하다.21 코드는 문서보다 더 정확하게 시스템의 동작을 반영하므로 21, 이해하기 쉬운 코드를 작성하는 것이 우선이다.54
리팩토링 (Refactoring): 코드 구조를 개선하고, 중복을 제거하며(DRY 원칙), 로직을 단순화하고, 복잡성 메트릭을 낮추기 위해 지속적으로 리팩토링을 수행해야 한다.5 '메서드 추출(Extract Method)' 6, '조건문 단순화' 60, '조건문을 다형성으로 대체(Replace Conditional with Polymorphism)' 6 등 구체적인 리팩토링 기법을 활용할 수 있다. 리팩토링은 기술 부채 축적을 방지하는 중요한 활동이다.6
테스트 주도 개발 (TDD/BDD): TDD와 같은 테스트 우선 접근 방식은 개발자가 코드를 사용하고 테스트하는 관점에서 먼저 생각하도록 유도하여 자연스럽게 더 단순하고 모듈화되며 테스트 가능한 코드를 작성하도록 이끌 수 있다.42
죽은/불필요한 코드 제거 (Eliminate Dead/Useless Code): 사용되지 않는 코드, 불필요한 추상화 계층(예: 단일 구현 인터페이스), 가치를 제공하지 않는 디자인 패턴 등은 시스템에 불필요한 복잡성만 더한다.21 정기적으로 이러한 코드를 식별하고 과감하게 제거해야 한다.21

다. 견고한 프로세스 및 팀 협업

코드 리뷰 (Code Reviews): 동료 검토는 복잡성을 식별하고, 개선 방안을 제안하며, 지식을 공유하고, 코딩 표준을 유지하는 데 매우 중요한 역할을 한다.6 코드 리뷰는 잠재적인 문제를 조기에 발견하고 협업을 통해 코드 품질을 향상시키는 데 기여한다.42
코딩 표준 (Coding Standards): 명확한 코딩 표준과 스타일 가이드라인을 수립하고 팀 전체가 이를 준수하도록 장려해야 한다.8 이는 코드의 일관성과 가독성을 높여 복잡성을 줄이는 데 도움이 된다.15 자동화된 린터(linter)나 포맷터(formatter)를 활용하면 표준 준수를 용이하게 할 수 있다.54
자동화 (테스팅, CI/CD): 리팩토링이나 새로운 기능 개발 시 발생할 수 있는 회귀 오류(regression)를 잡기 위해 자동화된 테스트를 적극 활용해야 한다.42 CI/CD(지속적 통합/지속적 배포) 파이프라인을 구축하여 빌드, 테스트, 배포 프로세스를 자동화하면 통합 문제를 조기에 발견하고 일관성 있고 안정적인 배포를 보장할 수 있다.57
문서화 (Documentation): 아키텍처, 복잡한 로직, API 등 필요한 부분에 대해서는 명확하고 간결한 문서를 유지해야 한다.42 하지만 가능한 경우, 코드가 스스로 설명하도록(self-documenting code) 작성하는 것을 우선시해야 한다.57
명확한 요구사항 (Clear Requirements): 모호하거나 불완전한 요구사항은 복잡하거나 불필요한 구현으로 이어질 수 있다. 프로젝트 시작 단계에서 요구사항을 명확하게 정의하는 것이 중요하다.58
측정 및 모니터링 (Measure and Monitor): 순환 복잡도, 인지 복잡도 등의 메트릭을 절대적인 목표치가 아닌, 주의가 필요한 잠재적 문제 영역(hotspot)을 식별하기 위한 신호로 사용해야 한다.5 복잡성 추세를 지속적으로 모니터링하고 분석하여 개선 기회를 찾아야 한다.6

표 3: 복잡성 관리 전략 요약

전략 범주 구체적 전략 설명 주요 관리 대상 복잡성 차원
설계/아키텍처 모듈화 (Modularization) 시스템을 작고 응집력 있으며 느슨하게 결합된 단위로 분할 54 구조적, 의존성, 인지적
추상화 (Abstraction) 구현 세부 사항 숨기고 필수 인터페이스 노출 42 인지적, 구조적
적절한 패턴 사용 (Appropriate Patterns) 문제 해결에 도움이 될 때 신중하게 패턴 적용 42 구조적, 인지적 (잘못 사용 시 우발적 증가)
단순성 우선 (Simplicity Focus) 불필요한 기능/일반화 회피, 최소 의존성 유지 21 우발적, 구조적
코딩/리팩토링 클린 코드 (Clean Code) 가독성 높은 코드 작성 (명명, 스타일, 작은 함수 등) 21 인지적, 구조적
지속적 리팩토링 (Continuous Refactoring) 코드 구조 개선, 중복 제거, 로직 단순화 15 구조적, 인지적, 우발적
TDD/BDD 테스트 우선 접근 방식으로 설계 개선 유도 42 구조적, 테스트 용이성
죽은/불필요한 코드 제거 사용되지 않거나 가치 없는 코드 제거 21 우발적, 구조적
프로세스/팀 코드 리뷰 (Code Reviews) 동료 검토를 통한 문제 식별, 지식 공유, 표준 유지 6 모든 차원 (특히 인지적, 구조적, 우발적)
코딩 표준 (Coding Standards) 일관성 및 가독성 향상을 위한 표준 수립 및 준수 57 인지적, 우발적
자동화 (Testing, CI/CD) 회귀 방지, 통합 문제 조기 발견, 안정적 배포 57 테스트 용이성, 운영 복잡성 (우발적)
문서화 (Documentation) 필요한 정보 기록, 단 코드 자체의 명확성 우선 42 인지적
명확한 요구사항 (Clear Requirements) 모호성 제거로 불필요한 복잡성 방지 58 본질적 (명확화), 우발적 (방지)
측정 및 모니터링 (Measure & Monitor) 메트릭을 신호로 활용하여 문제 영역 식별 및 추세 관리 5 구조적, 인지적 (지표로서), 관리 효율성

효과적인 복잡성 관리는 단순히 영리한 코드를 작성하는 기술적인 문제를 넘어선다. 이는 기술적 실천(설계, 코딩, 리팩토링), 프로세스 규율(리뷰, 표준, 자동화), 그리고 아키텍처에 대한 선견지명을 결합하는 전체론적인 접근 방식을 요구한다.54 단일 전략만으로는 충분하지 않으며, 소프트웨어 개발 생명주기의 여러 단계에서 상호 보완적인 노력이 필요하다.

또한, 복잡성 관리는 일회성 작업이 아니라 지속적인 활동이다. 지속적인 리팩토링, 정기적인 리뷰, 표준 준수, 시스템 진화에 따른 설계 조정 등이 필요하다.15 복잡성과 관련된 기술 부채는 적극적으로 관리하지 않으면 시간이 지남에 따라 누적된다.6

마지막으로, 많은 효과적인 전략들이 복잡성의 인간적 측면을 직접적으로 다룬다는 점을 주목해야 한다. 가독성 향상 21, 표준과 리뷰를 통한 공동 이해 증진 57, 모듈화와 추상화를 통한 인지 부하 감소 42 등이 그 예이다. 이는 코드 자체의 구조를 관리하는 것만큼이나 코드와 상호작용하는 인간의 경험을 관리하는 것이 중요함을 다시 한번 강조한다.

결론

본 보고서는 현대 소프트웨어 개발에서 '진정한' 코드 복잡성이 무엇인지에 대한 심층적인 분석을 제공하고자 했다. 30년 이상 전에 정의된 순환 복잡도나 코드 라인 수와 같은 전통적인 메트릭은 코드의 구조적 측면을 정량화하려는 초기 시도로서 가치가 있었으나, 현대 소프트웨어의 다면적인 복잡성을 포착하는 데는 명백한 한계를 지닌다. 특히 코드의 이해도와 관련된 인지적 부하, 중첩 구조의 영향, 그리고 새로운 프로그래밍 패러다임 및 아키텍처 패턴의 등장은 이러한 한계를 더욱 부각시킨다.

현대적인 관점에서 복잡성은 단순히 코드 구조의 얽힘을 넘어선다. 인지 복잡도는 코드를 이해하는 데 필요한 정신적 노력을 측정하려는 시도이며, 유지보수성, 가독성, 테스트 용이성은 복잡성이 실제 개발 및 운영 과정에 미치는 영향을 반영하는 중요한 품질 속성이다. 또한, 마이크로서비스나 모놀리스와 같은 아키텍처 선택, 디자인 패턴의 적용 방식, 그리고 시스템 내외부의 의존성 및 결합도는 개별 코드 조각의 복잡성을 넘어서는 시스템 수준의 복잡성을 결정짓는다. Fred Brooks가 제시한 본질적 복잡성과 우발적 복잡성의 구분은 우리가 통제하고 개선할 수 있는 영역(우발적 복잡성)에 노력을 집중해야 함을 시사한다.

결론적으로, 오늘날 '진정한' 코드 복잡성은 구조적, 인지적, 상태 공간적, 의존성 관련, 아키텍처적, 도메인 고유(본질적), 그리고 개발 환경(우발적) 차원들이 복합적으로 얽혀있는 다차원적 구성체이다. 이는 코드, 시스템, 문제 영역, 그리고 개발자 간의 상호작용에서 비롯되는 동적인 속성이다.

따라서 복잡성을 효과적으로 관리하기 위해서는 단일 메트릭에 의존하기보다, 다양한 지표(CC, 인지 복잡도, 결합도 메트릭 등)를 상황에 맞게 활용하고 이를 질적인 평가(코드 리뷰, 아키텍처 검토)와 결합하는 전체론적 접근이 필요하다. 선제적인 설계 원칙(모듈화, 추상화, 단순성) 준수, 부지런한 코딩 및 리팩토링 관행(클린 코드, 지속적 개선), 그리고 견고한 개발 프로세스(표준, 자동화, 협업)를 통해 우발적 복잡성을 최소화하고 본질적 복잡성을 효과적으로 다루어야 한다.

궁극적으로, 복잡성 관리는 기술적 숙련도를 넘어선 지속적인 관심과 규율을 요구하는 활동이다. 코드와 그것이 존재하는 맥락 모두에 주의를 기울이고, 명확성, 유지보수성, 그리고 팀의 이해도를 우선시하는 문화를 조성하는 것이 변화하는 소프트웨어 환경 속에서 지속 가능한 시스템을 구축하는 길이다.

참고 자료

  1. Cyclomatic complexity - a fresh look at code complexity|Blog, 4월 13, 2025에 액세스, https://codescene.com/engineering-blog/bumpy-road-code-complexity-in-context/
  2. On the accuracy of code complexity metrics: A neuroscience-based guideline for improvement - PMC - PubMed Central, 4월 13, 2025에 액세스, https://pmc.ncbi.nlm.nih.gov/articles/PMC9942489/
  3. Software disenchantment - why does modern programming seem to lack of care for efficiency, simplicity, and excellence - Reddit, 4월 13, 2025에 액세스, https://www.reddit.com/r/programming/comments/z7vsi0/software_disenchantment_why_does_modern/
  4. Cyclomatic complexity - Wikipedia, 4월 13, 2025에 액세스, https://en.wikipedia.org/wiki/Cyclomatic_complexity
  5. What is Cyclomatic Complexity? Definition Guide & Examples - Sonar, 4월 13, 2025에 액세스, https://www.sonarsource.com/learn/cyclomatic-complexity/
  6. Cyclomatic Complexity in Testing: Importance and Benefits - Bugasura, 4월 13, 2025에 액세스, https://bugasura.io/blog/cyclomatic-complexity-in-testing/
  7. A critique of cyclomatic complexity as a software metric - Computer Science Department, 4월 13, 2025에 액세스, https://www.cs.du.edu/~snarayan/sada/teaching/COMP3705/lecture/p1/cycl-1.pdf
  8. Cyclomatic complexity: Definition and limits in understanding code quality - DX, 4월 13, 2025에 액세스, https://getdx.com/blog/cyclomatic-complexity/
  9. Cyclomatic Complexity - The Ganssle Group, 4월 13, 2025에 액세스, https://www.ganssle.com/articles/cyclomaticcomplexity.html
  10. Why Is Counting Lines of Code (LOC) Useful? - NDepend Blog, 4월 13, 2025에 액세스, https://blog.ndepend.com/why-is-counting-lines-of-code-loc-useful/
  11. What Happened to Software Metrics? - PMC, 4월 13, 2025에 액세스, https://pmc.ncbi.nlm.nih.gov/articles/PMC5544018/
  12. Why lines of code are a bad measure of developer productivity - DX, 4월 13, 2025에 액세스, https://getdx.com/blog/lines-of-code/
  13. Why Lines of Code (LOC) Measures Irritate Me - ProjectManagement.com, 4월 13, 2025에 액세스, https://www.projectmanagement.com/blog-post/5104/Why-Lines-of-Code--LOC--Measures-Irritate-Me
  14. Avoid High Cyclomatic Complexity - Better Embedded System SW, 4월 13, 2025에 액세스, https://betterembsw.blogspot.com/2014/06/avoid-high-cyclomatic-complexity.html
  15. Code Complexity Metrics: Writing Clean, Maintainable Software - Iterators, 4월 13, 2025에 액세스, https://www.iteratorshq.com/blog/code-complexity-metrics-writing-clean-maintainable-software/
  16. Why your Lines of Code per day are not the right productivity metric—and what to use instead - Waydev, 4월 13, 2025에 액세스, https://waydev.co/lines-of-code-per-day/
  17. Should we prefer cognitive complexity over cyclomatic complexity ..., 4월 13, 2025에 액세스, https://community.sonarsource.com/t/should-we-prefer-cognitive-complexity-over-cyclomatic-complexity/12337
  18. A Fundamental Theory for Explaining and Measuring Code Complexity - ResearchGate, 4월 13, 2025에 액세스, https://www.researchgate.net/publication/362062741_A_Fundamental_Theory_for_Explaining_and_Measuring_Code_Complexity
  19. My solution to Microservices complexity : r/node - Reddit, 4월 13, 2025에 액세스, https://www.reddit.com/r/node/comments/1dvsfyz/my_solution_to_microservices_complexity/
  20. Code Complexity: An In-Depth Explanation and Metrics - Codacy | Blog, 4월 13, 2025에 액세스, https://blog.codacy.com/code-complexity
  21. How to Simplify Code Complexity: Avoid These Complexity Pitfalls - Turing, 4월 13, 2025에 액세스, https://www.turing.com/blog/reduce-code-complexity-simplify-your-code
  22. Is there a better programming metric than "lines of code"? - Reddit, 4월 13, 2025에 액세스, https://www.reddit.com/r/programming/comments/cbuwd/is_there_a_better_programming_metric_than_lines/
  23. Negative 2000 Lines of Code (1982) - Hacker News, 4월 13, 2025에 액세스, https://news.ycombinator.com/item?id=33483165
  24. The Pros and Cons of Using Cyclomatic Complexity as a Code Quality Metric - BlueOptima, 4월 13, 2025에 액세스, https://www.blueoptima.com/the-pros-and-cons-of-using-cyclomatic-complexity-as-a-code-quality-metric/
  25. Understanding cognitive complexity in software development - GetDX, 4월 13, 2025에 액세스, https://getdx.com/blog/cognitive-complexity/
  26. Cognitive Complexity - Code Climate, 4월 13, 2025에 액세스, https://docs.codeclimate.com/docs/cognitive-complexity
  27. (PDF) Comparative Analysis between Cognitive Complexity and ..., 4월 13, 2025에 액세스, https://www.researchgate.net/publication/389175932_Comparative_Analysis_between_Cognitive_Complexity_and_Cyclomatic_Complexity_in_Software_Development
  28. Cyclomatic Complexity vs Cognitive Complexity: Key Differences Explained - Graph AI, 4월 13, 2025에 액세스, https://www.graphapp.ai/blog/cyclomatic-complexity-vs-cognitive-complexity-key-differences-explained
  29. Do you use metrics for code complexity? : r/cpp - Reddit, 4월 13, 2025에 액세스, https://www.reddit.com/r/cpp/comments/l03xrr/do_you_use_metrics_for_code_complexity/
  30. A Notional Understanding of the Relationship between Code Readability and Software Complexity - MDPI, 4월 13, 2025에 액세스, https://www.mdpi.com/2078-2489/14/2/81
  31. An Empirical Study of the Relationships between Code Readability and Software Complexity - arXiv, 4월 13, 2025에 액세스, https://arxiv.org/pdf/1909.01760
  32. Simplifying Unit Testing with Dependency Injection: A Comprehensive Guide, 4월 13, 2025에 액세스, https://www.codingexplorations.com/blog/simplifying-unit-testing-with-dependency-injection-a-comprehensive-guide
  33. Testable and Maintainable code with Dependency Injection and Containers, 4월 13, 2025에 액세스, https://fideloper.com/testable-maintainable-di-containers
  34. unit testing - What are the design principles that promote testable code? (designing testable code vs driving design through tests) - Software Engineering Stack Exchange, 4월 13, 2025에 액세스, https://softwareengineering.stackexchange.com/questions/153410/what-are-the-design-principles-that-promote-testable-code-designing-testable-c
  35. Do you prefer Mock or Dependency Injection when Unit Testing Functions in Python?, 4월 13, 2025에 액세스, https://www.reddit.com/r/Python/comments/195uk6d/do_you_prefer_mock_or_dependency_injection_when/
  36. Good Code, Testable Code | Epic Web Dev, 4월 13, 2025에 액세스, https://www.epicweb.dev/good-code-testable-code
  37. Monolithic vs Microservices - Difference Between Software Development Architectures, 4월 13, 2025에 액세스, https://aws.amazon.com/compare/the-difference-between-monolithic-and-microservices-architecture/
  38. Microservices vs. monolithic architecture - Atlassian, 4월 13, 2025에 액세스, https://www.atlassian.com/microservices/microservices-architecture/microservices-vs-monolith
  39. Monolith Versus Microservices: Weigh the Pros and Cons of Both Configs | Akamai, 4월 13, 2025에 액세스, https://www.akamai.com/blog/cloud/monolith-versus-microservices-weigh-the-difference
  40. Comparing microservices to monoliths - BellSoft, 4월 13, 2025에 액세스, https://bell-sw.com/blog/microservices-vs-monoliths-which-approach-is-better-for-your-project/
  41. Micro Frontends - Martin Fowler, 4월 13, 2025에 액세스, https://martinfowler.com/articles/micro-frontends.html
  42. Understanding Code Complexity: Measurement and Reduction Techniques - Metabob, 4월 13, 2025에 액세스, https://metabob.com/blog-articles/understanding-code-complexity-measurement-and-reduction-techniques.html
  43. How to Reduce Cyclomatic Complexity: A Complete Guide | LinearB Blog, 4월 13, 2025에 액세스, https://linearb.io/blog/reduce-cyclomatic-complexity
  44. Do design patterns increase or decrease the complexity of an Application? - Stack Overflow, 4월 13, 2025에 액세스, https://stackoverflow.com/questions/759796/do-design-patterns-increase-or-decrease-the-complexity-of-an-application
  45. Essential and Accidental complexity, and performance - Eduardo Lavaque's Blog, 4월 13, 2025에 액세스, https://greduan.com/blog/2024/07/25/essential-and-accidental-complexity-and-performance/
  46. Coupling And Cohesion - C2 wiki, 4월 13, 2025에 액세스, https://wiki.c2.com/?CouplingAndCohesion
  47. Reducing Coupling, 4월 13, 2025에 액세스, https://martinfowler.com.cn/ieeeSoftware/coupling.pdf
  48. Book notes: Agile Technical Practices Distilled, 50 years of Cohesion and Coupling, 4월 13, 2025에 액세스, https://espumita.org/2024/09/01/agile-technical-practices-distilled-book-notes/
  49. Accidental Complexity in Software Engineering - Chi Blog, 4월 13, 2025에 액세스, https://cedricchee.com/blog/accidental-complexity/
  50. No Silver Bullet - Wikipedia, 4월 13, 2025에 액세스, https://en.wikipedia.org/wiki/No_Silver_Bullet
  51. Avoiding Accidental Complexity in Software Design - Nutshell CRM, 4월 13, 2025에 액세스, https://www.nutshell.com/blog/accidental-complexity-software-design
  52. Simplify! - Part 3 - Uwe Friedrichsen, 4월 13, 2025에 액세스, https://www.ufried.com/blog/simplify_3_essential_and_accidental_complexity/
  53. Linking Modular Architecture to Development Teams - Martin Fowler, 4월 13, 2025에 액세스, https://martinfowler.com/articles/linking-modular-arch.html
  54. 8 Effective Strategies to Reduce Software Complexity, 4월 13, 2025에 액세스, https://www.expeed.com/8-effective-strategies-to-reduce-software-complexity
  55. When Do Software Complexity Metrics Mean Nothing?- When Examined out of Context - The Journal of Object Technology, 4월 13, 2025에 액세스, https://www.jot.fm/issues/issue_2016_01/article2.pdf
  56. Code Complexity: Best Practices for Efficient Software - Metridev, 4월 13, 2025에 액세스, https://www.metridev.com/metrics/code-complexity-best-practices-for-efficient-software/
  57. Managing Software Complexity: Strategies & Benefits | IN-COM - IN-COM Data Systems, 4월 13, 2025에 액세스, https://www.in-com.com/blog/software-management-complexity/
  58. 17 Software Development Best Practices for Writing Code in 2024 - Pulsion Technology, 4월 13, 2025에 액세스, https://www.pulsion.co.uk/blog/17-software-development-best-practices-for-writing-code/
  59. Cyclomatic Complexity Explained With Practical Examples - YouTube, 4월 13, 2025에 액세스, https://www.youtube.com/watch?v=vmyS_j3Kh8g
  60. What Is Cyclomatic Complexity? - AutoRABIT Knowledge Base, 4월 13, 2025에 액세스, https://knowledgebase.autorabit.com/fundamentals/faq/codescan-faqs/general/what-is-cyclomatic-complexity
  61. Minimizing Cyclomatic Complexity with Pattern Matching - DanylkoWeb, 4월 13, 2025에 액세스, https://www.danylkoweb.com/Blog/minimizing-cyclomatic-complexity-with-pattern-matching-SR
  62. Cyclomatic complexity and cognitive complexity - DEV Community, 4월 13, 2025에 액세스, https://dev.to/packmind/cyclomatic-complexity-and-cognitive-complexity-4c03
  63. Standards in software development and 9 best practices - OpsLevel, 4월 13, 2025에 액세스, https://www.opslevel.com/resources/standards-in-software-development-and-9-best-practices
@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중