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ą:

  1. nieoficjalna

    Rozmowy z ludźmi 'w realu' lub zdalnie.

  2. 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:

  1. 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:

  2. 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.

    1. 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 [2024-06-03 Mon] 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."

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:

  1. całościowy
    1. 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.

    2. 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.

  2. 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ę.

Date: 2024-06-07 Fri 00:00

Author: Slawomir Grochowski

Email: slawomir.grochowski@gmail.com

Created: 2024-06-25 Tue 14:02