legacy code: jak przejąć stary projekt i go naprawić
[Read this post in english here]
Każdy projekt zaczynam od rewizji potrzeb użytkowników i rozwiązań w systemie (ficzery). Aby określic jakie są aktualne potrzeby użytkowników i w jaki sposób są one zaspokajane poprzez aplikację. Pomoga to podzielić aplikację na części i określić które części są używane i w jakim stopniu. Szczególnie interesują mnie elementy systemu które nie są używane. Funkcjonalności które zostały zapomniane, porzucone i które można bezpiecznie usunąć. Aby nie inwestować czasu i energii na analizowanie, próby zrozumienia oraz modernizowanie i pisanie testów dla kodu, który jest zbędny.
Rewizję tę opieram na trzech źródłach informacji: dokumentacja, ludzie i dane. A praktycznie rzecz biorąc to dwóch bo dokumentacja jest po prostu zapisem informacji i wniosków wyciągniętych na podstawie dwóch pozostałych źródeł. Ale zazwyczaj jest ona szczątkowa i nieaktualna.
Staram się uzyskać odpowiedzi na pytania zebrane w tabeli poniżej:
pytania | ludzie | dane |
---|---|---|
aplikacja | ||
po co istnieje? | powiedzą dlaczego istnieje | z danych tego się nie dowiemy |
jakie ma problemy? | powiedzą dlaczego mają problemy z aplikacją | wskażą problemy techniczne aplikacji |
ficzery | ||
jak można je podzielić? | powiedzą jak i dlaczego | wskażą kto, z czego i jak często korzysta |
która są najważniejsze? | powiedzą dlaczego są najważniejsze | wskażą które są najczęściej używane |
które są używane? | powiedzą dlaczego je używają | wskażą które są używane |
które nie są używane? | powiedzą dlaczego ich nie używają | wskażą które nie są używane |
czyje dany ficzer zaspokaja potrzeby? | powiedzą dla kogo jest dany ficzer | wskażą kto korzysta z danego ficzera |
użytkownicy | ||
kim są i jak można ich pogrupować? | powiedzą kim są i jak można ich pogrupować i dlaczego podzielić | wskażą którzy użytkownicy z czego korzystają |
jakie mają potrzeby? | powiedzą jakie mają potrzeby | z danych tego się nie dowiemy |
jakie mają problemy? | powiedzą o problemach jakościowych | wskażą problemy ilościowe |
ficzery | powiedzą w jaki sposób z nich korzystają | wskażą którzy użytkownicy z czego korzystają |
ilu jest 'martwych' użytkowników? | powiedzą dlaczego są martwi | wskażą którzy są martwi i ilu ich jest |
Wykonując taką rewizję, tworzę dokumentację w formacie tekstowym org, który jest podobny do markdown. Format tekstowy to jedyny naprawdę uniwersalny format który gwarantuje, że każdy w przyszłości będzie mógł łatwo edytować tak zapisaną dokumentacje.
1. źródła informacji
1.1. informacje spisane - dokumentacja
Informacje które zostały spisane i są dostępne w jednym z takich źródeł jak:
- dokumenty Office np. Microsoft 365, Word i SharePoint
- firmowe serwisy wiki
- systemy zarządzania projektami np. Jira
- komunikatory np. Teams, Slack
Mnogość dostępnych opcji które często są używane jednocześnie powoduje dodatkowe rozproszenie informacji. Co pogłębia trudności w wyszukiwaniu a duży wysiłek potrzebny aby zapewnić aktualność powoduje, że informacje te są mocno zdezaktualizowane.
Zazwyczaj znajduje jednorazową ogólną specyfikacje projektową z początku projektu która dodatkowo została zapisana w formacie uniemożliwiającym jego dalszą modyfikacje (PDF).
Źródła te mają pewną wartość historyczną. Ciekawie na to spojrzeć. To pozwala zrozumieć genezę powstania projektu.
1.2. informacje niespisane - ludzie
Informacje pozyskane z rozmów z ludźmi, którzy dysponują tzw. wiedza tubylczą przekazywaną z ust do ust, są najważniejszym źródłem. Rezultatem działań zbierania informacji niespisanych jest ich odpowiednia selekcja i spisanie.
1.2.1. podział ludzi
Zależnie od aplikacji można podzielić i pogrupować użytkowników w określone grupy które mają wspólne cechy i właściwości. Można zacząć od podziału na użytkowników wewnętrznych, czyli firmowych, oraz zewnętrznych czyli klientów danej firmy. Następnie stosowny podział na poszczególne grupy i role. Sprawdzenie czy podział ma odzwierciedlenie w aplikacji.
1.2.2. sposoby zbierania
Pozyskanie informacji od danego przedstawiciela grupy, lub roli. Tutaj dzielę to na część oficjalną i nieoficjalną:
- nieoficjalna
Rozmowy z ludźmi 'w realu' lub zdalnie.
- oficjalna
Dłuższy, zaplanowany wywiad 'w realu' albo zdalnie, przebieg jest rejestrowany. Po prostu pomagam przygotować dłuższą wypowiedź pisemną jednemu z użytkowników aplikacji np. użytkownik korzystający z firmowego CRM'a. Proszę go aby pokazał w jaki sposób korzysta z aplikacji i z poszczególnych funkcjonalności, jakie ma problemy i pomysły na ich rozwiązanie.
Zapis audio-video rozmowy jest nieoceniony aby później w spokoju opracować całą rozmowę. Po opracowaniu przedstawiam mu spisane pomysły, opinię i wnioski z prośbą o przejrzenie, poprawki, dopisanie rzeczy które mu się przypomniały, oraz wydanie zgody na publikację. Po wyrażeniu zgody niszczę materiał video, a wywiad dodaje do dokumentacji jako oficjalną wypowiedź użytkownika aplikacji. To stanowi podstawę do dalszych działań przy projektowaniu aplikacji.
1.3. informacje za'kod'owane - dane
Dane powstające podczas użytkowania kodu (np. dane aktywności użytkowników) powstające w skutek normalnego działania aplikacji (np. użytkownik składa zamówienie) bądź na skutek zaplanowanego wymuszenia powstania danych (np. logowanie) w celu sprawdzenia czy dany kod jest wywoływany.
To jedyne obiektywne źródło prawdy które da nam odpowiedzi na pytania: co jest używane, jak często i przez kogo.
1.3.1. kod którego wykonanie powoduje zapis danych
Dane najczęściej są zapisywane w bazie danych. Więc po prostu tworzę zapytanie do bazy danych uwzględniając dwa pytania poniżej:
- ile jest wierszy w danej tabeli?
Tutaj zależnie od wieku aplikacji. Jeśli świeża to wcale nie musi mieć dużo wierszy w każdej z tabel. Więc drugim ważniejszym pytaniem jest:
- kiedy powstały ostatnie wiersze w danej tabeli?
Zazwyczaj mamy jakieś informacje określające date powstania danego wiersza np. kolumnę created_at (timestamp) albo zawsze możemy taką utworzyć. Tu również sami musimy wywnioskować czy jeśli ostatnie wiersze powstały np. rok temu to czy istnienie tej funkcjonalności jest zasadne.
- przykładowe zapytanie MySQL - dokumentacja
Korzystając z pliku tekstowego org mogę w edytorze tekstowym napisać zapytanie SQL i je wykonać na bazie produkcyjnej a wynik bezpośrednio zamieścić w dokumentacji. A później wyeksportować to do dowolnego formatu np. HTML, w taki właśnie sposób powstał ten post w formacie HTML.
Robiąc analizę bazy danych dokumentuję swój tok myślenia: zapisuję zapytania które wykonuje, dane które otrzymałem i wnioski. Raz aby mieć jasność i klarowność podejmowanych działań, dwa to dokumentacja, świetna dokumentacja dla innych.
Dla prostego przykładu zamieszczam zapytania do bazy danych - poniżej dokładne zapytanie które wykonałem oraz jego wyniki.
SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = 'fighterchamp' AND table_type = 'BASE TABLE' ORDER BY table_name;
table_name table_rows user 1000 tournament 10 info 0 W podsumowaniu napisałbym coś takiego: "Na podstawie zapytania z dnia
wykonanego na bazie produkcyjnej, stwierdzam, że tabela 'info' nie zawiera żadnych danych, co stanowi podstawę do rewizji kodu z tego obszaru i ocenę przydatności dla użytkownika - martwy kod, pewnie nikt nie korzysta."
- przykładowe zapytanie MySQL - dokumentacja
1.3.2. kod którego wykonanie nie powoduje zapisu danych
Więc tutaj musimy doprowadzić aby jakieś dane powstały przy wywołaniu danego kodu. Powstawanie danych a raczej ich zapis przy dużej ilości zapytań może spowolnić działanie aplikacji i być odczuwalny przez użytkowników. Dlatego tego typu 'obserwacje' prowadzimy przez określony czas, starając się zebrać reprezentatywną próbkę danych. Tutaj możemy wyodrębnić dwa zakresy zbierania danych:
- całościowy
- na poziomie zapytań HTTP
Czyli logujemy prawie wszystkie requesty HTTP. Zależnie od potrzebnych informacji - warto mieć ścieżkę, nazwę kontrolera i akcji, użytkownika i jego role. Zazwyczaj korzystam z tego rozwiązania. W Symfony można zrobić szybko i prosto RequestListener.
- na poziomie PHP
Tutaj https://github.com/krakjoe/tombs ale przyznaje, że jeszcze nigdy z tego nie korzystałem produkcyjnie. Wymaga zainstalowania dodatkowego extension do PHP.
- na poziomie zapytań HTTP
- punktowy
Kiedy ze względu wydajnościowych nie chcemy odpalać logowania wszystkiego albo mamy mało podejrzanych miejsc to możemy powstawiać logger w konkretne miejsca. Sprawdzić czy użytkownicy wchodzą na daną stronę czy wywołują daną akcję np. kupno produktu.
To podejście doczekało się własnego terminu tzw. tombstone. Logger ma zadanie logować miejsce w którym został wywołany i czas wywołania. Logujemy do specjalnego pliku. Dla ułatwienia wstawiamy sobie date powstania nagrobka np. Log::('2024-05-26'). A w pliku z logami powstanie zapis. np. '2024-05-26 UserController::showAction()'. Tutaj fajna i krótka (5min) prezka na ten temat David Schnepper's Ignite talk, "Isn't That Code Dead?"
2. dalsze kroki
Po zebraniu odpowiednich informacji staram się usunąć jak najwięcej martwego kodu. W rozmowie z biznesem używam bardziej precyzyjnego języka. Sądzę, że lepiej używać wyrazu 'archiwizacja' bo z systemem kontroli wersji to właśnie robimy. Nic nie usuwamy na zawsze, tylko usuwamy z 'aktualnej wersji' oprogramowania. Po to abyśmy nie tracili czasu i zasobów na 'utrzymanie' czyli przeglądanie, czytanie i modernizowanie kodu którego nikt nie używa.
Po takiej czystce zabieram się za postawienie pierwszych testów. Szybkim sposobem na zapewnienie sobie minimalnej siatki bezpieczeństwa jest podpięcie smoke testów. Jest do tego paczka której używam od lat i polecam https://github.com/shopsys/http-smoke-testing.
Uzyskanie danych o aktywności użytkowników pozwala też określić które elementy systemu są najważniejsze a więc określić w jakiej kolejności należy prowadzić dalszą pracę.