„Zakulać” lub „podeprzeć kulami” — oznacza stworzenie tymczasowego rozwiązania problemu, które zamyka błąd lub dodaje funkcjonalność, ale nie usuwa pierwotnej przyczyny i nie spełnia standardów architektonicznych projektu. Kule są nieuniknione w każdym rozwoju: terminy, niepełne zrozumienie systemu i zewnętrzne ograniczenia zmuszają do podejmowania kompromisowych decyzji. Według Refactoring Guru, kluczowa różnica między pragmatyczną kulą a długiem technicznym — w świadomości decyzji i posiadaniu planu jej usunięcia. Umiejętne korzystanie z tymczasowych rozwiązań wymaga dyscypliny i dokumentowania.
Najważniejsze
Kula (crutch) — rozwiązanie programowe, które działa, ale jest zrobione „na szybko”: zamyka konkretny problem, ale nie usuwa jego przyczyny, nie przestrzega architektury projektu i może się zepsuć przy najmniejszych zmianach otoczenia. Metafora jest trafna — jak prawdziwa kula, taki kod pomaga „iść”, ale nie leczy „nogi”.
Programiści „podpierają kulami” błędy, niekompatybilności wersji, cechy platformy i pilne wymagania klienta. Typowa kula — kula-warunek: jeśli iOS 15, dodaj odstęp; jeśli Huawei — ukryj przycisk. Takie sprawdzenia mnożą się i zamieniają kod w „warstwowe ciasto” z platformowych i wersyjnych rozgałęzień.
Kule bywają różnej skali: od jednej linii z kulowym warunkiem do całego modułu-warstwy, który „naprawia” zachowanie biblioteki. Ważne jest zrozumieć, że kula to nie zawsze zło: w odpowiednich rękach to narzędzie pozwalające wydać produkt na czas. Problem zaczyna się, gdy kula pozostaje w kodzie na zawsze.
Główną przyczyną pojawiania się kul jest konflikt między idealnym rozwiązaniem a rzeczywistymi ograniczeniami projektu. Programista wie, jak zrobić poprawnie, ale czas, pieniądze lub ograniczenia techniczne na to nie pozwalają. W rezultacie powstaje kompromisowe rozwiązanie, które „po prostu działa”.
Rozważmy cztery główne przyczyny, dla których programiści świadomie sięgają po kule. Zrozumienie tych przyczyn pomaga traktować kule nie jako błąd, ale jako pragmatyczne narzędzie wymagające zarządzania.
Najczęstsza przyczyna. Wydanie jutro, błąd reprodukuje się tylko na konkretnym modelu, architektoniczna naprawa zajmuje dwa tygodnie. Kula-warunek zajmuje godzinę i zamyka problem. Po wydaniu zespół obiecuje wrócić i przepisać poprawnie. „Nie ma nic bardziej stałego niż tymczasowe" — właśnie o takich kulach.
Biblioteka A wymaga Android 12, ale twoja aplikacja obsługuje Android 10. Rozwiązanie — napisać warstwę pośredniczącą, która sprawdza wersję systemu i wybiera ścieżkę wykonania. To kula, ponieważ przy aktualizacji biblioteki warstwę trzeba będzie przepisać. Ale alternatywa — rezygnacja z biblioteki lub wsparcia starych urządzeń — może być gorsza.
// Kula dla zgodności z API 29
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
useModernApi()
} else {
useLegacyFallback()
}
Biblioteka, od której zależy projekt, zawiera błąd, ale jej aktualizacja może zająć tygodnie (potrzebny PR, code review, publikacja). Zamiast czekać zespół pisze wrapper, który łatuje zachowanie biblioteki na bieżąco. Po wydaniu poprawionej wersji biblioteki wrapper jest usuwany. Jeśli nie zostanie usunięty — to już problem architektoniczny.
Nowy programista w legacy-projekcie nie rozumie, dlaczego kod działa właśnie tak. Zamiast się w to zagłębić, dodaje nowy warunek na istniejący. To najbardziej niebezpieczny typ kuli, ponieważ autor nie zdaje sobie sprawy, że to kula. Jedynym lekarstwem jest code review i programowanie w parach dla nowych członków zespołu.
Nie każda kula to zło. W rzeczywistym programowaniu absolutna czystość kodu jest nieosiągalna i często niecelowa. Pragmatyczne podejście uznaje, że tymczasowe rozwiązania są częścią procesu, ale wymaga ich świadomości, dokumentowania i planowania usunięcia. Kula jest uzasadniona, gdy rozwiązuje zadanie biznesowe szybciej niż czyste rozwiązanie architektoniczne.
Kryteria uzasadnionej kuli: zamyka konkretny problem, ma właściciela (kto odpowiada za jej usunięcie) i istnieje plan refaktoryzacji. Jeśli choć jeden z trzech warunków nie jest spełniony — kula zamienia się w dług techniczny. Narzędzia takie jak TODO-komentarze z ticketem w trackerze — to minimalny sposób dokumentowania.
Krytyczny błąd w gałęzi wydaniowej, który trzeba zamknąć przed jutrzejszym wdrożeniem. Czyste rozwiązanie wymaga refaktoryzacji architektury i zajmie dwa tygodnie. Kula — dodać sprawdzenie na nil i wysłać fix jako hotfix. Warunki uzasadnienia: w trackerze utworzono ticket na refaktoryzację, wyznaczono osobę odpowiedzialną, kulę oznaczono komentarzem. Za dwa tygodnie zespół wraca do zadania.
// TODO: IT-1234 — usuń tę kulę po refaktoryzacji AuthService
guard let userId = session.user?.id else {
return Result.failure(.notAuthenticated)
}
Granica między świadomą kulą a problemem architektonicznym (długiem technicznym) przebiega według dwóch parametrów: świadomość decyzji i posiadanie planu jej usunięcia. Kula to zawsze tymczasowe rozwiązanie z określonym czasem życia. Dług techniczny — to konsekwencje wielu kul pozostawionych bez uwagi.
| Parametr | Świadoma kula | Dług techniczny |
|---|---|---|
| Świadomość | Zespół wie, że to tymczasowe rozwiązanie | Nikt nie pamięta, dlaczego kod jest taki |
| Dokumentacja | Jest TODO, ticket w trackerze | Brak komentarzy, linków, opisu |
| Plan usunięcia | Wyznaczony sprint na refaktoryzację | „Kiedyś przepiszemy” |
| Wpływ | Lokalny, nie przeszkadza nowej funkcjonalności | Blokuje zmiany, spowalnia rozwój |
Sytuacja pogarsza się, gdy liczba kul przekracza masę krytyczną. Każda nowa kula zwiększa „kruchość” systemu: zmiana w jednym miejscu psuje inne. W rezultacie rozwój zwalnia, błędy mnożą się, a nowy programista nie może zrozumieć kodu bez pomocy autora. W tym momencie kule przestają być tymczasowymi rozwiązaniami i stają się problemem architektonicznym.
Jeśli w kodzie występuje pięć zagnieżdżonych sprawdzeń wersji systemu, producenta urządzenia i obecności konkretnej biblioteki — to nie kula, to problem architektoniczny. Jeśli dodanie jednej poprawki powoduje trzy regresje w sąsiednich modułach — kule przestały być lokalne. Jeśli code review regularnie odrzuca się z powodu „kolejnej kuli” — czas zaplanować refaktoryzację.
Refaktoryzacja kul — proces zastępowania tymczasowych rozwiązań architektonicznie poprawnymi. Wymaga czasu, dlatego potrzebna jest strategia priorytetyzacji: nie wszystkie kule trzeba usuwać natychmiast. Dobra strategia — oceniać każdą kulę według dwóch parametrów: częstotliwość zmian w tym obszarze kodu i wpływ na użytkowników.
Wysoki priorytet — kule w często zmienianych modułach (logika biznesowa, UI ogólnego przeznaczenia), które spowalniają rozwój i powodują regresje. Średni priorytet — kule w rzadko zmienianych modułach, ale z potencjalnym wpływem na użytkowników (przetwarzanie płatności, autoryzacja). Niski priorytet — kule w legacy-kodzie, który działa stabilnie i nie jest planowany do modyfikacji.
Krok 1: inwentaryzacja — znajdź wszystkie TODO i FIXME związane z kulami. Krok 2: ocena — określ, które z nich są nadal aktualne. Krok 3: planowanie — przypisz refaktoryzację kul do sprintu, zaczynając od wysokopriorytetowych. Krok 4: zastąpienie — zaimplementuj czyste rozwiązanie, usuń kulę i jej TODO-komentarz. Krok 5: weryfikacja — upewnij się, że testy przechodzą i nie ma regresji.
# Znajdź wszystkie TODO-kule w projekcie
grep -r "TODO\|FIXME\|HACK\|XXX" --include="*.kt" --include="*.swift" .
Najlepszym sposobem walki z kulami jest nie tworzenie ich bez potrzeby. Zanim napiszesz kulę, zadaj sobie trzy pytania: czy można zrobić czyste rozwiązanie w rozsądnym czasie? Czy istnieje alternatywa, która nie jest kulą? Czy zespół będzie miał czas wrócić i to przepisać? Jeśli choć na jedno pytanie odpowiedź brzmi „nie” — pomyśl jeszcze raz, zanim „podeprzesz” kod.
Często zadawane pytania
Zakulać — napisać tymczasowe rozwiązanie, które zamyka problem, ale nie usuwa jego przyczyny. Kod działa, ale nie odpowiada architekturze projektu i może się zepsuć przy zmianach.
Kula — świadome tymczasowe rozwiązanie z planem usunięcia. Dług techniczny — konsekwencje wielu zapomnianych kul. Kula jest lokalna, dług systemowy i blokuje rozwój.
Gdy termin jest krytyczny, czyste rozwiązanie wymaga czasu, a kula jest udokumentowana TODO-komentarzem i ticketem w trackerze. Warunek: kula ma plan usunięcia w przewidywalnej przyszłości.
Dodaj TODO lub FIXME z numerem ticketa i krótkim opisem prawidłowego rozwiązania. Przykład: // TODO: IT-567 — przepisać używając Factory pattern. Bez ticketa kula zostanie zapomniana.
Przeprowadź inwentaryzację wszystkich TODO, oceń priorytet, zacznij od często zmienianych modułów. Zastąp kulę czystym rozwiązaniem, usuń komentarz i sprawdź testami.
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ż