Opiszę wam tu pewien mechanizm refaktoryzacji kodu. Nazywam to „refaktoryazcja na trzy” ponieważ w trakcie procesu wykonujemy trzy kroki z czego dwa to refaktoryzacja właściwa. Metoda jest dobra jeżeli mamy na głowie termin, dokumentację i mało czasu. Jej zaletą jest bardzo szybkie prototypowanie i możliwość oddania gotowego kodu. Wadą jest to, że podlega jej tylko nowy kod. Metoda sprawdza się najlepiej gdy mamy do zrobienia kilka podobnych elementów np. formularzy, ale nie mamy jeszcze klarownej struktury dziedziczenia i nie wiemy jak dokładnie będzie wyglądał kod.

Krok 0. Wymagania

Załóżmy, że mamy do wykonania kilka podobnych funkcjonalności. Stanowią one niezależne moduły, ale mają wspólne części. Nie wiadomo jednak jak te wspólne elementy zdefiniować na poziomie projektu. Może to wynikać z kilku powodów.

  • Niedookreślenie wymagań – najczęściej spotykane. Zazwyczaj wiemy jak coś ma mniej więcej wyglądać, ale nie mamy konkretów.
  • Niespójne wymagania – podobnie jak wyżej, tylko, że dla danego modelu dane wymaganie może być realizowane w różny sposób.
  • Brak dokumentacji/projektu – często spotykane gdy dane funkcjonalności są ekstra i na wczoraj.

Mając tak postawione wymagania i zdefiniowane problemy można sobie w łeb strzelić. Rzecz w tym, że nie mamy zazwyczaj możliwości wykonania projektu, a na nasze pytania klient lub manager reagują jak arab na żyda. Ubarwiając swoja wypowiedź całym słownikiem niepoprawnej polszczyzny.
Naszą jedyna szansą jest w takim przypadku jak najszybsze przedstawienie działającej aplikacji, a w kolejnych krokach już na spokojnie możemy całość ogarnąć do ludzkiej postaci. W przeciwny wypadku będziemy mogli pożegnać się z premią.
Przykładowa aplikacja będzie wyświetlać dwie zakładki. Pierwsza zawiera formularz dodawania i listę osób. Druga formularz dodawania i listę zasobów. Zasoby mogą być przypisane do konkretnej osoby, ale nie muszą. Zastosuje język kozioł (paradygmat filozoficzno-wyobrażeniowo-machany rencami), bo nie chce mi się w Javie klepać.

Krok 1. Model, DAO, testy, ogół

Na początek łatwa praca. Tworzymy klasy Użytkownik i Zasób. Dodajemy odpowiednie klasy DAO i piszemy testy dla nich. Zazwyczaj wszystko bangla na tym etapie. Ważne jest jednak by odpowiednio umieścić nasze klasy w pakietach. aplikacja.Model i aplikacja.Dao są git. Testy powinny być w miarę możliwości niewielkie. Zazwyczaj klasy dao wykorzystują ogólnie przyjęte standardy etyczno-moralne. Innymi słowy testowania ORMa nie ma sensu.
Następnie tworzymy dwie klasy EkranUżyszkodnika EkranUżytkownika i EkranZasobów. Każda z nich w swoim osobnym pakiecie widok.Użytkownik i widok.Zasób (ten podział jest bardzo, ale to bardzo ważny). Co w nich umieszczamy? Oczywiście cały kod odpowiedzialny za wszystkie elementy związane ze wszystkimi działaniami na tym ekranie. Potrzeba 10 klas anonimowych? Olać! Piszemy i 30 jeżeli trzeba. Generalnie po takiej operacji utrzymamy coś w postaci „kodu, którego nigdy nie chciałbyś serwisować i nic na świecie nie zmusi cię do przyznania się do jego autorstwa” (no chyba, że sms za 5PLN + VAT 😉 ). To jest pierwszy realise naszej aplikacji.

Ale dlaczego tak?

W brew pozorom ma to sens. Jeżeli bardzo nam się spieszy i nie mamy czasu na dokładne przemyślenie działań warto pisać co elektryka na palce naniesie. Kod będzie niezbyt dobry, ale co ważne będziemy mieli już jakiś punkt zaczepienia. Po drugie taki kod wbrew pozorom tworzy się bardzo szybko, a koszt wprowadzenia zmian jest niski. Wynika to z faktu, że pracujemy na konkretach – funkcjonalnościach czy elementach UI. Co ważne mamy raptem dwa ekrany. Czyli nie tak dużo. Musimy tylko…

… pamiętać o zasadach

