Swing to z języka angielskiego "huśtawka" (między innymi). Źródło obrazka: thehindu.com
Nauczony doświadczeniem zdobytym w czasie tworzenia mojego pierwszego projektu, od razu wiedziałem, że prace nad ITCandidateEvaluator zacznę od frontendu napisanego w Swingu.
Ale dlaczego znowu Swing?
Swing oferuje bardzo podstawowy interfejs graficzny pamiętający jeszcze czasy Windowsa XP i nie jest już używany przy tworzeniu komercyjnego oprogramowania. Ale jednocześnie jest to część standardowych bibliotek Javy, więc nawet tworząc front wciąż zdobywam cenne doświadczenie backendowca. Alternatywą byłoby proste korzystanie z konsoli do wyświetlania i przyjmowania danych, ale takiemu podejściu daleko do komfortowego. Mogłem też oczywiście spróbować nauczyć się frontendu z prawdziwego zdarzenia – liznąć trochę HTMLa i JavaScript, ale to tylko jeszcze bardziej odsunęłoby w czasie moment wysłania pierwszego CV. Dlatego zostaję przy Swingu.
W tym tekście przejdziemy sobie przez widoki jakie oferuje ITCandidateEvaluator, omówimy co-jak-dlaczego oraz zastanowimy się co będzie nam potrzebne od strony backendu. Zaczynamy od ekranu startowego reprezentowanego przez klasę InitialView:

Widzimy tutaj 3 przyciski, których znaczenia chyba nie trzeba tłumaczyć. Startujemy od stworzenia albo wczytania procesu rekrutacyjnego. To będzie się opierać o bazę SQL i zostanie opisane w przyszłych artykułach. Tymczasem już wiemy, że będzie nam potrzebna klasa Recruitment. Ale co ma zawierać?

Aby stworzyć proces rekrutacyjny będziemy koniecznie potrzebować jego nazwy i ustawień (Presets – kolejna wymagana klasa, musi pozwalać na ładowanie zapisanych ustawień z pliku .txt). Te elementy muszą bezwzględnie zostać wstrzyknięte do konstruktora. Przyda się też data utworzenia danego procesu oraz lista kandydatów biorących w nim udział. A na koniec zmienna typu boolean określająca czy rekrutacja jeszcze trwa czy już została zakończona.

Jeśli natomiast chodzi o obiekt kandydata to na screenie powyżej widać tylko podstawowe dane ustawione pod testy widoku. W rzeczywistości kandydat będzie musowo charakteryzowany przez obiekt rekrutacji w której bierze udział, oraz swoje imię i nazwisko (wstrzyknięte w konstruktor). Musi także posiadać hashmapę zawierającą uzyskane wyniki na każdym ze szczebli procesu rekrutacyjnego.
Obiekt kandydata może zostać stworzony manualnie (widok poniżej) albo automatycznie (przycisk: „Add many”) w którym podajemy ścieżkę do katalogu zawierającego CV wszystkich kandydatów. Osobna klasa CandidateFactory zajmie się przetworzeniem nazw plików pdf, tak aby z nazwy np. CV_JanKowalski.pdf albo JanKowalski.pdf wyeksportować tylko imię i nazwisko.

Ponadto aplikantowi automatycznie zostanie nadane id (na rzadki wypadek pojawienia się dwóch kandydatów o tym samym imieniu i nazwisku), czas poświęcony na jego ewaluację i datę dołączenia do procesu oraz opcjonalnie:
- rok urodzenia (aby w widoku listy przedstawić jego wiek)
- ścieżkę do pliku CV (aby w widoku listy móc na szybko podejrzeć CV)
- notatki
Każdy kandydat będzie również posiadał zmienną (typu String) feedback zawierającą wyniki poszczególnych kroków zapisane w czytelnej formie i gotowej do przesłania mailem.

W tym miejscu muszę się pochwalić zastosowaną sztuczką z dziedziczeniem. Otóż wszystkie 8 kroków rekrutacji są do siebie na tyle podobne, że musiałbym popełnić duplikację kodu na masową skalę. Zamiast tego utworzyłem klasę AbstractStage zawierającą powtarzalną logikę i dzięki temu klasa Resume (poniżej) ma tylko kilka linijek kodu.

