Klient pyta: "Skoro dodanie tej funkcji w nowej aplikacji zajęłoby tydzień, dlaczego tutaj potrzeba trzech tygodni?"
To bardzo dobre pytanie.
I często odpowiedź nie brzmi: "bo programiści pracują wolniej".
Problem może znajdować się znacznie głębiej - w architekturze systemu, jego zależnościach, sposobie przechowywania danych, braku testów, historycznych decyzjach i kolejnych zmianach dokładanych przez lata.
Właśnie dlatego koszt rozwoju oprogramowania nie jest stały.
Ta sama funkcja może kosztować zupełnie różną kwotę w dwóch różnych systemach.
Kod nie jest wyceniany tylko według liczby funkcji
Na pierwszy rzut oka zadanie może wyglądać banalnie.
"Dodajmy możliwość eksportowania danych do Excela."
Albo: "Dodajmy nową rolę użytkownika."
Albo: "Połączmy system z naszym CRM."
Problem w tym, że funkcja nigdy nie istnieje w całkowitym oderwaniu od reszty systemu.
Nowa funkcjonalność może wymagać zmian w:
- bazie danych,
- API,
- backendzie,
- frontendzie,
- systemie uprawnień,
- logowaniu,
- raportowaniu,
- integracjach,
- testach,
- mechanizmach cache,
- dokumentacji,
- procesie wdrożenia.
Im bardziej powiązany jest system, tym więcej elementów trzeba przeanalizować przed zmianą.
Największy koszt może powstać przed napisaniem pierwszej linii kodu
W dojrzałym systemie programista nie powinien po prostu zacząć pisać.
Najpierw trzeba odpowiedzieć:
- Gdzie ta funkcja powinna zostać dodana?
- Z jakimi modułami będzie się komunikować?
- Jakie dane wykorzystuje?
- Czy istniejące mechanizmy uprawnień ją obejmują?
- Czy zmiana wpłynie na inne procesy?
- Jakie testy trzeba zaktualizować?
- Czy obecna architektura w ogóle pozwala zrobić to poprawnie?
To wszystko jest częścią kosztu wytworzenia funkcji.
Dlatego w starym systemie znaczną część pracy może stanowić nie samo programowanie, lecz rozpoznanie zależności i ograniczeń istniejącego rozwiązania.
Technical debt działa jak odsetki
Dobrym sposobem myślenia o technical debt jest właśnie koszt kolejnych zmian.
Jeżeli pewne rozwiązanie zostało kiedyś zrobione szybko, może być całkowicie uzasadnione.
Problem pojawia się wtedy, gdy tymczasowe rozwiązanie staje się stałym elementem systemu.
Powstaje kolejna funkcja.
Potem kolejna.
Pojawia się wyjątek.
Potem kolejny wyjątek.
Do tego dochodzi integracja, obejście problemu, ręczny proces i dodatkowa reguła.
Po kilku latach nikt nie pamięta już, dlaczego system działa właśnie w ten sposób.
Ale każda kolejna zmiana musi uwzględnić wszystkie te historyczne decyzje.
Martin Fowler opisuje technical debt jako dodatkowy wysiłek ponoszony przy zmianach systemu wskutek problemów z jego wewnętrzną jakością.
Można więc powiedzieć: technical debt nie musi zatrzymać rozwoju od razu. Najpierw sprawia, że każda kolejna zmiana staje się droższa.
Sygnał pierwszy: "przy okazji trzeba poprawić jeszcze pięć rzeczy"
To jeden z najbardziej charakterystycznych objawów.
Klient zamawia jedną funkcję.
Podczas analizy okazuje się, że aby ją wdrożyć, trzeba:
- poprawić strukturę tabeli,
- zmienić sposób autoryzacji,
- zaktualizować bibliotekę,
- naprawić stare API,
- przepisać fragment frontendu.
Nagle mała funkcja przestaje być małą funkcją. Nie dlatego, że wymaganie jest skomplikowane. Dlatego, że system nie ma już odpowiednich granic architektonicznych.
Sygnał drugi: jedna zmiana wymaga testowania całego systemu
Jeżeli niewielka modyfikacja wymaga pełnego ręcznego regresu, organizacja płaci za brak automatyzacji.
Wraz ze wzrostem systemu liczba możliwych kombinacji rośnie.
Bez odpowiedniego zestawu testów coraz trudniej mieć pewność, że nowa funkcja nie uszkodziła starej.
To z kolei powoduje ostrożność.
Wdrożenia są rzadsze.
Zmiany są większe.
Ryzyko rośnie.
A większe wdrożenia są trudniejsze do diagnozowania w przypadku problemów.
Powstaje błędne koło.
Sygnał trzeci: "tego modułu lepiej nie ruszać"
To zdanie powinno zapalić lampkę ostrzegawczą.
Jeżeli konkretny moduł stał się obszarem, którego zespół unika, ponieważ jego zachowanie jest nieprzewidywalne, system posiada istotny problem utrzymaniowy.
Jeszcze gorzej, jeśli jego działanie zna tylko jedna osoba. Wtedy firma ma nie tylko technical debt. Ma również knowledge risk.
Odejście jednego pracownika może oznaczać utratę wiedzy potrzebnej do bezpiecznego rozwijania systemu.
Sygnał czwarty: każda funkcja wymaga wyjątków
Dobrze zaprojektowany system powinien mieć przewidywalne reguły.
Jeżeli każda kolejna funkcja wymaga dopisywania specjalnego wyjątku, dodatkowego warunku albo indywidualnej ścieżki, architektura prawdopodobnie zaczyna ograniczać rozwój.
To często prowadzi do kodu, którego nie da się już łatwo przewidywać.
A brak przewidywalności oznacza większy koszt analizy, testów i utrzymania.
Czy trzeba wszystko przepisać?
Nie.
I tutaj dochodzimy do bardzo ważnego rozróżnienia.
Technical debt nie oznacza automatycznie konieczności rewrite'u.
Możliwe rozwiązania obejmują:
Refaktoryzację
Czyli poprawę struktury istniejącego kodu bez zmiany jego zachowania biznesowego.
To dobry kierunek, kiedy system nadal ma sensowną architekturę, ale konkretne fragmenty są trudne w utrzymaniu.
Modernizację wybranych komponentów
Nie trzeba wymieniać całej aplikacji.
Można zacząć od najbardziej problematycznego modułu, integracji albo warstwy.
Stopniową migrację
Nowe elementy mogą działać obok starego systemu, a kolejne obszary są sukcesywnie przenoszone.
To podejście pozwala ograniczyć ryzyko jednorazowej migracji. W literaturze dotyczącej legacy modernization często wykorzystuje się właśnie stopniowe wydzielanie funkcjonalności i zastępowanie kolejnych części systemu.
Rewrite
Budowa nowego systemu ma sens wtedy, gdy obecna architektura jest na tyle ograniczająca, że dalsze modernizowanie nie daje uzasadnionego zwrotu.
Ale rewrite powinien być decyzją wynikającą z analizy, a nie reakcją na frustrację zespołu.
Kiedy nie warto jeszcze inwestować w modernizację?
Technical debt sam w sobie nie jest powodem do zatrzymywania rozwoju. Każdy system ma pewien poziom długu technicznego. Czasem jego spłacanie nie ma sensu ekonomicznego.
Jeżeli aplikacja:
- działa stabilnie,
- jest bezpieczna,
- ma niewielką liczbę zmian,
- obsługuje proces, który nie będzie się istotnie rozwijał,
- nie generuje problemów operacyjnych,
może być racjonalne pozostawienie jej w obecnym stanie.
Nie chodzi o to, aby każdy system był technologicznie idealny.
Chodzi o to, aby poziom długu był świadomą decyzją.
Kiedy koszt długu staje się problemem biznesowym?
Wtedy, gdy zaczyna wpływać na wyniki firmy.
Na przykład:
Nowa funkcja miała wejść na rynek w miesiąc, ale potrzebuje trzech.
Integracja z nowym partnerem przeciąga się, bo API starego systemu nie pozwala łatwo obsłużyć nowych danych.
Kluczowa osoba w zespole musi za każdym razem uczestniczyć w pracach, bo tylko ona zna stary moduł.
Każde większe wdrożenie wymaga wielogodzinnego regresu.
Konkurent szybciej wprowadza nowe funkcje, bo jego platforma pozwala szybciej eksperymentować.
W tym momencie technical debt przestaje być problemem działu IT.
Staje się problemem biznesowym.
Jak mierzyć, czy sytuacja się pogarsza?
Nie trzeba tworzyć skomplikowanego systemu KPI.
Warto obserwować kilka prostych wskaźników:
Lead time - ile czasu mija od rozpoczęcia pracy nad zmianą do jej wdrożenia.
Częstotliwość wdrożeń - jak często zespół może bezpiecznie dostarczać zmiany.
Change failure rate - jak często wdrożenia powodują problemy.
Czas przywrócenia działania - jak szybko można wrócić do stabilnego działania po awarii.
Czas realizacji funkcji - czy podobne zadania wymagają coraz większego nakładu.
Do tego warto analizować liczbę ręcznych operacji, pokrycie testami, aktualność zależności i czas potrzebny na wdrożenie nowego programisty do projektu.
Takie dane pozwalają zobaczyć, czy problem jest rzeczywiście techniczny, czy może wynika z procesu, wymagań albo sposobu organizacji pracy.
Najgorszym rozwiązaniem jest "jeszcze jedna szybka poprawka"
Jeżeli zespół wie, że architektura wymaga zmian, ale za każdym razem odkłada temat, system może wejść w spiralę.
"Zróbmy teraz workaround."
"Refaktoryzację zrobimy później."
"Na razie wystarczy."
"Przy następnym release."
Problem polega na tym, że następny release przynosi kolejne wymagania.
A każdy kolejny workaround zwiększa koszt następnej zmiany.
Dlatego decyzja o spłacaniu technical debt powinna być częścią strategii rozwoju produktu, a nie przypadkową reakcją na kryzys.
Dobra aplikacja to nie taka, która nigdy się nie starzeje
Każdy system będzie się zmieniał.
Technologie będą się zmieniać.
Klienci będą mieć nowe potrzeby.
Pojawią się nowe integracje.
Zmieni się sposób pracy firmy.
Dlatego celem nie powinno być stworzenie aplikacji, której nigdy nie trzeba będzie modernizować. Celem powinno być stworzenie takiej architektury, w której modernizacja jest możliwa bez zatrzymywania biznesu. To ogromna różnica.
Bo najlepszy system nie jest tym, który wygląda najbardziej nowocześnie w dniu uruchomienia. Jest nim system, który również po kilku latach pozwala firmie szybko reagować na zmiany.
A jeśli każda nowa funkcja kosztuje coraz więcej, to nie zawsze oznacza, że funkcja jest trudna.
Być może trudny stał się już sam system.



