순환 복잡도를 읽는 방법: WordPress 렌더 콜백 측정
요약
순환 복잡도는 함수의 독립적 실행 경로 개수를 측정하는 지표입니다. WordPress에서는 렌더 콜백의 분기가 빠르게 증가하곤 합니다. 이 글은 실제 프로젝트에서 순환 복잡도를 올바르게 측정하고, AI 생성 패턴의 높은 복잡도를 이해하고, 리팩토링 신호를 파악하는 방법을 설명합니다.
순환 복잡도(cyclomatic complexity)는 함수를 통과하는 독립적 경로의 개수를 세는 지표입니다. WordPress의 렌더 콜백에서는 이 숫자가 대부분의 개발자 예상보다 훨씬 빠르게 증가합니다. 세 개의 조건문, 블록 변형에 따른 switch, 내부 블록을 순회하는 루프가 있다면 아무도 알아차리지 못하는 사이에 12나 14에 도달할 수 있습니다. 이것이 반드시 잘못된 것은 아닙니다. 리팩토링을 할지, 배포할지, 아니면 그냥 두지 할지 판단하기 전에 이 숫자를 올바르게 읽어야 합니다.
프리랜서와 소규모 팀은 이 지표를 두 가지 방식으로 만납니다. 첫째, 프로젝트에서 아무도 도구를 실행하지 않아 한 번도 만나지 않거나. 둘째, 엔터프라이즈 Java 스타일 가이드에서 문맥을 무시하고 순환 복잡도 10이라는 상한선을 절대 법칙처럼 받아들이는 경우입니다. 둘 다 WordPress 테마에는 도움이 되지 않습니다. 이 글에서는 세 가지 구체적인 사례를 다룹니다. 자신의 렌더 콜백에서 이 숫자가 무엇을 의미하는지, AI 패턴 생성기가 인간이 만들 때보다 왜 더 높은 값을 만드는지, 그리고 모든 함수를 한 줄 헬퍼의 미로로 만들지 않으면서 점수를 읽는 방법입니다.
WordPress 렌더 콜백에서 순환 복잡도가 실제로 무엇을 측정하는가
Thomas McCabe는 1976년 이 지표를 프로그램의 제어 흐름 그래프를 통과하는 선형 독립 경로의 개수로 정의했습니다. 평상시로 말하면: 1에서 시작해서 if, elseif, case, for, foreach, while, catch, 그리고 실행을 분기할 수 있는 모든 부울 연산자(&&, ||)에 대해 1을 더합니다. is_admin() 검사, $attributes['items'] 순회, layout 속성에 따른 switch를 처리하는 렌더 콜백은 데이터를 처리하기 전에 벌써 5를 넘습니다.
이 지표를 처음 다루는 개발자가 놓치는 두 가지가 있습니다. 첫째, 길이가 아니라 분기를 측정한다는 것입니다. 조건문이 없는 200줄 함수는 복잡도 1입니다. 5개의 중첩 삼항 연산자가 있는 15줄 함수는 복잡도 8입니다. 둘째, 코드를 읽기가 얼마나 어려운지를 측정하지 않으며, 테스트 스위트가 완전히 커버하기 위해 필요한 테스트 케이스의 개수만 측정합니다. 복잡도 12인 함수는 모든 경로를 실행하려면 12개의 별개 테스트 케이스가 필요합니다. 대부분의 WordPress 클라이언트 프로젝트는 0개로 배포됩니다.
마지막 부분이 짚고 넘어갈 가치가 있습니다. 일반적인 프리랜서 보수 계약에서, 아무도 홈페이지 히어로 블록을 위해 12개 테스트 케이스를 작성합니다. 실제로는 클라이언트가 자신이 직접 사용하는 3~4개 경로를 클릭으로 확인하고 완료를 선언하며, 나머지 8개 경로는 6개월 후 아무도 테스트하지 않은 경로에서 지원 티켓이 들어올 때까지 테스트되지 않은 상태로 남습니다. 순환 복잡도는 스타일 선호도가 아닙니다. 자신의 코드 중 얼마나 많은 부분을 실제로 확인했는지, 그리고 얼마나 많은 부분을 운에 맡기고 있는지를 나타내는 대략적인 지표입니다.
AI 생성 블록 패턴이 이 지표에서 높은 값을 보이는 이유
이 부분은 대부분의 복잡도 설명에서 건너뜁니다. 백엔드 엔지니어를 위해 작성되었기 때문입니다. 클라이언트에 Gutenberg 패턴을 배포하는 사람들을 위해서는 아닙니다. AI 패턴 생성기는 Pattern Forge를 포함해 세 가지 구체적이고 예측 가능한 방식으로 복잡도를 증가시킵니다.
반응형 중단점 로직은 헬퍼로 추출되지 않고 중첩 조건문으로 작성됩니다. RTL 미러링은 거의 모든 레이아웃 결정에 방향 검사를 추가해 이중언어 테마의 분기 개수를 두 배로 늘립니다. 그리고 동적 콘텐츠 블록(포스트 루프, 쿼리 변형, 조건부 CTA)은 렌더 콜백 내에 if 체인을 쌓습니다. 왜냐하면 단일 자체 포함 콜백이 모델이 첫 번째 시도에서 올바르게 생성하기 더 쉽기 때문입니다.
2026년 6월 단순 증언 카드부터 필터가 있는 완전한 포스트 그리드까지 11개 프롬프트에서 Pattern Forge를 실행하고 PHPMD로 생성된 렌더 콜백을 측정했습니다. 중앙값 복잡도: 9. 증언 카드는 4였습니다. 필터가 있는 포스트 그리드는 조건 로직이 가장 많은 프롬프트로 19에 도달했습니다. 이것은 Pattern Forge 특유의 결함이 아닙니다. Elementor AI의 동일한 필터 그리드 프롬프트 결과는 동일 측정에서 21이었습니다. 둘 다 인간이 클라이언트 사이트에 배포하기 전에 함수를 한 번 읽어야 하는 지점을 넘었습니다.
점수를 해석하는 방법: 110, 1120, 50+ 실제로 의미하는 것
아래 기준값들은 자의적이지 않습니다. 이들은 McCabe의 원래 NIST 인용 권장사항까지 거슬러 올라가며 오늘날 여전히 지표를 배포하는 모든 정적 분석 공급업체가 반복합니다.
1~10: 간단하고 합리적인 케이스 수로 테스트 가능하며 결함 위험이 낮습니다.
11~20: 중간 정도. 병합 전에 한 번 더 검토할 가치가 있으며, 특히 클라이언트의 다음 개발자가 상속할 코드의 경우 더욱 그렇습니다.
21~50: 복잡합니다. 모든 경로를 테스트하는 것은 실무적으로 불가능합니다. 이곳이 아무도 테스트하지 않은 분기에 버그가 숨어 있는 곳입니다.
50 이상: 기능적으로 테스트 불가능합니다. 렌더 콜백에서 이를 발견하면 다른 것을 건드리기 전에 멈추고 나누세요.

