Logo David Burdelak
Webdevelopment

Jak zmierzyć i spłacać dług technologiczny w projekcie

Dług technologiczny

Każdy projekt, który żyje dłużej niż rok, ma dług technologiczny. Sama jego obecność nie jest problemem. Problemem jest dług, którego nikt nie policzył, bo wtedy nie da się zdecydować, co naprawiać najpierw, ile to kosztuje i kiedy przestać. W tym wpisie pokazuję, jak mierzę dług w stronach, sklepach i systemach CRM, jak prowadzę jego listę i jak spłacam go małymi krokami, bez zatrzymywania rozwoju projektu.

Po czym poznaję, że dług zaczyna kosztować

Zanim sięgnę po narzędzia, rozmawiam z zespołem i patrzę na historię zgłoszeń. Dług prawie zawsze daje o sobie znać tymi samymi sygnałami:

  • drobna zmiana, np. nowe pole w formularzu, zajmuje dni zamiast godzin,
  • te same błędy wracają w tych samych modułach, mimo że były już naprawiane,
  • aktualizacja CMS-a lub wtyczki jest odkładana, bo „coś się może wysypać”,
  • są fragmenty kodu, których nikt nie chce dotykać, a tylko jedna osoba wie, jak działają,
  • po każdym wdrożeniu trzeba ręcznie przeklikać pół serwisu, bo nie ma testów.

Osobną odmianą jest dług z nadmiaru, czyli overengineering: zbyt wiele wtyczek, frameworków i warstw abstrakcji tam, gdzie wystarczyłoby prostsze rozwiązanie.

To są objawy, a nie liczby. Pomagają ustalić, gdzie szukać, ale nie wystarczą, żeby przekonać klienta albo zespół do zaplanowania czasu na naprawy. Do tego potrzebuję pomiaru.

Jak zmierzyć dług technologiczny w liczbach

Nie ma jednego wskaźnika „ilości długu”. W praktyce zbieram kilka prostych miar i patrzę, jak zmieniają się w czasie:

  • Czas drobnej zmiany – ile mija od zgłoszenia prostego zadania do wdrożenia. Jeśli rośnie z miesiąca na miesiąc, dług rośnie razem z nim.
  • Błędy i regresje na moduł – które części systemu generują najwięcej poprawek. Wystarczy oznaczać zgłoszenia nazwą modułu.
  • Nieaktualne zależności – ile bibliotek i wtyczek jest za wersją bieżącą i ile z nich ma znane luki bezpieczeństwa.
  • Wydajność front-endu – wyniki Core Web Vitals i waga stron. Ciężkie obrazy czy nadmiar skryptów to też dług, tylko widoczny w raportach, a nie w kodzie.
  • Pokrycie testami kluczowych ścieżek – nie procent całego kodu, tylko to, czy koszyk, formularz kontaktowy czy logowanie mają automatyczne testy.

Część z tych danych zbieram jednym poleceniem. Dla zależności w projektach PHP i JavaScript wystarczy:

composer outdated --direct
npm outdated
npm audit

Do znalezienia miejsc, w których dług boli najbardziej, używam historii repozytorium. Pliki zmieniane najczęściej to kandydaci do spłaty, bo każda trudność w nich powtarza się przy każdym zadaniu:

git log --since="12 months ago" --format=format: --name-only | sort | uniq -c | sort -rn | head -20

Jeśli plik jest jednocześnie często zmieniany, długi i pełen zależności, to właśnie tam „odsetki” są najwyższe.

Rejestr długu, czyli jedna lista zamiast wiedzy w głowach

Dług, który istnieje tylko w pamięci programisty, znika razem z nim z projektu. Dlatego każdą znalezioną pozycję zapisuję w tym samym narzędziu, w którym zespół prowadzi zadania, z osobną etykietą. Dla każdej pozycji uzupełniam kilka pól:

  • co i gdzie – konkretny moduł, plik albo integracja,
  • skutek – co dzieje się dziś, np. wolne zapytania na liście produktów albo blokada aktualizacji CMS-a,
  • ryzyko – bezpieczeństwo, utrata danych, przestój sprzedaży, spadek widoczności w Google,
  • koszt spłaty – szacunek w godzinach lub dniach,
  • koszt odsetek – ile czasu tracimy, dopóki problem istnieje.

Taki rejestr zmienia rozmowę z klientem. Zamiast „trzeba by to kiedyś przepisać” mogę pokazać, że dany moduł kosztuje kilka godzin przy każdej zmianie, a jego naprawa zwróci się po kilku zadaniach.

Co spłacać najpierw

