Rzadko kiedy rozpoczyna się dzień od decyzji:
„Dzisiaj trochę zadłużymy architekturę.”
Dług techniczny zazwyczaj pojawia się w znacznie bardziej eleganckiej formie.
„Na razie wystarczy.”
„Zrobimy to prościej.”
„Wrócimy do tego po MVP.”
„Nie ma sensu teraz budować całego mechanizmu dla jednego przypadku.”
I najgorsze jest to, że bardzo często są to całkiem dobre decyzje.
Przy budowaniu produktu trudno byłoby funkcjonować inaczej. Nie każda abstrakcja musi powstać od razu. Nie każdy ręczny krok potrzebuje automatyzacji. Nie każdy model danych musi od pierwszego dnia przewidywać następne pięć lat rozwoju.
Czasem prostsze rozwiązanie jest dokładnie tym właściwym.
Problem polega na tym, że dług techniczny ma dość osobliwy sposób przedstawiania rachunku.
Nie przychodzi następnego dnia.
Najpierw działa.
Potem nadal działa.
Jeszcze przez jakiś czas właściwie nic szczególnego się nie dzieje.
Dopiero po kilku kolejnych zmianach okazuje się, że funkcjonalność, która kiedyś zajmowała godzinę, potrzebuje pół dnia. Deployment wymaga pamiętania o jednym dodatkowym wyjątku. Nowa część systemu musi uwzględnić decyzję, która miała być tylko tymczasowa.
I wtedy łatwo dojść do wniosku, że system „po prostu zrobił się bardziej złożony”.
Tylko że czasem ta złożoność nie pojawiła się sama.
Po prostu właśnie zaczęliśmy płacić odsetki.
Bo dług techniczny bardzo często wygląda na początku jak oszczędność czasu.
Dopiero później okazuje się, że czas nie został oszczędzony.
Został pożyczony.
SaaS bez długu technicznego byłby podejrzany
Przy budowaniu produktu cyfrowego nie każda decyzja powinna być docelowa.
Jeżeli nie wiemy jeszcze, czy funkcja będzie używana, budowanie wokół niej perfekcyjnego modelu domenowego może być mniej odpowiedzialne niż prosty kompromis.
Jeżeli proces administracyjny wykonujemy dwa razy w miesiącu, automatyzowanie go przez trzy dni niekoniecznie jest oznaką dojrzałości.
Jeżeli przykładowy MVP ma kilkudziesięciu użytkowników, nie każda część architektury musi być przygotowana na dziesięć milionów requestów.
Dług techniczny nie musi więc oznaczać złego kodu.
Może być świadomą decyzją:
dzisiaj wybieramy rozwiązanie tańsze, wiedząc, że później może kosztować więcej.
I to jest całkowicie racjonalne.
Pod warunkiem, że pamiętamy o drugiej części tego zdania.
Bo dług ma jeszcze jedną cechę.
Ma oprocentowanie.
Odsetki rzadko przychodzą jako osobna faktura
Pierwszy symptom często wygląda niewinnie.
Zmiana, która kiedyś zajmowała godzinę, zaczyna zajmować pół dnia.
Deployment wymaga sprawdzenia jeszcze jednej rzeczy.
Nowy feature musi uwzględnić stary wyjątek.
Przed zmianą tabeli ktoś mówi:
„Tylko uważaj, bo jeszcze ten jeden proces z tego korzysta”.
Nie ma awarii.
Nie ma incydentu.
Nie ma nawet konkretnego miejsca w kodzie, na które można wskazać i powiedzieć:
oto nasz dług techniczny.
System po prostu zaczyna pobierać niewielką opłatę za każdą kolejną zmianę.
I to właśnie jest oprocentowanie.
Ręczna operacja wykonywana raz na pół roku może mieć oprocentowanie praktycznie zerowe.
Nieformalny kontrakt między pięcioma usługami, którego nikt nie chce zmieniać, może pobierać odsetki codziennie.
Dlatego samo pytanie:
Czy mamy dług techniczny?
niewiele mówi.
Prawie każdy rozwijany system go ma.
Znacznie trafniejsze jest:
Jak często za niego płacimy?
Czasem rachunek przychodzi w 45 minut
1 sierpnia 2012 roku Knight Capital wdrażał zmianę do swojego systemu obsługującego handel na giełdzie.
Nowy kod został poprawnie zainstalowany na siedmiu z ośmiu serwerów.
Na ósmym pozostała stara wersja.
Było tam również coś jeszcze.
Dawna funkcja o nazwie Power Peg, nieużywana od lat, nadal pozostawała w kodzie i nadal można było ją uruchomić. Nowa funkcjonalność wykorzystała flagę, która wcześniej aktywowała właśnie ten stary mechanizm.
Na siedmiu serwerach wszystko działało zgodnie z planem.
Ósmy zinterpretował tę samą flagę zupełnie inaczej.
W ciągu około 45 minut system wygenerował miliony błędnych zleceń. SEC opisała później około 4 milionów wykonanych transakcji obejmujących niemal 400 milionów akcji. Knight poniósł stratę przekraczającą 460 milionów dolarów.
Najciekawsze nie jest nawet to, że ktoś nie zaktualizował jednego serwera.
SEC ustaliła również, że Knight nie miał procedury wymagającej drugiej osoby do weryfikacji deploymentu. Stary kod pozostał możliwy do uruchomienia. Przed otwarciem rynku system wygenerował 97 wiadomości wskazujących problem z Power Peg, ale wiadomości te nie zostały zaprojektowane jako właściwe alerty i nie doprowadziły do reakcji.
To już nie wygląda jak jeden bug.
To wygląda jak kilka małych decyzji, które przez długi czas nie kosztowały prawie nic.
Nieusunięty kod.
Proces deploymentu oparty na założeniu.
Informacja techniczna, która formalnie istniała, ale operacyjnie nie była alarmem.
Każda z osobna mogła spokojnie przeżyć jeszcze kilka lat.
Razem dostały okazję, żeby naliczyć wszystkie odsetki naraz.
To właśnie odróżnia dług od zwykłego bałaganu
Bałagan można zobaczyć.
Dług bywa schludny.
Możemy mieć dobre API.
Czytelne repozytorium.
Terraform.
Testy.
Structured logging.
Ładny dashboard.
A mimo to istnieją decyzje, które coraz mocniej ograniczają następne decyzje.
Przykład jest bardzo prosty.
Na początku przechowujemy fragment zmiennego modelu w JSON-ie, bo nie wiemy jeszcze, jak produkt się rozwinie.
Z pozoru rozsądne.
Potem frontend zaczyna polegać na dwóch polach z tego JSON-a.
Później raportowanie korzysta z trzeciego.
Integracja zewnętrzna zaczyna oczekiwać konkretnej struktury.
W końcu okazuje się, że „tymczasowe pole” ma kontrakt silniejszy niż część oficjalnego API.
Sam JSON nie był problemem.
Problem pojawił się wtedy, kiedy tymczasowa decyzja zaczęła produkować trwałe zależności.
Dokładnie tak samo może być z ręcznym wyjątkiem w Terraformie, retry bez dobrze określonej idempotencji czy procesem deploymentu zawierającym jeden krok, którego „na razie nie automatyzujemy”.
W pewnym momencie nie trzeba już niczego zepsuć.
Wystarczy, że każda następna zmiana musi pamiętać o przeszłości.
Dług security działa trochę inaczej
Jest rodzaj długu, który potrafi długo nie naliczać widocznych odsetek.
Security.
Można przez trzy lata mieć zbyt szerokie uprawnienie IAM i nie zobaczyć ani jednego problemu.
Można nie aktualizować biblioteki i nadal przechodzić zielone testy.
Można trzymać administracyjny endpoint trochę zbyt szeroko dostępny.
Można mieć sekret w miejscu, w którym nie powinien się znaleźć.
System nie staje się przez to wolniejszy.
Nowe funkcjonalności nie muszą powstawać trudniej.
Klient niczego nie zauważy.
Aż do momentu, w którym koszt przestaje być techniczny.
Equifax i jedna aktualizacja, która nie była tylko aktualizacją
W marcu 2017 roku US-CERT ostrzegł Equifax o krytycznej podatności w Apache Struts.
Patch był dostępny.
Wewnętrzna polityka Equifax wymagała załatania takiej podatności w ciągu 48 godzin.
Mimo tego portal obsługujący spory konsumenckie pozostał podatny przez kolejne miesiące. Według skargi FTC osoba odpowiedzialna za ten system nie otrzymała właściwej wiadomości, a późniejszy automatyczny skan podatności był skonfigurowany w sposób, który nie wykrył problemu.
Skutek jest dobrze znany.
Naruszenie objęło dane około 147 milionów osób, a późniejsze globalne porozumienie z FTC, CFPB oraz stanami opiewało na co najmniej 575 milionów dolarów, potencjalnie do 700 milionów.
Ale sprowadzenie całej historii do:
ktoś zapomniał zainstalować patch
byłoby zbyt wygodne.
GAO wskazało później również problemy z identyfikacją podatnych zasobów, wykrywaniem, segmentacją oraz zarządzaniem danymi. FTC opisywała także „legacy infrastructure”, która była już wtedy przez samą firmę określana jako przestarzała.
I właśnie dlatego ten przypadek jest interesujący z perspektywy długu technicznego.
Nie chodzi o jedną brakującą aktualizację.
Chodzi o system, w którym koszt kilku dawnych kompromisów spotkał się w jednym miejscu.
Dług techniczny dla security ma bardzo specyficzne oprocentowanie.
Przez długi czas może wynosić zero.
Potem staje się trudne do policzenia.
A teraz potrafimy produkować dług szybciej niż kiedykolwiek
Jest jeszcze jeden powód, dla którego ten temat robi się dziś ciekawszy.
Kod stał się tani.
Modelowi można powiedzieć:
„zrób endpoint do uploadu”
„dodaj autentykację”
„podłącz Stripe”
„zrób panel admina”
„napisz Terraform”
i po chwili mamy coś, co wygląda bardzo przekonująco.
Czasem również działa.
Vibe coding nie stworzył długu technicznego.
Zmienił tylko jego prędkość.
Wcześniej trzeba było spędzić godzinę, żeby napisać kod, którego się dobrze nie rozumiało.
Dzisiaj można wygenerować go w minutę.
NIST w aktualnych materiałach dotyczących AI w procesie developmentu wskazuje wprost na ryzyka takie jak generowanie niebezpiecznego kodu, nadmierne uprawnienia agentów, wyciek danych czy artefakty trafiające do łańcucha dostaw bez odpowiedniego zatwierdzenia. Jednocześnie rekomenduje, aby treści generowane przez AI były monitorowane i weryfikowane przez ludzi.
OWASP idzie jeszcze bardziej praktycznie: kod wygenerowany przez AI powinien mieć ludzkiego właściciela, który go rozumie, zatwierdza i bierze odpowiedzialność za jego bezpieczeństwo.
I tutaj pojawia się chyba najbardziej współczesna odmiana długu technicznego.
Kod, który działa, ale którego nikt naprawdę nie posiada poznawczo.
Nie chodzi o to, czy napisał go człowiek, Claude, Copilot czy inny agent.
Chodzi o moment, kiedy odpowiedź na pytanie:
Dlaczego ten mechanizm autoryzacji wygląda właśnie tak?
brzmi:
Bo tak został wygenerowany i testy przechodzą.
To jest bardzo drogi kredyt.
Szczególnie w security.
Happy path nie płaci rachunków
AI wyjątkowo dobrze przyspiesza budowanie happy path.
Użytkownik wysyła request.
Walidacja przechodzi.
Dane zapisują się.
Response wraca.
Demo wygląda dobrze.
Tylko security bardzo często mieszka poza happy path.
Co jeżeli ten sam request wykona ktoś inny?
Czy autoryzacja sprawdza własność zasobu, czy tylko obecność tokena?
Co jeżeli payload jest dziesięć razy większy?
Czy endpoint można wywołać tysiąc razy?
Czy wygenerowana polityka IAM rzeczywiście potrzebuje Resource: *?
Czy zasleżności, które model wybrał, nadal powinno być używane?
Czy sekret przypadkiem nie trafił do logów?
To nie są pytania szczególnie widowiskowe.
Nie poprawiają demo.
Nie zwiększają velocity widocznego na pierwszy rzut oka.
Dlatego bardzo łatwo pożyczyć od nich trochę czasu.
Problem w tym, że security bardzo rzadko zgłasza się później po małą ratę.
Dług zaczyna być groźny, kiedy traci datę ważności
Nie potrzebuję ogromnego rejestru TECH-DEBT.
W małym SaaS-ie wystarczy często odpowiedzieć przy świadomym kompromisie na cztery pytania:
- Co dokładnie odkładamy?
- Dlaczego robimy to teraz?
- Co powinno spowodować powrót do tej decyzji?
- Co się stanie, jeśli nigdy do niej nie wrócimy?
To zmienia bardzo dużo.
„Retry jest do poprawienia” niewiele znaczy.
Ale:
Nie budujemy jeszcze osobnego mechanizmu idempotencji, ponieważ ten flow nie wykonuje obecnie nieodwracalnych pobocznych działań. Wracamy do decyzji przed wprowadzeniem płatności.
to już zupełnie inna informacja.
Podobnie:
IAM jest teraz szerszy, bo iterujemy nad zakresem zasobów. Przed publicznym uruchomieniem tej funkcji ograniczamy uprawnienia.
Albo:
Model danych jest celowo elastyczny, dopóki nie poznamy rzeczywistych wzorców użycia. Jeśli raportowanie zacznie zależeć od tej struktury, formalizujemy schemat.
Dług nadal istnieje.
Ale ma granicę.
A to bardzo dużo.
Nie każdy dług warto spłacać
To też jest część, o której łatwo zapomnieć.
Niektóre kompromisy są całkowicie poprawne aż do końca życia produktu.
Ręczna operacja wykonywana cztery razy w roku może nigdy nie potrzebować automatyzacji.
Fragment kodu może być nieładny, ale stabilny, dobrze przetestowany i nigdy niezmieniany.
Tymczasowa architektura może przeżyć cały produkt, bo problem, na który miała być przebudowana, nigdy się nie pojawił.
Nie ma nagrody za system pozbawiony wszystkich niedoskonałości.
Jest za to koszt czasu poświęconego na rozwiązywanie problemów, których biznes jeszcze nie ma.
Dojrzałe podejście nie polega więc na eliminowaniu całego długu.
Polega na rozpoznaniu, który dług ma wysokie oprocentowanie.
Jeżeli kompromis dotyczy kodu łatwego do wymiany, można być odważniejszym.
Jeżeli dotyczy danych, autoryzacji, pieniędzy, granic między systemami albo operacji trudnych do cofnięcia to stopy procentowe robią się trochę mniej przyjazne.
Najgorszy moment przychodzi później
Najpierw to my podejmujemy kompromis.
Potem kompromis wpływa na następną decyzję.
Nowa funkcjonalność musi uwzględnić dawny wyjątek.
Kolejna usługa musi korzystać z kontraktu, którego nikt nigdy formalnie nie zaprojektował.
Nie zmieniamy deploymentu, bo jest tam ten jeden krok.
Nie aktualizujemy komponentu, bo nie jesteśmy pewni, co jeszcze od niego zależy.
Nie dotykamy uprawnień, bo „na razie działa”.
I gdzieś po drodze role się odwracają.
To już nie my zarządzamy długiem technicznym.
Dług zaczyna zarządzać tym, jakie decyzje możemy jeszcze podjąć.
To chyba najlepszy moment, żeby przestać mówić o nim jak o jakości kodu.
Bo wtedy problemem nie jest już kod.
Problemem jest utrata swobody.
Dług techniczny nie musi być mały
Musi być widoczny.
MVP może mieć skróty.
SaaS może mieć kompromisy.
Architektura może zawierać elementy, które kiedyś prawdopodobnie zostaną wymienione.
To nie jest porażka projektowa.
Czasem dokładnie tak wygląda rozsądne budowanie produktu.
Ale przy każdym takim wyborze warto wiedzieć, czy pożyczamy godzinę, tydzień czy kawałek przyszłej architektury.
Bo najdroższy dług techniczny nie jest tym, który świadomie zaciągnęliśmy.
Najdroższy jest ten, o którym zespół zdążył zapomnieć, ale system nadal pamięta.