Są one bardzo proste i ułatwiają życie.

  • Używamy jednej konwencji nazewniczej. Listenery to listenery, akcje to akcje, DAO to dao.
  • Najpierw implementujemy jedną klasę w całości, a następnie drugą za pomocą kopiuj-wklej.
  • Nie kombinujemy z optymalizacją czy refaktoryzacją.
  • Jedyne co mamy prawo wyciągnąć poza nasz kod to walidatory danych prostych np. dat.

Co otrzymamy? Kod który jest pełen różnych buraczków, ale też będziemy wiedzieli jak całość działa i co najważniejsze dowiemy się mniej więcej które części są wspólne.

Krok 2. Od ogółu do szczegółu…

Przystępujemy do uszczegóławiania naszego kodu. Popatrzmy na klasę EkranUżytkownika:

Listing 1. Klasa EkranUżytkownika

klasa EkranUżytkownika{
    listaUżytkowników
    tabelaZUżytkownikamiIIchZasobami
    panelZFormularzemDodawania
    polaFormularza
    cośCoOdpowiadaZaKomuikacjęWzględnieKilkaDAO
    dodajUżytkownika()
    wyczyśćFormularz()
    pobierzdaneIWstawDoTabeli()
}

Coś tu tego za dużo. Ekran jest jednak ekranem i służy do ekranowania jak do Iwan Wisarionowicz Ekranow wymyślił. Po ludzku mówiąc mamy tu klasyczny przykład wielu odpowiedzialności w jednej klasie.
W pierwszej kolejności należy usunąć do osobnych klas wszystkie akcje. Konstruktory mogą przyjmować wiele parametrów na przykład:

Listing 2. Klasa WyczyśćFormularz

klasa WyczyśćFormularz 
<p>Na razie nam to nie przeszkadza. Kolejnym krokiem jest wydzielenie do osobnych klas elementów UI. Powstanie zatem klasa <samp>PanelDodawaniaUżytkownka</samp> oraz klasa <samp>TabelaUżytkowników</samp>. Wszystkie te klasy lądują w tym samym pakiecie co klasa z której zostały wydzielone.<br></br>
Następnie zaczynamy wydzielać kolejne mniejsze elementy kodu. <samp>RekordTabeli</samp> niech reprezentuje pojedynczy kontener z danymi, które  wstawiane do tabeli. <samp>PoleFormularza</samp> czyli klasa zawierająca parę <samp>OpisPola</samp> i <samp>Pole</samp>.<br></br>
Operację powtarzamy z drugą klasą.</p>
<h5>Co uzyskaliśmy?</h5>
<p>Nasz kod w tym momencie nie przypomina już spaghetti, ale raczej coś w rodzaju kotleta mielonego potraktowanego petardą. Mamy wiele małych elementów w dwóch pakietach... czas na ich eksterminację.</p>
<h4>... i z powrotem</h4>
<p>Przejrzyjmy nasz kod. Jesteśmy na etapie, na którym kod jest mocno rozdrobniony. Pozwala nam to na wyszukanie wspólnych elementów w obu pakietach. I tak <samp>PoleFormularza</samp> oraz <samp>WyczyśćFormularz</samp>  wspólne. Co ciekawe należy zastanowić się czy formularze się czymś różnią? Nie to znaczy, że logikę ich działania można przenieść do wspólnej nadklasy, a lokalnie pozostawić pewne charakterystyczne elementy. Podobnie z wyświetlaniem tabel. W praktyce różnią się tylko ilością kolumn, a logika pozostaje taka sama.<br></br>
Idąc  ścieżką należy pamiętać, że np. w Javie mamy mechanizm genericsów. Mówiąc inaczej gdy wyszukujemy części wspólne kodu to jednocześnie staramy się go uogólnić. W ten sposób otrzymamy kod ogólny, spełniający SRP i luźno ze sobą powiązany (czasami zamiast nadklas wyłączamy interfejsy np. dao)</p>
<h4>Czy tak trzeba zawsze</h4>
<p>Nie! na początku wymieniłem przypadki w których taka droga jest jedynym rozsądnym kompromisem pomiędzy czasem tworzenia aplikacji i jej jakością. Na co dzień należy odpowiednio projektować elementy systemu, a refaktoryzacja powinna tylko weryfikować stan rzeczywisty tak by odpowiadał zmianom w projekcie oraz pozwalała wdrożyć nowe lepsze sposoby rozwiązania problemów.</p>

Artykuły  dostępne na licencji <a href="https://creativecommons.org/licenses/by/3.0/deed.pl" rel="noopener" target="_blank">CC-BY</a>.<br></br>
Jeżeli spodobał ci się ten wpis, to podziel się nim z innymi lub <a href="https://www.paypal.me/koziolekweb" rel="noopener" target="_blank">wesprzyj autora</a>.
</div>