Nie każdy dług trzeba spłacać. Kod, którego nikt nie zmienia i który działa stabilnie, może zostać brzydki latami. Kolejność ustalam według prostych zasad:

  • Najpierw ryzyko – nieaktualne zależności z lukami, brak kopii zapasowych, przestarzała wersja PHP. To dług, który może zamienić się w awarię lub wyciek danych.
  • Potem miejsca, w których zespół pracuje najczęściej – pliki z góry listy z historii repozytorium. Każda poprawa wraca przy każdym zadaniu.
  • Na końcu reszta – drobne niespójności i stary kod, który nikomu nie przeszkadza. Te pozycje spłacam przy okazji, a nie w osobnych zadaniach.

Pomocne jest też pytanie, czy problem rośnie sam z siebie. Ciężkie zdjęcia dodawane przez redakcję przybywają co tydzień, więc odkładanie naprawy tylko powiększa dług. Stara, ale zamknięta funkcja raportu nie zmieni się, dopóki nikt jej nie rusza.

Jak spłacać dług bez zatrzymywania projektu

Wielkie przepisywanie systemu od zera rzadko się udaje: trwa dłużej niż zakładano, a w tym czasie stary system dalej wymaga poprawek. Dużo lepiej sprawdzają się stałe, małe spłaty:

  • Stały budżet w każdej iteracji – np. 15% czasu sprintu na pozycje z rejestru. Dzięki temu spłata nie zależy od tego, czy akurat jest „spokojniejszy miesiąc”.
  • Zasada skauta – jeśli i tak pracuję w danym pliku, zostawiam go w lepszym stanie: poprawiam nazwy, usuwam martwy kod, dopisuję test.
  • Refaktoryzacja małymi krokami – każda zmiana osobno, z testem, w osobnym pull requeście i z code review. Łatwiej ją sprawdzić i w razie problemu wycofać.
  • Nowy moduł obok starego – przy dużych częściach systemu buduję nową wersję równolegle i stopniowo przełączam na nią ruch, zamiast wymieniać wszystko jednego dnia.
  • Automaty, które nie wpuszczą nowego długu – linter, testy i budżet wydajności w CI/CD, czyli automatyczne sprawdzanie szybkości i jakości kodu przed każdym wdrożeniem.

Po kilku tygodniach wracam do miar z początku. Jeśli czas drobnej zmiany spada, a liczba regresji w spłacanych modułach maleje, wiem, że budżet jest wydawany sensownie.

Dwa przykłady z moich audytów

W jednym z audytów strony firmowej większość zdjęć dodawanych przez system ważyła po kilka megabajtów. Strona ładowała się wolno, pogarszały się Core Web Vitals i rosło zużycie transferu. Ręczna optymalizacja setek plików byłaby tylko jednorazową spłatą, bo kolejne zdjęcia znów trafiałyby do serwisu bez kompresji. Trwałym rozwiązaniem jest usunięcie przyczyny: automatyczne skalowanie i kompresja obrazów przy wgrywaniu, a dopiero potem jednorazowe przetworzenie starych plików.

W innym projekcie nowa wersja strony używała pełnych adresów do wewnętrznych podstron i zasobów zamiast linków względnych. Po zmianie domeny i wersji www powstały dziesiątki zbędnych przekierowań 301, a poprawka wymagała zmian w kilkudziesięciu plikach i komponentach. Tu dług dało się zmierzyć wprost: crawl serwisu pokazał liczbę linków prowadzących przez przekierowania. Spłata polegała na ujednoliceniu sposobu budowania adresów w jednym miejscu, tak aby kolejna zmiana domeny nie wymagała szukania linków po całym kodzie. Gdy takie przekierowania nałożą się na reguły serwera, łatwo o pętlę przekierowań, której naprawa kosztuje więcej niż zapobieganie.

Oba przypadki łączy jedno: dług nie wynikał z braku umiejętności, tylko z decyzji „na teraz”, której nikt potem nie zapisał ani nie policzył.

Kod z AI i vibe coding, czyli jak nie dokładać nowego długu

Narzędzia AI przyspieszają pisanie kodu, ale tak samo szybko potrafią produkować dług. Typowy scenariusz to vibe coding: fragment funkcji, zapytanie SQL czy skrypt front-endowy wygenerowany na szybko, który działa, ale nikt w zespole nie wie dokładnie dlaczego. Po kilku takich iteracjach projekt składa się z kawałków bez wspólnej logiki i standardu.

Nie rezygnuję z AI, ale kod z generatora traktuję tak samo jak kod od nowego członka zespołu: przechodzi code review, ma test, a jeśli wprowadza nowe rozwiązanie, zapisuję krótko, dlaczego zostało wybrane. Najważniejsza zasada jest prosta: do repozytorium trafia tylko kod, który ktoś w zespole rozumie i potrafi zmienić. Wtedy AI przyspiesza pracę, zamiast powiększać rejestr długu.