Zwrot z inwestycji
Przeanalizuj poniższy kawałek kodu:
List<Transit> ts;
if (t == null) {
ts = transitRepository
.findAllByClientAndFromAndStatusOrderByDateTimeDesc(
client,
from,
Transit.Status.COMPLETED);
} else {
ts = transitRepository
.findAllByClientAndFromAndPublishedAfterAndStatusOrderByDateTimeDesc(
client,
from,
t.getPublished(),
Transit.Status.COMPLETED);;
}
// Workaround for performance reasons.
if (ts.size() > 1000 && client.getId() == 666) {
// No one will see a difference for this customer ;)
ts = ts.stream().limit(1000).collect(Collectors.toList());
}
// if (ts.isEmpty()) {
// return List.of(t.getTo());
// }
Przymiotniki, jakie pewnie przychodzą Ci do głowy to: brzydki, nieczytelny, nieuporządkowany. Można odnieść się do zasad czystego kodu i powiedzieć, że nazewnictwo jest niejasne, a metoda zbyt długa. Dlatego też możesz zechcieć pozbyć się tego kodu z systemu, więc zaczynasz refaktoryzację. Przy okazji pociąga ona za sobą zmiany w czterech sąsiadujących funkcjach. Wszystko, łącznie z pisaniem testów, zajmuje tydzień.
Załóżmy, że:
- tydzień pracy = 40 godzin
- średni czas zrozumienia metody przed refactoringiem = 30 minut
- średni czas zrozumienia metody po refactoringu = 10 minut
Na tej podstawie możemy obliczyć, że zwrot z inwestycji (ROI - Return on investment) w refactoring wynosi 20 minut.
Coś tu jednak pomijamy, a żeby to zobaczyć, wystarczy spojrzeć na historię zmian w repozytorium:

Ostatnia zmiana została wprowadzona 5 lat temu. Mówi nam to, że ten kawałek kodu najpewniej nie sprawia problemów. Mimo braku estetyki spełnia swoje funkcje. Przez te lata, mimo braku testów, działała poprawnie — bo "wytestowała" go produkcja i kolejne zgłoszenia błędów. Innymi słowy, jest to kod stabilny, niezmienny. Oznacz to z kolei, że ten kawałek kodu czytany jest raczej sporadycznie. Zazwyczaj czytamy kod wtedy, gdy chcemy go zmienić lub zmienić coś wokół niego.
Koszt poniesiony na zyskanie tych 20 minut zwróci się dopiero po wielu latach. Załóżmy, że ten fragment musi być zrozumiany przez kogoś trzy razy w tygodniu. To dosyć optymistyczne założenie jak na kod, który nie zmieniał się od pięciu lat. W tydzień zyskujemy godzinę, poświęciliśmy ich 40, więc inwestycja w refaktoring zacznie się spłacać po 40 tygodniach.
O refaktoringu należy właśnie myśleć jak o inwestycji i tak też do niego podchodzić, czyli zadając sobie pytania:
- czy będzie się opłacać?
- kiedy będzie się opłacać?
Być może przewidywany cykl życia produktu jest krótszy niż wspomiane 40 tygodni. Wówczas inwestycja nie zwróci się nigdy. Refaktoring powinien rozwiązywać konkretny problem, a nie być sztuką dla sztuki czy zaspokojeniem indywidualnych pobudek estetycznych.
Przy refactoringu bardzo ważną umiejętnością jest zrozumienie, że:
- system składa się z komponentów
- komponenty cierpią na różne problemy — nieczytelny kod będzie bolał bardziej w miejscach zmieniających się często, a mniej lub wcale tam, gdzie tych zmian nie ma. Analogicznie, zbyt szerokie granice transakcji bazodanowej będą powodować problemy wydajnościowe w miejscach, gdzie wolumetryka zapytań jest duża, w innym przypadku prawdopodobnie nawet nie zauważymy tego problemu.
- dobieramy rozwiązanie do klasy problemu — pomaga nam to podjąć dobre decyzje refaktoryzacyjne.
Analiza repozytorium
Jednym ze sposobów szukania potencjalnie problematycznych miejsc w kodzie jest analiza historii repozytorium. Firma Google przeprowadzała badania na wielu systemach. Oszacowano, że często zmienia się tylko około 20% kodu, a pozostałe 80% jest stabilne. To raczej w tych 20% powinniśmy naprawiać problemy związane z czytelnością. Jest wiele narzędzi, które potrafią bardzo dokładnie przeanalizować, jak często zmieniają się poszczególne miejsca w kodzie. Korzystają one często ze skryptów i funkcji, które są częścią narzędzia do kontroli wersji, na przykład GITa.
Poniższa komenda listuje dziesięć najczęściej zmieniających się plików:
Można je również przefiltrować po rozszerzeniu, co pozwoli zawęzić wynik do plików w danym języku programowania.
Macierz stabilności
Informacja o stabilności to jedno, ale drugim ważnym czynnikiem jest trudność wprowadzenia zmiany. Nie zawsze wiemy to a priori, ale metryki typu: linie kodu do refaktoryzacji, liczba klientów korzystających z funkcji/klasy, czy też liczba przypadków testowych mogą nam w tym szacunku pomóc.
Prosta analiza pod kątem stabilności i trudności zmiany dzieli nam system na cztery ćwiartki:

Pod względem zwrotu z inwestycji raczej nie opłacała się naprawiać czegoś, co rzadko się zmienia i jest trudne do zmiany. Co innego, jeśli często się zmienia i mamy sprytny pomysł na łatwą poprawkę.
Oczywiście reguła ta nie zawsze jest wiążąca. Sprawdzi się, jeśli naszą pobudką jest poprawienie czytelności. Często jednak jest tak, że przygotowujemy się do nowej dużej funkcji biznesowej, która nie pasuje do obecnego modelu oprogramowania. Żeby ten model przygotować, potrzebujemy zmienić potencjalnie nawet stabilne miejsca w kodzie. W tym przypadku zmieniła się pobudka: nie optymalizujemy czytelności, tylko rozszerzalność naszego oprogramowania. Problemem nie są zawiłe metody, tylko najprawdopodobniej niedopasowany do problemu model. Ponadto, klasy często zmieniane niekoniecznie są nieczytelne. Mogą to być klasy konfiguracyjne, które siłą rzeczy są często zmieniane, albo specyficzne wzorce projektowe typu fasada.
Nie ma też nic złego w refaktoryzacji kodu, który jest stabilny. Naszą pobudką może być wtedy po prostu ćwiczenie techniczne. Wtedy najczęściej mamy na to zasoby i zdajemy sobie sprawę z tego, że zwrot z inwestycji jest przełożony w czasie. Tak naprawdę zwróci się ona przy kolejnych refaktoryzacjach, gdzie zaprocentuje nasze doświadczenie. Innym przykładem na zmiany, gdy kod jest stabilny, może być poprawianie jego wydajności, bo jest często używany.
Podsumowanie
Warto zapamiętać:
system składa się z komponentów
komponenty cierpią na różne problemy, a tym samym różny jest sposób ich rozwiązania
dobieramy rozwiązanie do klasy problemu — bez kontekstu, nieczytelność kodu, czy brak rozszerzalności modelu są po prostu cechami, ale nie można powiedzieć czy złymi, czy po prostu neutralnymi.
Materiały: