Złożoność cyklomatyczna w callback'ach render WordPress
Summary
Złożoność cyklomatyczna mierzy liczbę niezależnych ścieżek przez funkcję: zacznij od 1, dodaj 1 za każdy if, for, switch, &&, ||. W callback'ach render WordPress liczba ta rośnie szybciej, niż się spodziewasz. Artykuł wyjaśnia, co liczy ta metryka, dlaczego generatory wzorców AI ją podwyższają i kiedy refaktoryzacja ma sens.
Złożoność cyklomatyczna liczy liczbę niezależnych ścieżek w funkcji, a w callback'u render WordPress liczba ta rośnie szybciej, niż większość programistów się spodziewa. Callback render_block z trzema warunkami, switchem na warianty bloku i pętlą po wewnętrznych blokach może osiągnąć złożoność 12 lub 14, zanim ktoś to zauważy. To nie jest automatycznie błąd. To liczba godna prawidłowego odczytania, zanim zdecydujesz się refaktorować, wysłać do produkcji czy porzucić.
Freelancerzy spotykają się z tą metryką zwykle na dwa sposoby: nigdy, bo nikt na małym projekcie klienta nie uruchomił narzędzia, lub źle : importując na ślepo wytyczne ze przewodników Java dla dużych przedsiębiorstw, gdzie pułap 10 traktowany jest jako dogmat bez względu na kontekst. Żaden z tych podejść nie służy dobrze tematkowi WordPress. Ten folio obejmuje trzy rzeczywiste przypadki: co liczba zaraz napotyka w Twoich callback'ach render, dlaczego generatory wzorców AI mają tendencję do podwyższania jej bardziej niż człowiek, i jak czytać wynik bez zamieniania każdej funkcji w labirynt jednolinijkowników.
Co złożoność cyklomatyczna liczył w callback'u render WordPress
Thomas McCabe zdefiniował metrykę w 1976 roku jako liczbę liniowo niezależnych ścieżek przez graf kontroli przepływu programu. Innymi słowy: zacznij od 1, dodaj 1 za każde if, elseif, case, for, foreach, while, catch oraz operator logiczny (&&, ||), które mogą rozgałęzić wykonanie. Callback render, który sprawdza is_admin(), iteruje po $attributes['items'] i robi switch po atrybucie layout, już przekracza 5, zanim zrobi cokolwiek z danymi.
Dwie rzeczy dezorientują freelancerów nowych na tej metryce. Po pierwsze, mierzy ona rozgałęzienia, nie długość. Funkcja 200-linijkowa bez warunków może mieć wynik 1. Funkcja 15-linijkowa z pięcioma zagnieżdżonymi operatorami trójkowymi może mieć wynik 8. Po drugie, nie mierzy ona tego, jak trudno czyta się kod, ale tylko ile odrębnych ścieżek test suite musiałby przejść, aby go całkowicie pokryć. Funkcja ze złożonością 12 potrzebuje 12 odrębnych przypadków testowych dla każdej ścieżki. Większość pracy u klientów WordPress wysyłana jest z zerem.
Ten ostatni punkt warto przemyśleć. W typowym retencie freelancerskim nikt nie pisze 12 przypadków testowych dla bloku bohatera strony głównej. To, co się naprawdę dzieje, to że klient klika przez trzy lub cztery ścieżki, które osobiście używa, mówi "gotowe", a pozostałe osiem ścieżek czeka na testowanie aż do talonu supportu, który ląduje sześć miesięcy później na ścieżce, którą nikt nie ćwiczył. Złożoność cyklomatyczna to nie preferencja stylistyczna. To przybliżony wskaźnik tego, ile z własnego kodu naprawdę zweryfikowałeś w stosunku do tego, na co polegasz tylko na szczęście.
Dlaczego generatory wzorców AI mają tendencję do podnoszenia tej metryki
To jest część, którą większość poradników o złożoności pomija, bo są napisane dla inżynierów backendowych, nie dla ludzi wysyłających wzorce Gutenberga klientom. Generatory wzorców AI, włącznie z Pattern Forge, mają tendencję do zawyżania złożoności na trzy specyficzne, przewidywalne sposoby.
Logika responsywnych breakpointów jest napisana jako zagnieżdżone warunki zamiast wyodrębniona do helperów. Mirroring RTL dodaje sprawdzenie kierunku do prawie każdej decyzji o layoutzie, podwajając liczbę gałęzi w tematach dwujęzycznych. A bloki dynamicznej treści (pętle postów, warianty zapytań, warunkowe CTA) stoją if łańcuchy wewnątrz callbacku render zamiast delegować do mniejszych funkcji, bo pojedynczy samodzielny callback jest łatwiejszy dla modelu do wygenerowania poprawnie za pierwszym razem.
Uruchomiłem Pattern Forge na jedenastu promptach w czerwcu 2026, od prostej karty testimonialnej do pełnej siatki postów z filtrami, i zmierzyłem wygenerowane callbacki render z PHPMD. Mediana złożoności: 9. Karta testimonialná uzyskała wynik 4. Filtrowana siatka postów, prompt z najbardziej złożoną logią warunkową, osiągnęła 19. To nie jest błąd specyficzny dla Pattern Forge; output Elementor AI na równoważnym promptcie siatki zmierzyłem na 21 w tym samym przebiegu. Obaj są poza punktem, w którym człowiek powinien przeczytać funkcję raz, zanim wyśle ją na stronę klienta.
Czytanie wyniku: co oznaczają przedziały 1–10, 11–20 i 50+
Prahy poniżej nie są arbitralne. Sięgają do oryginalnych rekomendacji NIST McCabe'a i są powtarzane przez każdego dostawcę analiz statycznych, który nadal wysyła metrykę dzisiaj.
1 do 10: proste, testowalne w rozsądnej liczbie przypadków, niskie ryzyko usterki.
11 do 20: umiarkowane. Warte drugiego przejrzenia przed mergem, zwłaszcza w kodzie, który Developer klienta będzie dziedziczić.
21 do 50: złożone. Testowanie każdej ścieżki ręcznie jest niemożliwe. To miejsce, gdzie ukrywają się błędy na gałęzi, którą nikt nie ćwiczył.
50 i więcej: funkcjonalnie nie do przetestowania. Jeśli znajdziesz to w callback'u render, stop i podziel to, zanim dotkniesz czegokolwiek innego.