Każdy z etapów ewaluacji ma swój tytuł, opis i suwak służący do ustawienia oceny. Możemy się wycofać do wcześniejszego etapu (przycisk: „Back„), możemy iść dalej („Continue„), możemy zapisać i zakończyć ocenę na chwilę obecną („Save & Exit„) lub całkowicie porzucić wszelkie zapisane zmiany („Discard„).
Te dwa ostatnie przyciski sugerują istnienie tymczasowej klasy TemporaryCandidate. W czasie rekrutacji wszelkie zmiany będą zapisywane tylko do wersji tymczasowej i dopiero po kliknięciu Save & Exit (lub „Finish” na końcu procesu) właściwy obiekt zostanie zaktualizowany.

Jeśli chodzi o sam sposób oceny to postawiłem na prosty suwak, który jest łatwy w obsłudze i intuicyjny. Jednocześnie usunąłem jakiekolwiek liczby, aby oszczędzić rekruterowi zastanawiania się czy na tym etapie aplikant zasłużył na 72 czy 73 punkty. Owe punkty jak najbardziej istnieją (w zakresie 0-100) tylko są niewidoczne dla użytkownika, zamiast nich mamy 7 dynamicznie zmieniających się opisów:
{"Unacceptable", "Poor", "Not too good", "Average", "Quite good", "Impressive", "Amazing!"};
Powyższy przykład z etapem pytań technicznych różni się nieco od innych kroków jako, że pytań może być sporo i każde z nich będzie miało własną ocenę. Pytanie to klasa Question z własną fabryką, najlepiej będącą osobnym wątkiem, tak aby ładowanie długich list z wielu plików nie blokowało programu. Jak widzimy powyżej – można wybrać rodzaj pytania (czyli plik z którego są sczytywane) oraz samo pytanie (lub postawić na losowe: Pick random question). Po wciśnięciu „Save score” suwak wraca na domyślną pozycję, a poprzednie pytanie wraz z wynikiem lądują do hashmapy. Wynik końcowy z tego etapu to średnia arytmetyczna z wszystkich zadanych pytań.

Podobnie klasa SalaryStagePanel została uzbrojona w dwa pola: From oraz To pozwalające rekruterowi określić widełki płacowe. W oparciu o nie wyliczana jest średnia płaca – jeśli kandydat zażyczy sobie kwotę niższą niż średnia, jego stosunek jakość/koszt rośnie. Lub maleje, wraz ze zwiększającymi się wymaganiami finansowymi.
Ten stosunek nie wpłynie na końcowy wynik osoby rekrutowanej, ale będzie można na jego postawie sortować kandydatów.
Z kolei ostatni krok czyli ocena umiejętności miękkich będzie globalnym modyfikatorem oceny za etapy 1-6 (czyli bez SalaryStage) na podstawie którego wynik kandydata będzie mógł być zwiększony/zmniejszony o pewną wartość procentową. To o jaką dokładnie, zostanie wyliczone z uwzględnieniem ustawień presets zadeklarowanych na początku procesu rekrutacyjnego.
Podsumowanie
Jak dotąd sam frontend liczy już 20 klas, do tego klasa główna, Model i Controller. Jeśli chodzi o część modelową to zakładam kolejne kilkanaście klas. Mam natomiast problemy z warstwą kontrolera, który ze względu na Swinga jest w większości obsługiwany już w widoku. Niby mogę umieścić obiekt JButton w View i dać mu ActionListener zadeklarowany w kontrolerze, ale tam są zazwyczaj pojedyncze metody obsługiwane jedną lambdą więc to byłoby trochę bez sensu.
openRecruitment.addActionListener(e -> {view.setCurrentPanel(new RecruitmentsListView(view));});
Myślę jednak, że dobrym pomysłem będzie umieszczenie tam obiektu typu DTO (Data Transfer Object – obiekt służący tylko do transferu danych) TemporaryCandidate, który będzie przechowywany tymczasowo w trakcie trwania ewaluacji. Po jej zakończeniu wszelkie wartości weń zapisane mają trafiać prosto do właściwego obiektu Candidate.
Jeszcze na koniec dodam, że przy tym projekcie chciałem zasymulować pracę komercyjną w metodologii Agile. W związku z tym wszystkie zadania są opisywane na platformie Trello gdzie przerzucam je z koszyków „TODO” w „DOING” i wreszcie w „DONE” wraz z postępami. Stworzenie frontendu to był mój pierwszy Sprint, który właśnie się zakończył. Przed nami jeszcze trzy kolejne:
- backend: logika działania
- backend: bazy danych
- testy jednostkowe i manualne
Na dzisiaj tyle i widzimy się za około tydzień. Postępy prac możecie śledzić na moim GitHubie oraz wspomnianym Trello (po angielsku).