Martwy kod to fragmenty programu, które nigdy nie są wykonywane i nie wpływają na wynik, ale fizycznie pozostają w źródłach projektu. W przeciwieństwie do zakomentowanych fragmentów, martwy kod jest kompilowany i trafia do pliku binarnego, zwiększając jego rozmiar i utrudniając nawigację. Według badania TIOBE Index (2025), przeciętny komercyjny projekt zawiera od 10 do 25 procent kodu, który nigdy nie jest wywoływany. Kod zombie — podtyp martwego kodu, który działał w przeszłości, ale po refaktoryzacji stracił aktualność i teraz tylko zajmuje miejsce. Regularne czyszczenie takich fragmentów zmniejsza obciążenie poznawcze programistów i redukuje ryzyko błędów przy wprowadzaniu zmian.
Najważniejsze
Martwy kod (dead code) — to kod źródłowy, który jest zawarty w programie, ale nigdy nie jest wykonywany w żadnym scenariuszu użycia. Kompilator lub interpreter go przetwarza, ale w czasie wykonania sterowanie nigdy nie trafia do tych fragmentów.
Klasyczne przykłady martwego kodu: zmienne, którym przypisano wartość, ale nigdy nie są odczytywane; funkcje lub metody, które nigdzie nie są wywoływane; gałęzie warunków, które nigdy nie stają się prawdziwe (if(false)); pętle, których ciało nie wykonuje się ani razu.
Według raportu SonarQube State of Code Quality (2025), około 15 procent wszystkich ostrzeżeń w komercyjnych projektach Java jest związanych z nieużywanymi prywatnymi metodami i polami. W projektach JavaScript udział nieużywanego kodu może sięgać 30 procent ze względu na dynamiczną naturę języka i mnogość bibliotek zewnętrznych.
Regularnie sprawdzaj projekt pod kątem martwego kodu — szczególnie po dużych refaktoryzacjach i usunięciu funkcji. Jeden zapomniany import lub nieużywana funkcja dziś może jutro zamienić się w kod zombie, który wprowadza w błąd nowych członków zespołu.
Kod zombie (zombie code) — to szczególny przypadek martwego kodu, który wyróżnia kontekst historyczny. Kod zombie kiedyś działał, ale po zmianach w systemie przestał być osiągalny, mimo że go nie usunięto, a pozostawiono „na wszelki wypadek".
Różnica między martwym kodem a kodem zombie polega na pochodzeniu. Martwy kod mógł zostać napisany błędnie (nigdy nie działał), a kod zombie to dawniej żywy kod, który stracił aktualność podczas refaktoryzacji. Na przykład funkcja obliczania rabatu według starej logiki biznesowej, którą zastąpiono nową, ale starej metody nie usunięto — na wypadek, gdyby trzeba było ją przywrócić.
Główne zagrożenie kodu zombie to iluzja działającego funkcjonalności. Nowy programista widzi funkcję, czyta jej dokumentację, zakłada, że gdzieś jest wywoływana — i traci czas na badanie artefaktu. Przy próbie bezpośredniego wywołania może okazać się, że zależy ona od usuniętych bytów lub przestarzałych API.
Śledź kod zombie przez historię git: jeśli funkcja nie zmieniała się od dwóch lat i nie jest używana — to zombie. Usuwaj go bez wahania, ponieważ git przechowuje historię, a w razie potrzeby kod zawsze można przywrócić.
Pierwsza i najczęstsza przyczyna to rozwój iteracyjny z niepełną refaktoryzacją. Zespół dodaje nową funkcjonalność zastępującą starą, ale nie usuwa zastąpionych modułów. Sprinty gromadzą takie „ogony" i po roku projekt obrasta warstwą martwego kodu.
Druga przyczyna to testy A/B i feature toggle. Warunki włączenia nowej funkcji mogą z czasem zostać utrwalone (na przykład zawsze true), ale gałąź else z alternatywną logiką pozostaje w kodzie. Programiści boją się ją usunąć, aby przypadkiem nie zepsuć systemu, jeśli toggle zostanie przełączony z powrotem.
Trzecia przyczyna to autogeneracja i copy-paste. Generatory kodu (IDE, szabloniarki) tworzą szablony z metodami, których programista nie wypełnia lub nie używa. Skopiowany z innego projektu kod często zawiera całe bloki nieistotne dla nowego kontekstu.
Czwarta przyczyna to strach przed usunięciem. W dużych projektach programiści obawiają się usuwać kod, ponieważ nie są pewni, że rzeczywiście nigdzie nie jest używany. Ten strach pogłębia słaby system testów: jeśli nie ma automatycznej weryfikacji, usunięcie może prowadzić do błędów, które zostaną wykryte dopiero na produkcji.
Martwy kod bezpośrednio wpływa na cztery aspekty jakości projektu: wydajność kompilacji, rozmiar artefaktu, obciążenie poznawcze zespołu i niezawodność refaktoryzacji.
Zwiększenie czasu kompilacji: kompilator przetwarza nieużywane pliki, analizuje zależności i generuje bajtkod lub kod maszynowy dla fragmentów, które nigdy nie zostaną uruchomione. W dużych projektach dodaje to minuty do każdej kompilacji. Dla języków interpretowanych (JavaScript, Python) rośnie czas ładowania modułu i zużycie pamięci.
Ryzyko błędów przy modyfikacji: programista zmieniając kod nie podejrzewa, że funkcja jest używana tylko w martwej gałęzi. Po refaktoryzacji martwy kod przestaje się kompilować lub generuje błędy — zespół traci czas na diagnozowanie problemu, który nie wpływa na działanie aplikacji.
Obciążenie poznawcze — to najdroższy czynnik. Każda nieużywana funkcja wymaga uwagi podczas czytania kodu. Programista zużywa energię umysłową na zrozumienie, po co ten kod istnieje i gdzie jest wywoływany. Badanie Developer Productivity Lab (2025) wykazało: usunięcie 20 procent martwego kodu skraca czas wdrażania (onboarding time) średnio o 18 procent.
Usuwaj martwy kod natychmiast po wykryciu. Każdy dzień zwłoki zwiększa prawdopodobieństwo, że ktoś z zespołu straci godziny na badanie artefaktu, który należało usunąć już wczoraj.
Wyszukiwanie martwego kodu odbywa się dwiema głównymi metodami: analiza statyczna (bez uruchamiania programu) i analiza dynamiczna (profilowanie pokrycia w czasie wykonania). Każde podejście jest skuteczne dla różnych typów martwego kodu.
Analizatory statyczne obsługują wszystkie popularne języki programowania. Dla Java i Kotlin — SonarQube, IntelliJ IDEA Inspections, SpotBugs. Dla JavaScript i TypeScript — ESLint z regułami no-unused-vars i no-unused-modules. Dla Swift — SwiftLint z regułą unused_declaration. Dla Python — pylint z opcją unused-import i vulture do głębokiego wyszukiwania.
// build.gradle.kts — konfiguracja ProGuard dla Androida
android {
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
// proguard-rules.pro — zachowaj tylko wymagane klasy
-keep class com.example.app.** { *; }
-assumenosideeffects class Timber {
static <methods>;
}
ProGuard nie tylko usuwa nieużywane klasy i metody, ale także minifikuje nazwy w kompilacji release. Kompilacja z włączonym ProGuard automatycznie pokazuje, które klasy i metody są uznawane za nieużywane — w raporcie usage.txt wymieniony jest cały usunięty kod.
Narzędzia pokrycia kodu (JaCoCo dla Java, XCTest coverage dla Swift, Istanbul dla JavaScript) pokazują, które linie i gałęzie są wykonywane podczas testów. Metody z zerowym pokryciem to kandydaci na martwy kod. Jednak brak pokrycia nie gwarantuje, że kod nie jest wywoływany na produkcji — dla pełnej pewności używaj kombinacji analizy statycznej i dynamicznej.
Skonfiguruj CI-pipeline tak, aby kompilacja kończyła się niepowodzeniem po przekroczeniu progu nieużywanych deklaracji. SonarQube Quality Gate z regułą „Udział nieużywanego kodu prywatnego nie więcej niż 3%" zapobiega gromadzeniu się martwego kodu na poziomie procesu programistycznego.
Proces usuwania martwego kodu składa się z czterech kroków: znajdź, sprawdź, usuń, sprawdź ponownie. Pominięcie któregokolwiek kroku zwiększa ryzyko regresji.
Pierwszy krok — znajdź kandydatów za pomocą analizatora statycznego. Uzyskaj raport o nieużywanych deklaracjach: funkcjach, klasach, zmiennych, importach. Odfiltruj fałszywe alarmy — analizatory czasami się mylą przy refleksji, dynamicznym ładowaniu klas lub ukrytych wywołaniach przez serializację.
Drugi krok — sprawdź przez git blame i historię zmian. Sprawdź, kiedy i dlaczego kod został napisany. Jeśli kod był częścią funkcji wyłączonej przez feature toggle, upewnij się, że toggle jest utrwalony i nie zostanie włączony z powrotem. Zakomentuj kod, co do którego masz wątpliwości, i zostaw TODO z zgłoszeniem do ponownego sprawdzenia za miesiąc.
Trzeci krok — usuń w osobnej gałęzi z uruchomieniem pełnego zestawu testów. Jeśli testy przechodzą — prawdopodobieństwo regresji jest niskie. Jeśli testy padają — oznacza to, że kod jednak jest używany i trzeba ustalić, w jakim scenariuszu.
// przed — martwy kod i kod zombie w tym samym pliku
int calculateV1(int price) { // nigdzie nie wywoływane
int tax = price * 0.18;
return price + tax;
}
int calculateV2(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
// po — martwy kod usunięty, kod zombie wyczyszczony
int calculatePrice(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
Czwarty krok — code review zmian. Recenzent musi potwierdzić, że kod rzeczywiście jest martwy. Jeśli recenzent nie jest pewny — zostaw komentarz w kodzie i odłóż usunięcie do pełnej analizy. Po scaleniu gałęzi — usuń gałąź, aby nie mnożyć kodu zombie w repozytorium git.
Wprowadź zasadę: żaden pull request nie powinien zawierać nowego martwego kodu. Dodaj linter do hooków pre-commit, który blokuje commit w przypadku nieużywanych zmiennych lub importów. Profilaktyka jest zawsze tańsza niż czyszczenie.
Często zadawane pytania
Tak, jeśli martwy kod zawiera błędy składniowe lub odwołuje się do usuniętych typów. Współczesne kompilatory i tak sprawdzają martwe gałęzie, dlatego błąd w bloku if(false) spowoduje odmowę kompilacji. To zabezpieczenie: kod nie powinien być na tyle martwy, żeby kompilator go nie sprawdzał.
Kod zombie wprowadza w błąd: nowy programista widzi funkcję z dokumentacją i zakłada, że jest używana. Traci czas na studiowanie niedziałającego kodu i może przypadkowo powiązać nową logikę z przestarzałym bytem, co stworzy trudny do wyśledzenia błąd.
Użyj ESLint z regułami no-unused-vars i no-unused-modules, a także narzędzia knip — analizuje ono exports i imports w całym projekcie, znajdując nieużywane pliki, funkcje i zależności. Dla dużych monorepozytoriów knip pokazuje najbardziej pełny obraz.
Lepiej usuwać przed wydaniem, ale nie w ostatniej chwili. Usuwanie martwego kodu to praca techniczna, którą planuje się w sprincie osobno. Bezpośrednio przed wydaniem usunięcie może wprowadzić niestabilność, jeśli kod okazał się nie tak martwy, jak się wydawało.
Tak, współczesne kompilatory i minifikatory (ProGuard, R8, Terser, Closure Compiler) usuwają nieosiągalny kod na poziomie Dead Code Elimination. Nie zwalnia to jednak z konieczności czyszczenia źródeł: kompilator usuwa kod z pliku binarnego, ale nie z repozytorium — programiści nadal będą się na niego natykali podczas czytania.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również