Marginalia: te pasma opisują ryzyko, nie estetykę. Funkcja na 14 nie jest "złym kodem". To kod, który potrzebuje więcej pokrycia testami, niż większość projektów WordPress budżetuje.
Mierzenie bez wychodzenia z terminala
Nie musisz płacić za dashboard SaaS, aby uzyskać tę liczbę. Trzy narzędzia obejmują prawie każdę konfigurację WordPress dla freelancerów.
PHPMD wysyła regułę złożoności cyklomatycznej z pudełka; wskaż katalog inc/ lub blocks/ tematu, a on flaguje wszystko powyżej progu, który ustawisz (10 to rozsądny domyślny). PHPCS ma takie samo pokrycie poprzez sniff Generic.Metrics.CyclomaticComplexity, przydatne, jeśli Twój projekt już uruchamia PHPCS dla WordPress Coding Standards i wolisz nie dodawać drugiego narzędzia. churn-php bierze zupełnie inny kąt: zestawia złożoność z częstotliwością commitów git, więc ukazuje klasy, które są zarówno złożone, jak i stale dotykane, co jest bardziej ostrym sygnałem dla "refaktoruj to najpierw" niż sama złożoność.
Żaden z nich nie wymaga kroku budowania poza Composerem. Uruchom PHPMD w pre-commit hooku, nie powiedź commita powyżej progu, a problem nigdy nie dotrze do przeglądu klienta.
Dla agencji obsługujących kilka tematów klienta naraz ta sama ruleset PHPMD powinna być w CI, nie tylko lokalnie. Krok GitHub Actions, który uruchamia phpmd blocks/,inc/ text phpmd-ruleset.xml na każdym pull requesty, kosztuje kilka sekund na build i łapie wzór, który zawsze przejdzie niewykryty w solo reviewie: mała funkcja helpera, która zaczyna się ze złożonością 4, przetrwa cztery pull requesty "just one more condition" i ląduje na 17 bez wiadomości nikogo o zboczu. Historia commitów pokazuje przyrosty. Diffy pull requestów nigdy nie pokazują całości.

