Archeologia oprogramowania: praktyczna metoda analizy kodu dziedziczonego
Przyswoj metodę krok po kroku do bezpiecznego badania nieudokumentowanych, starszych baz kodu – od analizy historii zapisów zmian po refaktoryzację bez zakłócania działania systemu w produkcji.
Istnieje specyficzny rodzaj strachu, który każdy programista w końcu doświadcza.
Otwierasz plik – ma on ponad 4000 wierszy. Nie widać ani jednego komentarza, połowa zmiennych ma nazwy takie jak x2 lub tempFinal_REAL, a gdzieś pośrodku znajduje się funkcja o nazwie doStuff(), która, jak można przypuszczać, jest odpowiedzialna za rozliczenia w całej firmie.
Ruchasz komendę git blame, a ślad prowadzi do kogoś, kto odszedł z firmy sześć lat temu. Poszukiwania w Slack nie przynoszą żadnych informacji na temat powstania tego kodu. Jedyna osoba, która mogłaby jeszcze pamiętać, jest nieobecna w biurze, a szczerze mówiąc, prawdopodobnie sama nie pamiętałaby wszystkich szczegółów.
To jest archeologia oprogramowania.
Nie pojawia się ona na żadnej strukturze organizacyjnej ani w ogłoszeniach o pracę, ale jeśli spędziłeś ponad rok lub dwa na pisaniu oprogramowania, już to praktykujesz. Przeglądasz stary kod w taki sam sposób, w jaki archeolog terenowy przegląda miejsce wykopalisk — powoli, uważnie, starając się odkryć, co tak naprawdę myśleli ludzie, którzy go stworzyli, mimo że dawno odeszli i nie mogą nic wyjaśnić.
Poniżej znajduje się przewodnik, jak wykonywać tę pracę skutecznie — bez tracenia zdrowia psychicznego ani przerywania działania systemu.
Czym właściwie jest archeologia oprogramowania?
Archeologia oprogramowania polega na badaniu, interpretacji i rozumieniu starego lub nieudokumentowanego kodu, zwykle po to, by móc go bezpiecznie utrzymywać, rozszerzać lub ostatecznie zastąpić.
To inny rodzaj zadania niż zwykłe debugowanie. Debugowanie wychodzi z założenia, że rozumiesz system i coś w nim się zepsuło. Archeologia oprogramowania opiera się na przeciwnej zasadzie – jeszcze w ogóle nie rozumiesz systemu, a pierwszym zadaniem jest zdobycie tej wiedzy, zanim odważysz się cokolwiek zmienić.
To różnica między mechanikiem naprawiającym model samochodu, przy którym pracował lata, a tym, który restauruje Peugeota z 1962 roku, którego nigdy wcześniej nie otwierał. Ten sam zestaw kluczy, zupełnie inny sposób myślenia.
Większość inżynierów nie wybiera tej pracy – po prostu w nią trafiają. Zaczynasz nową posadę, a po sześciu tygodniach ktoś podaje ci ticket dotyczący „starego serwisu magazynowego”. Nagle nie piszesz już nowego kodu – zajmujesz się wykopywaniem informacji.
Dlaczego ta umiejętność jest ważniejsza, niż ludzie przyznają
Oto nieprzyjemny fakt: większość oprogramowania, które faktycznie napędza świat, nie jest nowa. Jest stara, składana z różnych elementów na przestrzeni lat, tylko częściowo zrozumiała i cicho stanowi źródło niepokoju dla tych, którzy formalnie są za nią odpowiedzialni.
Banki wciąż używają języka COBOL napisanego w latach 70. Linie lotnicze kierują loty za pomocą systemów, które istniały jeszcze przed pilotami obsługującymi te samoloty. Nawet startupy działające z pełną prędkością gromadzą kod dziedziczny w ciągu roku lub dwóch – kod stworzony pod presją terminów przez kogoś, kto później zmienił zespół, aby rozwiązać problem, który nigdzie nie został spisany.
Jeśli pisanie nowego kodu to jedyne, co potrafisz robić, jesteś ograniczony do nowych projektów. Ale jeśli potrafisz naprawdę czytać stary kod — tak jak detektyw bada miejsce zbrodni — stajesz się osobą, do której zwracają się zespoły inżynierów, gdy trzeba naprawić coś poważnego. To profesjonalna przewaga, o której rzadko otwarcie mówi się publicznie.
Myślenie archeologa
Zanim przejdziemy do konkretnych technik, istnieje zmiana perspektywy, która ułatwia wszystko później.
Załóż, że kod miał sens dla kogoś, w jakimś momencie.
To pojedyncze przemyślenie ma większe znaczenie niż jakakolwiek technika z tej listy. Patrząc na skomplikowany, mylący kod, łatwo jest doszukać się wniosku, że osoba, która go napisała, nie wiedziała, co robi. To prawie nigdy nie jest prawda. Znacznie częściej istniał zbliżający się termin, ograniczenie, którego już nie widać, wymóg systemowy, który zniknął, albo prośba, która w 2016 roku wydawała się zupełnie uzasadniona, ale od tamtej pory nigdy nie została ponownie rozpatrzona.
Wyobraź sobie wejście do starego domu i zauważenie belki nośnej w dziwnym, pozornie arbitralnym miejscu. Wydaje się, że nie służy żadnemu celowi — dopóki nie dowiesz się, że kiedyś stała tam ściana, która została zburzona, pozostawiając tę belkę jako jedyną rzecz, która powstrzymuje dach przed zawaleniem się. Stare bazy kodowe są pełne takich belek. Zanim cokolwiek zmienisz, twoim zadaniem jest dowiedzenie się, co każda z nich w ciszy podtrzymuje.
Przyjęcie takiego sposobu myślenia przynosi dwie korzyści: po pierwsze, pomaga zachować skromność wobec własnych założeń, a po drugie zastępuje frustrację ciekawością. Okazuje się, że ciekawość jest o wiele skuteczniejszym narzędziem do rozwiązywania problemów niż irytacja.
Techniki analizy kodu z przeszłości
1. Czytaj historię komitetów jak dziennik
Historia Git jest najbliższym, co możesz mieć z rzeczywistą maszyną czasu. Nie ograniczaj się do sprawdzania aktualnego stanu pliku — prześledź, jak doszedł do niego.
Rozpoczęcie wykonywania polecenia git log --follow w odniesieniu do konkretnego pliku często ujawnia prawdziwą historię: funkcję dodaną w pośpiechu tuż przed ważną prezentacją dla klienta, pospieszną poprawkę wprowadzoną późnym wieczorem w piątek, lub komentarz brzmiący „tymczasowe rozwiązanie, usunąć po premierze w trzecim kwartale”, który jest już cztery lata przestarzały.
Znalazłem przypadkowo dziwną instrukcję if, która obsługiwała dokładnie jeden identyfikator klienta, bez żadnego wyjaśnienia. Sprawdzenie zapisów w git blame ujawniło notatkę osoby, która ją napisała, w której wyjaśniono, że dane produkcyjne konkretnego klienta zawierały błąd pisowni, a ta instrukcja miała służyć jedynie tymczasowemu utrzymaniu spraw w ryzach, dopóki klient nie naprawi swojej strony. Ten tymczasowy rozwiązanie pozostało w mocy przez pięć lat. Zrozumienie tła sprawy zmieniło sposób, w jaki zespół ostatecznie je usunął – postąpili ostrożnie, przeprowadzając prawidłową migrację danych, zamiast po prostu usunąć instrukcję i mieć nadzieję, że nic się nie zepsuje.
2. Śledź dane, a nie tylko kod
Kod pokazuje ci to, co *mogło* się zdarzyć. Dane pokazują ci to, co *faktycznie* się stało.
Idź i zapytaj bazę danych bezpośrednio. Przyjrzyj się rzeczywistym wierszom. Jeśli tabela ma kolumnę status z wartościami takimi jak 1, 2, 7 i 99, nie próbuj wywnioskować ich znaczenia wyłącznie na podstawie lektury kodu — pobierz rzeczywiste rekordy dla każdej wartości i sprawdź, co się z nimi stało w praktyce.
Rozważ pole type w starej tabeli zamówień, gdzie kod źródłowy uwzględniał jedynie wartości od 1 do 5, a mimo to dane produkcyjne pokazywały tysiące wierszy z wartością type = 0. Okazało się, że 0 oznacza „stworzone przed tym, jak w ogóle pojawiło się pole typu” — fragment historii systemu, który zniknął z obecnego kodu, ale wciąż istniał, wyraźnie widoczny w danych.
3. Rozmawiaj z duchami (czyli z ludźmi, którzy wciąż są obecni)
Nawet gdy osoba, która stworzyła część systemu, dawno odeszła, ktoś w pobliżu zazwyczaj posiada fragment kontekstu — osoba, która pierwotnie przyjęła tę osobę do zespołu, inżynier wsparcia zajmujący się latami podobnymi zgłoszeniami, lub menedżer produktu, który wciąż pamięta „ten incydent”.
Zadawaj konkretne, mniej naciskujące pytania zamiast ogólnych. „Co robi ten kod?” zachęca do domysłów, ponieważ jest zbyt otwarty. „Czy pamiętasz coś niezwykłego dotyczącego sposobu obsługi zwrotów pieniędzy około 2021 roku?” jest na tyle konkretne, by przywołać rzeczywiste wspomnienia.
4. Stwórz mapę, zanim cokolwiek zmienisz
Archeolodzy nigdy nie zaczynają wykopalisk losowo — najpierw tworzą mapę stanowiska. Zastosuj tę samą dyscyplinę do bazy kodu.
Narysuj, choćby w przybliżeniu na papierze lub w dokumencie, ścieżkę, którą przechodzą dane podczas ruchu w systemie: które elementy uruchamiają inne, które części przechowują informacje w pamięci, a na jakich komponentach polegają pozostałe. Nie chodzi o idealny diagram — wystarczy tyle obrazu, by uniknąć dalszych nieoczekiwanych problemów.
Jeden przydatny trik: wybierz konkretną akcję z rzeczywistego świata, na przykład „użytkownik anuluje swoją subskrypcję”, i prześledź ją od początku do końca, zaznaczając każdy plik i funkcję, przez które przechodzi. Śledzenie tej jednej ścieżki często ujawnia większość informacji, których potrzebujesz, aby zrozumieć system jako całość.
5. Napisz testy przed refaktoryzacją
Jeśli baza kodu w ogóle nie zawiera testów, co jest częste w systemach starszych, oprzej się chęci naprawienia wszystkiego za jednym razem. Zamiast tego napisz tak zwane testy charakterystyczne — testy, które po prostu odzwierciedlają to, co kod obecnie robi, niezależnie od tego, czy to zachowanie jest poprawne.
Dzięki temu uzyskasz dwie rzeczy naraz: bezpieczną poduszkę dla wszelkich przyszłych zmian oraz wymuszoną jasność, ponieważ nie możesz dokładnie opisać obecnego zachowania, jeśli go faktycznie nie rozumiesz. Duża część wartości wynika z samego pisania testów, a nie tylko z ich uruchamiania.
6. Zmieniaj jedną rzecz naraz
Gdy w końcu zrozumiesz skomplikowany system, pojawia się pokusa przepisania całego kodu w jednej, spektakularnej prośbie o zmianę. Nie rób tego.
Systemy starsze zazwyczaj są bardziej kruche, niż się wydaje, właśnie dlatego, że żadna jedna osoba nie posiada pełnego obrazu wszystkich zależności. Dokonywanie małych, odwracalnych zmian — po jednej na raz, przy czym każda musi zostać zweryfikowana przed przejściem do następnej — to sposób na uniknięcie sytuacji, w której powstanie kolejny zagmatwany komit, który przyszły archeolog będzie musiał rozszyfrowywać.
7. Udokumentuj to, co odkryjesz — dla następnej osoby
Wszystko, co uda ci się odkryć i zrozumieć, warto zachować. Zapisz to gdzieś. Nawet krótki, nieformalny, niekompletny dokument z tytułem na przykład „Jak naprawdę działa system fakturowania starszy” jest prezentem dla osoby, która po tobie przejmie ten kod — a tą osobą możesz być ty sam, za sześć miesięcy, gdy już o wszystkim zapomnisz.
Krótka historia: Funkcja, której nikt nie rozumiał
W jednej firmie określona funkcja zyskała przydomek „bestia” – 900 linii kodu ukrytych głęboko w procesie płatności, których nikt nie chciał dotykać. Nowi pracownicy byli o tym ostrzegani podczas szkolenia, po części żartem, a po części jako prawdziwa historia ostrzegawcza, podobna do lokalnej folklorystyki.
Ostatecznie ktoś zdecydował się ją właściwie przeanalizować, zamiast jej unikać. Zamiast próbować przepisać kod, śledzili rzeczywiste transakcje w trakcie ich przepływu przez tę funkcję, zmapowali każdą gałąź logiki i napisali testy charakterystyczne, aby objąć każdą możliwą ścieżkę. Cały proces trwał około tygodnia.
To, co odkryli, to nie był chaos — okazało się to dość spójnym systemem obsługi pięciu różnych dostawców płatności, z których każdy miał swoje osobliwości i specyficzne przypadki. Został napisany przez kogoś, kto rozwiązywał rzeczywiste, konkretne problemy w realnych ograniczeniach, bez możliwości późniejszego uporządkowania sytuacji. Gdy został w pełni zmapowany, ta struktura przestała być przerażająca. Wciąż była złożona, ale teraz była to złożoność, którą ktoś naprawdę rozumiał.
W istocie to właśnie jest cała esencja tej dziedziny: przekształcanie strachu w mapę.
Ostateczne refleksje
Archeologia oprogramowania nie ma w sobie nic efektownego. Nikt nie umieszcza „umiejętności rozszyfrowywania zagmatwanego kodu innych” jako kluczowej umiejętności na swoim CV. A jednak pozostaje to jedną z najprzydatniejszych, a zarazem najbardziej zaniedbywanych umiejętności w inżynierii oprogramowania.
Stary kod nie powinien być odrzucany jako bezużyteczny — to zapis decyzji podjętych pod presją i w ograniczeniach, o których możesz nigdy w pełni nie wiedzieć. Podchodź do niego z ciekawością, a nie osądem, i w rezultacie wprowadzisz bezpieczniejsze zmiany, prawdopodobnie odkrywając przy tym, że proces ten jest znacznie bardziej satysfakcjonujący, niż się spodziewałeś.
Zatem następnym razem, gdy jakiś plik sprawi, że będziesz chciał zamknąć klapę laptopa i wyjść na zewnątrz, zatrzymaj się i oddechuj przez chwilę. To, co widzisz, to nie ruiny — to miejsce badań czekające na zrozumienie.
Weź pędzel, a nie buldożer.
Literatura pokrewna
- Dziewięć praktyk na poziomie kodu, które ułatwiają zaufanie do pracy doświadczonych inżynierów — Omawia dziewięć konkretnych praktyk programowania – od klauzul ochronnych po ścisłe modelowanie danych – które sprawiają, że kod jest bardziej odporny, czytelny i łatwiejszy do debugowania pod presją.
- Dziesięć częstych praktyk w JavaScript, które po cichu niszczą twoją bazę kodu — Wyjaśnia dziesięć powszechnych problemów w JavaScript i TypeScript, od luźnej równości po mutację stanu, oraz pokazuje bezpieczniejsze wzorce zastępujące każdy z nich.