Żeby stworzyć widok do ITCandidateEvaluatora potrzeba mi było około tygodnia codziennej pracy. Model i reszta klas w jego pakiecie zajęły połowę z tego. Teraz wystarczyło to już TYLKO połączyć.
Patrząc obiektywnie, mój drugi projekt nie jest jakąś szczególnie rozbudowaną aplikacją. Składa się z zaledwie 13 widoków (obiektów JPanel, które mogą zawierać inne panele i komponenty). Mimo to jest to 13 razy więcej niż miało CleanThemAll gdzie cała zabawa toczyła się na tym samym JPanelu. Tak, że to TYLKO połączenie modelu z widokiem wcale szybkie nie było.
O ile stworzenie frontu przypominało trochę zabawę z przestawianiem okienek i przycisków oraz takim ustawianiu pikseli aby to jakoś wyglądało, o tyle backend – choć technicznie zawierający się w mniejszej ilości klas – okazał się znacznie bardziej wymagający. Zobaczmy po kolei co się dzieje w każdej z jego klas:
Model – tutaj zaczyna się aplikacja i to tutaj umieściłem wszystkie potrzebne gettery z których korzysta View. Tu również rozpoczynają się dwa osobne wątki – jeden jest odpowiedzialny za ładowanie pytań z plików. Na mojej maszynie zajmuje to od 5 do 20 milisekund i równie dobrze mogłem to zrobić w wątku głównym bez zauważalnego przestoju. Ale ja mam szybki dysk NvME – na HDD to może trwać o wiele wiecej (już nie mówiąc nawet o sytuacji gdzie pytania byłyby pobierane z osobnego serwera).

Drugi wątek ustawiłem w tryb Daemona czyli taki podrzędny – działa nieprzerwanie i odświeża listę rekrutacji w widoku kiedy stworzymy nową albo usuniemy starą. Na szczęście żaden z tych dodatkowych wątków nie zmienia danych, dlatego nie musiałem bać się o poprawną synchronizację.
Kolejna klasa: „Recruitment” zajmuje się przechowywaniem listy kandydatów i wyliczaniem ich wyników w oparciu o ustawienia Presets. Te z kolei możemy utworzyć samodzielnie albo z poziomu aplikacji, albo dodając plik JSON w katalogu presets:


Mamy też klasę Candidate dziedziczącą po AbstractCandidate zawierającą wszystkie gettery, settery i inne potrzebne metody. W klasie kandydata trzymane są wszystkie niezbędne zmienne danego aplikanta – od imienia i nazwiska po wyniki z każdego zadanego mu pytania.
Oprócz tego jest też klasa CandidateDTO, która jest takim tymczasowym kandydatem gdzie zapisują się wyniki kolejnych kroków ewaluacji i dopiero na życzenie użytkownika przesyłane są one do właściwego kandydata. Candidate i CandidateDTO początkowo dziedziczyły po AbstractCandidate, ale w połowie prac uznałem, że nie będzie to potrzebne i odciąłem klasę DTO od abstrakcji.

Jeśli chodzi o pakiet ’controller’ gdzie umieściłem klasę CandidateDTO to na chwilę obecną jest on właściwie pusty, żeby nie powiedzieć – bezużyteczny. Planuję go jednak zapełnić klasami pomocniczymi implementującymi interfejs ActionListener na etapie refaktoryzacji kodu. Czasami logika niektórych przycisków jest dość rozbudowana i zaciemnia kod klasy widoku, która w założeniu ma odpowiadać tylko za pokazanie danego elementu. Przeniesienie tego do osobnych klas może ulepszyć hermetyzację kodu.

Dużo NullPointerException wpadło kiedy próbowałem połączyć widok pokazujący wybór pytań z fabryką ładującą je z plików (QuestionFactory). Powoli już mnie szlag trafiał kiedy raz za razem aplikacja gdzieś się wywalała, bo dana mapa była pusta (null) a ja jak zahipnotyzowany wpatrywałem się w kod analizując sekwencję zdarzeń. W końcu wywaliłem wszystko co napisałem i poszedłem pobawić się z dziećmi.
Następnego ranka temat ogarnąłem w 40 minut. Wniosek jest prosty:
Nie ma co czarować, jak się skończy mana.


ITCandidateEvaluator już właściwie działa jak miał działać od samego początku. Możemy rozpocząć rekrutację, dodać kandydatów, ocenić jednego po drugim, porównać i przesłać im feedback o ile zrobimy to…
…jednym ciurkiem
Progam nie posiada jeszcze żadnych opcji serializacji swoich obiektów rekrutacji i kandydatów przez co przy każdym włączeniu zaczynamy proces od samego początku. Te rekrutacje i ci kandydaci, których widać na załączonych screenach są zapisani bezpośrednio w kodzie i tylko na potrzeby testów:


To właśnie będzie moje następne zadanie – stworzenie strategii zapisu danych. Będą dwie strategie – ta prostsza zajmie się (de)serializacją do zwykłych plików. Ta bardziej zaawansowana skorzysta z bazy MySQL.
Ale to nie wszystko, bo wcześniej czeka mnie sroga refaktoryzacja kodu. Trochę nabałaganiłem podczas łączenia widoku z modelem i teraz wypadałoby posprzątać wszystkie nieużywane zmienne, czy spróbować odchudzić zbyt zapasione metody. Ponadto już teraz wiem o kilku mniej krytycznych błędach, które wypada załatać zanim się o nich zapomni.
A co potem?
Zakładając, że aplikacja będzie zapisywać dane jak zamierzono, kolejnym krokiem będzie podłączenie Loggera log4j, tak by wszystkie wyjątki były ładnie wyłapywane i dokumentowane.
Potem zostaną już tylko testy manualne i jednostkowe z wykorzystaniem JUnit oraz Mockito. Zobaczę też czy jest możliwość podłączenia Dockera do projektu. Ale o tym będzie już w kolejnym odcinku.