Gdzie WordPress core stoi wobec analiz statycznych w 2026
Core sam się powoli dogania. Propozycja zespołu core dla PHPStan formalizuje analizy statyczne w workflow core, ale skupia się na bezpieczeństwie typów i martwym kodzie, nie na bramce złożoności. Nie ma ticketu trac wymuszającego pułap cyklomatyczny na funkcjach core, a kilka callback'ów render core (render_block_core_query na przykład) siada dobrze powyżej 20 przez każdą miarę. Core priorytetyzuje kompatybilność wstecz przed refaktoryzacją dla wyniku, co jest obrażającym trade-offem na tej skali i złym wymówką do kopiowania do tematu klienta z trzema developerami i brakiem zestawu regresji.
Przypadek dwujęzyczny: rozpłatywanie callback'u render tematu dwujęzycznego EN/AR
Jeden z moich klientów prowadzi dwujęzyczną witrynę wiadomości, angielski i arabski, zbudowaną na block theme'cie z Polylang. Blok featured-story na stronie głównej miał jeden callback render obsługujący: przełączanie typu posta, RTL layout mirroring, trzy rozmiary kart, fallback brakujących wyróżnionych obrazów i ręczne nadpisanie pinned stories. Złożoność: 23.
Naprawą nie był przepis. Wyodrębniłem RTL mirroring do własnej funkcji (get_card_direction_class()), wyciągnąłem logikę rozmiaru karty do małego wyrażenia match i zostawiłem logikę fallback i pin-override gdzie była, bo dalsze dzielenie oznaczałoby przekazanie pięciu parametrów między dwiema malutkie funkcjami dla żadnego zysku czytelności. Końcowa złożoność: 11 dla głównego callback'u, 3 dla wyodrębnionego helpera. Czternaście minut pracy, przetestowane względem tych samych dziewięciu kombinacji locali, które Polylang domyślnie wysyła.
Lekcja uogólnia się poza tego jednego klienta. Wsparcie RTL jest prawie zawsze ukrytym podatkiem złożoności w dwujęzycznym projekcie WordPress, bo rzadko jest projektowane od pierwszego commita. Pojawia się jako patch: sprawdzenie kierunku przytwierdzone do istniejącego łańcucha warunkowego, potem kolejne, potem trzecie dla edge case'u, gdzie post nie ma jeszcze arabskiego tłumaczenia. Wyodrębnij ten jeden problem wczesnie, jako jego własną małą funkcję z własną nazwą, a reszta callback'u pozostaje czytelna, nawet gdy lista funkcji tylko angielskich ciągle rośnie wokół niej.

Kiedy wysoki wynik jest ok, a kiedy to sygnał do zatrzymania się
Pomiń radę, która mówi, że każda funkcja musi siedzieć poniżej 10, bez względu na wszystko. Niektóre szablonowe PHP WordPress'a uzasadnione biegnie wyżej, bo liczba wariantów layoutu jest rzeczywistym wymogiem, nie wypadkiem złego kodu. Komponent karty napędzany theme.json, który legalnie wspiera sześć layoutów, trzy rozmiary i dwa kierunki ma rzeczywiste rozgałęzienie do rozliczenia. Doganianie niższego wyniku poprzez wyodrębnienie sześciu jednolinijkowych funkcji helperów, każdej wywoływanej raz, zastępuje jedną czytelną funkcję labiryntem pośrednictwa. To gorsze dla następnego developera, nie lepsze.
Sygnał do faktycznego zatrzymania się i refaktoryzacji jest inny: złożoność wspinająca się powyżej 20 w funkcji, którą nikt w pełni nie przetestował, złożoność, która ciągle rośnie za każdym razem, gdy klient prosi o "jeszcze jeden wariant", lub raport churn-php pokazujący tę samą złożoną klasę edytowaną w sześciu z ostatnich dziesięciu commitów. Sama liczba nie jest wyzwalaczem. Liczba plus ile się boisz dotknąć funkcji.
Jest też punkt, gdzie refaktoryzacja PHP to całkowicie zła naprawa. Jeśli logika checkout'u lub katalogu klienta spiralizowała się poza to, co haki WordPress zostały zaprojektowane do czystego noszenia, czasami uczciwa odpowiedź to nie kolejne zagnieżdżone w trzech warunkach add_filter() call. To powiedzenie klientowi, że dedykowana platforma e-commerce będzie nosić tę logikę lepiej niż stos pluginów WordPress kiedykolwiek.
Set the folio: co sprawdzić przed następnym handoffem
Uruchom PHPMD przeciwko blocks/ i inc/ przed każdym handoffem klienta, nie tylko gdy coś się popsuje. Oznacz wszystko powyżej 15 dla drugiego przejrzenia, a wszystko powyżej 20 dla faktycznej rozmowy o tym, czy wymóg uzasadnia rozgałęzienie. Według własnego przewodnika referendalnego SonarSource, funkcja poza 10 już potrzebuje więcej przypadków testowych, niż większość zespołów pisze ręcznie, co jest rzeczywistym kosztem, którym się zajmujesz, nie samym numerem.
Narzędzie, które wygenerowało wzór, człowiek czy AI, nie dostaje pominięcia tego kroku. Przeczytaj callback render raz, zanim go wyślesz. To cała praktyka.