여백의 주석: 이 범주들은 위험을 설명하며, 미학이 아닙니다. 복잡도 14인 함수는 "나쁜 코드"가 아닙니다. 대부분의 WordPress 프로젝트가 예산을 잡지 않는 것보다 더 많은 테스트 커버리지가 필요한 코드입니다.
터미널을 떠나지 않고 측정하기
이 숫자를 얻기 위해 유료 SaaS 대시보드가 필요하지 않습니다. 세 가지 도구가 거의 모든 WordPress 프리랜서 설정을 다룹니다.
PHPMD는 순환 복잡도 규칙이 기본적으로 포함되어 있습니다. 테마의 inc/ 또는 blocks/ 디렉토리를 가리키면 설정한 기준값(기본값 10은 적절합니다)을 초과하는 모든 항목을 플래그합니다. PHPCS는 Generic.Metrics.CyclomaticComplexity sniff를 통해 동일한 적용 범위를 제공하며, 프로젝트가 이미 PHPCS를 실행 중이고 두 번째 도구를 추가하고 싶지 않다면 유용합니다. churn-php는 완전히 다른 각도를 취합니다. 복잡도를 git 커밋 빈도에 교차 참조하므로 복잡한 AND 지속적으로 건드려지는 클래스를 표면화합니다. 이는 복잡도만으로는 "먼저 리팩토링하세요"보다 더 sharp한 신호입니다.
이들 중 누구도 Composer를 넘어서는 빌드 단계가 필요하지 않습니다. PHPMD를 사전 커밋 훅에서 실행하고, 기준값을 초과하면 커밋을 실패하게 하면 문제가 클라이언트 검토에 도달하는 것을 방지합니다.
여러 클라이언트 테마를 동시에 운영하는 에이전시의 경우, 동일한 PHPMD 규칙 세트가 로컬뿐 아니라 CI에 있어야 합니다. 모든 pull request에서 phpmd blocks/,inc/ text phpmd-ruleset.xml을 실행하는 GitHub Actions 단계는 빌드당 몇 초 비용이 들며 항상 단일 검토를 슬쩍 지나가는 패턴을 잡습니다. 복잡도 4에서 시작한 작은 헬퍼 함수는 "단 하나의 조건을 더"로 네 번 pull request를 생존하고 아무도 기울기를 알아차리지 못하면서 17에 도달합니다. 커밋 이력은 증분을 보여줍니다. Pull request diff는 총합을 절대 보여주지 않습니다.

