
W życiu programistów zdarza się czasami tak, że naszym zadaniem jest implementacja ciekawej funkcjonalności, nad którą prace zaczęły się już jakiś czas wcześniej. Na tym etapie nic nie zapowiada katastrofy, ja jednak na swoim przykładzie mogę śmiało powiedzieć, że takie zadania potrafią znacząco przyspieszyć tempo pojawiania się siwych włosów na głowie. Dzisiaj dzielę się kilkoma refleksjami opartymi na przykładzie zadania, na którym właśnie pracuję.
Stan wyjściowy
Stan zadania na dzień, w którym otrzymałam zadanie do zrobienia, wyglądał następująco:
- Funkcjonalność została pomyślana jako alternatywna dla istniejącej: miałam wyszukiwarkę, a moim zadaniem było stworzenie dwóch kolejnych – do wyboru dla użytkownika.
- Istniejąca funkcjonalność była integralną częścią modułu, w którym się zawiera – nie była osobnym komponentem.
- Od czasu pracy nad tą pierwotną funkcjonalnością minęły długie miesiące, wyewoluował code style, pojawiło się mnóstwo funkcjonalności dookoła, nad którymi (niestety) powoli przestaliśmy panować.
- Potrzeba refactoru tego modułu była konieczna i sygnalizowana przez programistów od dłuższego czasu, jednak wiadomo, że nie jest priorytetem dla zamawiającego. W końcu jednak doczekaliśmy się zielonego światła 😀
- Prace nad warstwą front-endową (moja działka) zostały rozpoczęte, jednak od czasu ich porzucenia minęło kilka miesięcy, a w międzyczasie np. dwa razy „podbijaliśmy” w projekcie wersje frameworka i wprowadziliśmy szereg innych zmian.
- Back-end również powstał kilka miesięcy wcześniej i nie uwzględniał (o czym przekonałam się później) pewnych nowych funkcjonalności.
- Programista, który zajmował się początkowo front-endem, odszedł z projektu jakiś czas wcześniej.
- Programista back-end, który również był zaangażowany w prace, odszedł z firmy w trakcie moich prac.
Jak wyglądała moja praca nad zadaniem?
Nie skłamię jeśli powiem, że przez pierwszy dzień patrzyłam w kod z coraz większą zgrozą. Pod koniec dniówki miałam ochotę po prostu rzucić wszystko w kąt. Poziom skomplikowania logiki, która rządzi zależnościami po prostu mnie poraził. Projekt jest oczywiście żywym organizmem i w tej chwili wprowadzane są rozwiązania, które znacząco usprawniają zarządzanie stanem aplikacji, jednak nie dotyczy to opisywanego przeze mnie modułu.
Drugiego dnia podjęłam próbę wydzielenia istniejącej funkcjonalności do osobnych komponentów tak, aby na tej podstawie tworzyć funkcjonalności alternatywne. Dotarłam do momentu, w którym wydzielona wyszukiwarka podstawowa działała i dodatkowo była osobnym bytem w sensie klas w kodzie.
Rozpoczęłam prace więc nad docelową funkcjonalnością. Przekonałam się, że wiele informacji przesyłanych pomiędzy warstwą front-endową i back-endową jest zbędnych i również wymaga spójnego refactoru. Niestety wiele czasu (często z własnej winy) straciłam na próbach odtworzenia spójnego z istniejącą implementacją obiektu przesyłanego do serwera. Jak się potem okazało, niepotrzebnie.
…i wtedy wchodzi refactor cały na biało
Z uwagi na to, że zmienił się skład zespołu programistów front-end, dokładnie w tym czasie nadeszła potrzeba uspójnienia pewnych rozwiązań stosowanych w kodzie, konwencji tworzenia plików, podziałów na moduły, etc. Byłam w trakcie implementacji i zdecydowałam się na dostosowanie tworzonego przeze mnie rozwiązania do nowej konwencji. Niestety to również wpłynęło początkowo na efekty pracy: wymagało wydzielenia ze sporego modułu (który z czasem rósł coraz bardziej) mojej części, nad którą pracowałam i podzielenia tego obszaru na atomowe, samodzielne byty.
Do tego doszło porządkowanie umiejscowienia metod pomocniczych i wiele innych kwestii, które w pierwszej chwili nie przyszły mi do głowy i nie sądziłam, że zajmą dużo czasu. Okazuje się, że nie bez przyczyny podczas szacowań miewały w przeszłości miejsce sytuacje, w których ja jako mniej doświadczona programistka podawałam niższe szacowanie złożoności zadania, niż moi bardziej doświadczeni koledzy…
Wisienką na torcie implementacji była zmiana wersji docelowej dla mojej funkcjonalności: aplikacja jest wdrażana „na produkcję” co jakiś czas i integruje się z innymi aplikacjami, co oznacza, że przy swojej pracy muszę uwzględniać zmiany, o których czasami nawet nie mam pojęcia i dowiaduję się kiedy w konsoli widzę błąd 500 😉
Co mogłam zrobić lepiej?
Patrząc z perspektywy czasu uważam, że za mało czasu poświęciłam na przemyślenie implementacji. Powinnam była na początku usiąść z ołówkiem w ręku i dokładnie rozpisać w pseudokodzie co się dzieje w interesujących mnie miejscach aplikacji (co zrobiłam, jednak dużo później) oraz przeanalizować co wysyłam na back-end i co z niego otrzymuję.
Również wcześniej powinnam przeprowadzić testy na środowisku testowym: okazało się bowiem, że pomimo odpowiedniego działa na środowisku lokalnym (które teoretycznie jest dokładnym odwzorowaniem tego zdalnego) otrzymuję zupełnie inne rezultaty niż po wyjściu z implementacją „na testy”.
Byłoby też lepiej, gdybym grubą kreską oddzieliła od siebie refactor od implementacji: wtedy code review byłoby łatwiejsze, ja oraz moi koledzy bylibyśmy w stanie wyłapać więcej błędów już na etapie czytania kodu i nie byłoby dużych problemów ze scalaniem implementacji do nowszej wersji aplikacji.
Dodam jeszcze, że sytuacja jest rozwojowa, ponieważ task nie został jeszcze ukończony, jest na etapie testów i trochę mu brakuję do DoD 😉 Być może przyszykuję jeszcze jakiś status update.
2 komentarze
Pamiętam ten temat 😉 Dobrze, że ten moduł został zrefactorowany, niedobrze, że w kodzie pozostał (chyba jeszcze większy) potworek legacy code, którego każdy boi się dotykać 😉 Podejrzewam, że jeszcze przez długi czas będzie nam spędzał sen z powiek
Oj tak, sieje postrach u wszystkich developerów 🙂