WordPress 핵심이 2026년 정적 분석에서 어디에 서 있는가
코어는 여전히 따라잡고 있습니다. 코어 팀의 PHPStan 제안은 코어 워크플로에서 정적 분석을 공식화하지만 타입 안전성과 죽은 코드를 대상으로 합니다. 순환 복잡도 기준을 강제하는 trac 티켓은 없으며, 몇몇 코어 렌더 콜백(render_block_core_query 등)은 어떤 측정으로든 20을 훨씬 넘습니다. 코어는 스코어를 위한 리팩토링보다 하위 호환성을 우선시합니다. 이는 그 규모에서는 방어 가능한 트레이드오프이지만, 세 명의 개발자와 회귀 테스트 스위트가 없는 클라이언트 테마에 복사해서는 안 됩니다.
이중언어 사례: EN/AR 블록 테마의 렌더 콜백 풀어내기
나의 클라이언트 하나는 영어와 아랍어, Polylang을 기반으로 한 블록 테마로 구축한 이중언어 뉴스 사이트를 운영합니다. 홈페이지의 featured-story 블록에는 다음을 처리하는 렌더 콜백이 하나 있었습니다: 포스트 타입 전환, RTL 레이아웃 미러링, 세 가지 카드 크기, 누락된 featured 이미지에 대한 fallback, 그리고 고정 스토리에 대한 수동 오버라이드. 복잡도: 23.
수정은 완전한 재작성이 아니었습니다. 나는 RTL 미러링을 자체 함수(get_card_direction_class())로 추출했고, 카드 크기 로직을 작은 match 표현식으로 당겨왔으며, fallback과 pin-override 로직은 원래 위치에 둡니다. 왜냐하면 그들을 더 나누는 것은 5개 매개변수를 두 개의 작은 함수 사이로 전달하는 것을 의미하기 때문입니다. 최종 복잡도: 메인 콜백 11, 추출된 헬퍼 3. 14분의 작업, Polylang이 기본적으로 배포하는 동일한 9개 로케일 조합에 대해 테스트되었습니다.
학습은 이 한 클라이언트를 넘어 일반화됩니다. RTL 지원은 거의 항상 이중언어 WordPress 프로젝트의 숨겨진 복잡도 세금입니다. 첫 커밋부터 설계되지 않기 때문입니다. 패치로 도착합니다. 기존 조건문 체인에 볼트로 고정된 방향 검사, 그 다음 또 다른, 그 다음 아직 아랍어 번역이 없는 포스트의 엣지 케이스입니다. 그 한 가지 관심사를 자체 작은 함수로 추출하세요. 자체 이름으로. 나머지 콜백은 영어 전용 기능 목록이 계속 주변에서 증가하더라도 읽을 수 있는 상태로 유지됩니다.

높은 점수가 괜찮은 경우와 언제 멈춰야 하는 신호
모든 함수를 상관없이 10 이하로 유지해야 한다는 조언은 건너뛰세요. 일부 WordPress 템플릿 PHP는 정당하게 더 높게 실행됩니다. 레이아웃 변형의 개수가 실제 요구사항이기 때문입니다. 나쁜 코드의 우연이 아닙니다. theme.json으로 구동되는 카드 컴포넌트가 합법적으로 6개 레이아웃, 3개 크기, 2개 방향을 지원하면 설명할 분기가 있습니다. 6개의 한 줄 헬퍼 함수를 추출해 복잡도를 낮추려고 시도하면, 각각 한 번만 호출되고, 한 개의 읽을 수 있는 함수를 간접 미로로 교체합니다. 이것이 다음 개발자에게 더 나쁩니다.
실제로 멈추고 리팩토링하는 신호는 다릅니다. 복잡도가 20을 넘어 누군가 완전히 테스트하지 않은 함수로 올라갑니다. 클라이언트가 "한 가지 변형 더"를 요청할 때마다 올라가는 복잡도. 또는 churn-php 보고서가 지난 10개 커밋 중 6개에서 같은 복잡한 클래스가 편집된 것을 보여줍니다. 점수 자체는 트리거가 아닙니다. 점수와 그 함수를 건드리기가 얼마나 무서운지입니다.
PHP를 리팩토링하는 것이 완전히 잘못된 수정인 지점도 있습니다. 클라이언트의 체크아웃 또는 카탈로그 로직이 WooCommerce 훅이 깔끔하게 운반할 수 있도록 설계된 것을 넘어 나선형으로 상승했다면, 때로는 정직한 답은 세 가지 조건문 깊이로 중첩된 또 다른 add_filter() 호출이 아닙니다. WordPress 플러그인 스택보다 더 잘 그 로직을 운반할 전용 전자상거래 플랫폼입니다.
포리오를 설정하세요: 다음 인수도 전에 확인할 사항
모든 클라이언트 인수도 전에 blocks/ 및 inc/에 대해 PHPMD를 실행하세요. 단 뭔가 깨질 때가 아닙니다. 15를 초과하는 모든 항목을 두 번째 읽기를 위해 플래그하고, 20을 초과하는 모든 항목을 요구사항이 분기를 정당화하는지에 대한 실제 대화를 위해 플래그하세요. SonarSource 자신의 참조 가이드에 따르면, 10을 넘는 함수는 이미 대부분의 팀이 실제로 작성하는 것보다 더 많은 테스트 케이스가 필요합니다. 이것이 점수 자체가 아니라 당신이 관리하는 실제 비용입니다.
패턴을 생성한 도구가 인간이든 AI든 이 단계를 건너뛸 수 없습니다. 배포하기 전에 렌더 콜백을 한 번 읽으세요. 이것이 전체 실무입니다.