Sprzątaj swój kodzik nieustannie czyli o ciągłej refaktoryzacji.

refaktoryzacja to proces ciągły

Siadam do kodu i piszę… i piszę… i piszę… a potem save, commit, push. Done? No, nie bardzo. Jeśli pracujesz w TDD, to dobrze wiesz co to jest: red, green, refactor. REFACTOR!, REFAKTORYZACJA! Czyli moment kiedy po prawie skończonej pracy porządkujemy kod. To  sprowadza się do posprzątania śmieci, usunięcia zbędnych zmiennych, metod i klas oraz wprowadzenia abstrakcji tam gdzie trzeba – ogólnie doprowadzenie kodu do stanu używalności – do stanu jaki chciałbyś sam zastać jak będziesz z nim pracować. Jeśli nie pracujesz w TDD to pewnie i tak sprzątasz twórczy bałagan – tylko nie nazywasz to refaktoryzacją. No chyba, że tego nie robisz; to może pora zacząć.

Dlaczego kod nie refaktoryzowany ssie?

Ile razy ktoś marudził na kiepski kod? Jak to jest, że taki kod powstaje? Sam się przecież nie pisze. To my go takim robimy. Czasem nie ma czasu, żeby sprzątnąć bo deadline albo jakakolwiek inna wymówka, ot po prostu nie refaktoryzujemy. Czasami po prostu nie mamy chęci i energii; to jednak nikogo nie zwalnia z obowiązku refaktoryzowania od czasu do czasu. Przecież koniec końców to  my w przyszłości będziemy z tym kodem najwięcej obcować. Zawsze można coś zwalić na innych członków zespołu. Im większy zespół tym łatwiej ale czy na pewno o to chodzi? Jesteśmy dorośli… trzeba po sobie posprzątać. Jaki jest sens trzymania 10 pustych linii pomiędzy metodami? To nie jest kartka papieru gdzie kiedyś coś dopiszemy. Nic tak nie rozprasza jak wtedy, gdy przewijając kod widzisz raz większe a raz mniejsze dziury w kodzie. Do tego jakieś kawałki kodu wykomentowane. Serio? Przecież nawet nie wiadomo czy to jeszcze działa. Co to robiło. Wywal te komentarze i wywal puste miejsca i niech to jakoś wygląda. Na początek tyko tyle i aż tyle. Dzisiaj po otwarciu 690 linijkowego pliku zacząłem wywalać takie puste miejsca. Jak skończyłem zostało ich 615. Nie usunąłem wszystkich pustych linii bo to by było bez sensu.

Najprostsza refaktoryzacja – puste linie

Puste linie są świetnym narzędziem do poprawienia czytelności kodu gdy oddzielają metody, gdy oddzielają usingi od namespace-a czy logiczne kawałki kodu od siebie. Wtedy ich użycie jest bardzo zasadne. Kod ma się dobrze i łatwo czytać a takie wolne miejsce pozwala coś zaznaczyć . Edytory kodu nie mają (na szczęście) pogrubiania czy ustawiania różnych fontów dla kawałków kodu (wyobrażasz sobie pół metody boldem bo to jest ważniejsze pół metody?). Wolna linia jest naszym narzędziem.

A wracając do wątku… po wyczyszczeniu tego jednego pliku odpaliłem te same reguły na całym solution i okazało się, że 120 plików się zmieniło. Jak dobrze, że są automaty, które potrafią usprawnić ten proces 🙂

A jak to jest w innych branżach?

W piekarni, cukierni, kuchni robi się bałagan. To jest naturalny efekt uboczny procesu przygotowania produktu. Tu się coś wysypie, tam się wyleje. Nie da się utrzymać kuchni bez jednego brudka, bez jednej skazy podczas gotowania,ale naturalne jest, że po skończonej pracy wszystko się sprząta. Nie zostawia się na kiedyś. Nikt nie mówi, jutro. Jak się zostawi syf to potem się zalęgnie robactwo i będzie coraz trudniej doprowadzić lokal do stanu używalności. Zresztą, czy ty jako klient swojej ulubionej restauracji wolisz, aby sprzątali na kuchni codziennie czy może nie sprzątali w ogóle a raz na pół roku (jak wynegocjują z klientami sprint na sprzątanie) robili miesiąc wielkiego, gruntownego sprzątania? Myślisz, że tak sobie wymyślam? Zobacz jak pracują kucharze w MasterChefie gdzie gotują amatorzy a jak w Top Chefie gdzie gotują zawodowcy. Pomimo skrajnie skromnych ilości czasu w Top Chefie na ugotowanie czegokolwiek – oni ciągle sprzątają. Gotują i ciągle, dbają o to aby było czyściutko – to się nazywa ciągła refaktoryzacja w kuchni. To nie jest zryw przed końcem czasu, to jest ciągły proces, ich druga natura. Talerz leży do wydania a stanowisko prawie jak nietknięte. W MasterChef-ie natomiast jest więcej czasu a mimo tego często na stanowiskach jest… bałagan. Jest bałagan, bo uczestnicy nie mają we krwi ciągłego sprzątania swojego warsztatu no i jak już dzieło jest skończone to nie ma czasu aby doprowadzić stół do porządku. To jest różnica pomiędzy profesjonalistami a amatorami.

Z długiem czy bez długu

Z tematu dług technologiczny zrobił się całkiem spory cykl. Mimo, że nie wszystkie aspekty zostały poruszone, to myślę, że poruszone zostały wszystkie najważniejsze jego aspekty zatem nadszedł czas na odpowiedzenie sobie czy da się realizować projekty bez długu.

Odpowiedź krótka brzmi NIE.

Jeśli w każdym aspekcie będziemy korzystali z wszystkiego NAJ to przy dzisiejszym tempie rozwoju okaże się, że nie robimy nic oprócz zmian wersji narzędzi, bibliotek, framework-ów, wrzucaniu coraz to nowych lepszych języków i innych tego typu wynalazków. Projekt sam w sobie nie będzie szedł za bardzo do przodu – o ile w ogóle. Cała sztuka z długiem technologicznym polega na tym, że po pierwsze primo MUSIMY być świadomi takiego zjawiska a po drugie primo MUSIMY nim jakoś rozsądnie zarządzać. Tak jak banki zarządzają długiem, tak jak kraje zarządzają długiem tak i my programiści powinniśmy zarządzać swoimi długami.

Nie wszystko co najlepsze na rynku możemy wykorzystać, podstawowy hamulec to nasi klienci. Jeśli kilku strategicznych klientów korzysta ze starych systemów operacyjnych, na których nasze nowe zabawki nie będą działać to chcąc nie chcąc nie przeskoczymy tego.

Jeśli goni nas termin (czy to prezentacji czy wdrożenia czy też po prostu kończą się pieniądze na projekt) to nie ma sensu wrzucać nowych wynalazków.

Jeśli nie mamy odpowiednich ludzi, którzy są wstanie w miarę rozsądnie rozpoznać nową technologię, to nie ma sensu w nią wchodzić.

W każdym momencie projektu warto natomiast wiedzieć gdzie jesteśmy do tyłu i wiedzieć dlaczego. W każdej chwili powinniśmy być również przygotowani na zmianę jeśli znikną nasze hamulce.

W przypadku bibliotek zewnętrznych i kontrolek oraz wszelkich kanałów komunikacji (programu) ze światem zewnętrznym wydaje się idealnie sprawdzać architektura a ’la Robert Martin. Mianowicie, co nie zależy w 100% od nas powinno być odizolowane – zapakowane w odpowiednie pudełko. To pozwoli nam zmienić zawartość tych pudełek w sytuacji gdy elementy blokujące nas znikną. Myślę, że zarówno nasz klient jak i kierownik czy prezes będą woleli usłyszeć że wymiana bazę danych na inną będzie wymagała miesiąca pracy zamiast 2 lat – lub w skrajnej sytuacji przerobienia całego projektu od nowa.

Wiedza o długu pozwala nam również go zaciągać świadomie. Moim zdaniem nie wolno, pod karą chłosty klawiaturami, iść na skróty w częściach korowych systemu ale już w detalach np. interfejsu użytkownika (KTÓRY NIE POSIADA LOGIKI APLIKACJI I LOGIKI BIZNESOWEJ!!!), możemy sobie czasem pozwolić na gorszą jakość. Pójście na skróty w pierwszym przypadku będzie miało brzemienne skutki dla całego systemu, w drugim bardziej lokalne. Jeśli w obu sytuacjach pójście na skróty oznacza zysk 5h to gdzie lepiej zostawić małe lokalne piekiełko?

Od tej pory bardziej świadomi powinniśmy podejmować bardziej świadome decyzje. Jeśli macie jakieś przemyślenia na ten temat, zapraszam do dyskusji.

Brak testów to brak pewności

Jednym z ostatnich rodzajów długów jakie chcę poruszyć to testy. Testy we wszelakiej postaci, od jednostkowych, przez integracyjne, specyfikacji, smoke, white i black box do obrzydliwych ręcznych. Brak testów to najgorsza rzecz jaką możemy zrobić.

W każdej normalnej branży (a nie takiej, gdzie większość to pryszczaci kolesie z problemami interpersonalnymi Smile with tongue out ) czyli takiej o solidnych podstawach ukształtowanych przez lata praktyki, normą są testy.

Budowlańcy robią testy wytrzymałości betonu, mieszanek, nawet próbki z każdej partii wylewanego betonu. Testują rozciągliwość stali. Testują przenikalności cieplne i milion innych rzeczy.

Przemysł motoryzacyjny jest pełen testów od – znowu – wytrzymałości poszczególnych elementów, stopów i kompozytów. Przez jazdy testowe trwające długie miesiące zanim pojazd trafi do salonów aż na testach zderzeniowych kończąc.

Lekarze i ich wynalazki są tak dokładnie testowane, że wprowadzenie nowego leku trwa latami zanim zacznie się testy na ludziach, a to dopiero początek drogi do wprowadzenia na specyfików na rynek. Zresztą sami lekarze po odebraniu dyplomu nie idą od razu do pracy aby np. operować – swoją drogą to by były jaja, “może mi ktoś zszyć pacjenta bo na zajęciach ze zszywania mnie nie było”.

Dziwnym trafem jeśli chodzi o programowanie, to często okazuje się, że testy są albo niepotrzebne albo sprowadzają się do “przeklikania” aplikacji przez 5 minut po skompilowaniu ostatnie najświeższej najlepszej wersji – czasem nawet te 5 minut to za dużo. Czy jesteśmy tacy genialni? Obawiam się, że nie. Większość z nas to po prostu ignoranci. Brak testów to nic innego jak brak pewności czy system robi to co powinien. Prosty program to setki a czasem nawet tysiące małych założeń. Każde takie założenie ma wpływ na końcowy produkt więc wypadało by każde założenie zweryfikować czy jest poprawne. Bez automatycznych testów zdajemy się na los. Praktyka wykazuje, że w większości przypadków to się sprawdza. Jednak użytkowników oprogramowania najbardziej denerwuje tych kilka przypadków gdzie są błędy. Nikt nie doceni genialności Twojego programu, jeśli mały błąd będzie powodował, że aplikacja będzie się sypać co 5 minut.

Jakoś wszystkie branże potrafią zgodzić się, że testy są kluczowe a pryszczaci kolesie z problemami interpersonalnymi  programiści ciągle mają z tym problem.

Żeby odciążyć siebie z pamiętania wszystkich założeń i odciążyć się z ręcznego klikania po aplikacji należy stosować testy automatyczne. Testy automatyczne mogą się wykonywać cały czas, w kółko. Nie kosztują nas więcej niż prąd do komputera. Nie masz komputera do testów? Użyj komputera na którym programujesz. Postaw maszynę wirtualną i niech przez całą noc kiedy nie używasz komputera (albo dzień w zależności od preferencji) niech on pracuje dla Ciebie. Niech testuje.

Testy automatyczne powinny trwać możliwie krótko aby szybko dawać informację czy wszystko jest okej oraz powinny być łatwe i tanie w utrzymaniu bo przecież nikt nie będzie tygodniami pisał/poprawiał/zmieniał testów. Należy też pamiętać, że testy to narzędzie a nie cel sam dla siebie – aha…. “przeklikanie” aplikacji to nie są testy aplikacji.

“Ale my nie mamy czasu na testowanie”

Czy aby na pewno? Ile czasu tracicie na poprawianie błędów? Ile czasu tracicie na poprawianie powracających błędów? Gdyby zainwestować kilka dni pracy na przygotowanie sensownego, automatycznego zestawu testów okaże się, że zyski będą większe niż początkowa inwestycja a z czasem ten zysk będzie rósł. Dobry zestaw testów pozwoli szybko wyłapać błąd a im szybciej wyłapiemy błąd tym taniej go poprawimy. Dla przykładu: jeśli po zrobieniu błędu 5 minut później otrzymamy o tym informację to poprawiamy to momentalnie. Jeśli błąd pojawi się tydzień później to trzeba odszukać źródło błędu (debug i krok po kroku, krok po kroku) a potem poprawić. Zazwyczaj to trwa dużo dłużej niż w sytuacji początkowej (5 minut po napisaniu błedu). Najgorsza sytuacja to taka, gdzie klient nam zgłasza błąd. Oprócz procedury odszukiwania źródła błędu dochodzi jeszcze czas klienta, który musi to zgłosić i wytłumaczyć co się dzieje, czas supportu, który musi klienta wysłuchać a potem bład zarejestrować i pewnie jeszcze czas kogoś kto musi błąd potwierdzić a po poprawieniu sprawdzić czy na pewno został on naprawiony. Całkiem spora rozrzutność. Brak jakichkolwiek testów powoduje, że najczęściej mamy sytuację jak w ostatnim przykładzie. I jak teraz wygląda wymówka “my nie mamy czasu na testowanie”? Nie macie czasu na testowanie bo robicie błędy i nic innego nie robicie tylko w kółko poprawiacie błędy – które mogły by być wychwycone przez dobry zestaw testów.

Monolity to też dług technologiczny

Monolity – wielkie projekty składające się z setek klas ściśle powiązanych ze sobą. Czasem to nawet nie muszą być przesadnie wielkie te projekty jednak wystarczy, że klasy są ściśle powiązane ze sobą.

Co to znaczy ściśle powiązane ze sobą? Jeśli w jednej klasie użyjemy słowa new, żeby stworzyć obiekt innej klasy to właśnie ściśle powiązaliśmy te dwie klasy. Jedna bez drugiej żyć nie może, nie da się jednej z nich przenieść do innej biblioteki bez odpowiednich referencji. Jeśli chcemy zlecić komuś napisanie jakiegoś modułu do nasze aplikacji, to musimy dostarczyć całą implementację wszystkich zależnych klas. Jeśli mamy ciąg 3 ściśle powiązanych klas taki, że A używa B a B używa C i mając każdą z tych klas w osobnym assembly, nie jesteśmy wstanie zmienić klasy B na inną (np B’) wykonania stosownych poprawek w klasie A. I o te poprawki się w głównej mierze rozchodzi. W obecnych czasach chyba nikt nie ma wątpliwości, że software się zmienia. Jeśli się zmienia to trzeba wprowadzać zmiany. Jeśli zaś wprowadzać zmiany to łatwo i szybko a nie tak, że zmiana w klasie B powoduje potrzebę wprowadzenia zmian w klasie A. Jak to zrobić? Ano całkiem prosto. Klasy powinny zależeć od abstrakcji – czyli Dependency Inversion Principle. Jeśli zależą od abstrakcji to możemy je podmieniać w miarę łatwo. Jeśli użyjemy jakiegoś kontenera DI – takiego co konfiguracje ma w XML-u to poszczególne kawałki systemu możemy podmieniać na inne bez rekompilacji kodu. Czyż nie jest to piękne?

Monolity mają jeszcze jedną wadę. Jeżeli wszystko jest zależne od wszystkiego, klasy ściśle powiązane a każda zmiana wymaga przeorania setek miejsc w kodzie, żeby to to ruszyć w ogóle, to metody postępowania są dwie. Pierwsza, wprowadzanie jak najmniejszych zmian czyli w praktyce powolne uśmiercanie projektu przez zaniechanie modyfikacji. Druga, dodawanie cudownych rozwiązań w stylu tutaj parametr do funkcji, tam jakiś if a tutaj case. Tym sposobem zachodzi zjawisko powolnego gnicia kodu. Z każdą iteracją takich fantastycznych hack-ów kod staje się coraz mniej czytelny i coraz ciężej rozwijalny. Koniec końców spotka go to samo co w pierwszym przypadku – śmierć.

Modne ostatnio programowanie agile czy też programowanie zwinne oznacza w dużym uproszczeniu ciągłą ewolucję i ciągłe wprowadzanie zmian, tak żeby być elastycznym na potrzeby biznesu. Nie da się być agile budując monolity, nie da się być agile zaciągając coraz to nowe długi technologiczne. W końcu, nie da się być agile jeśli po roku czy dwóch wprowadzanie zmian zaczyna trwać dłużej i dłużej. Jeśli chcemy być agile to musimy bardzo mocno trzymać się zasad dobrego programowania. Musimy tworzyć elastyczne i otwarte konstrukcje zamiast monolitów. SOLID w tym bardzo pomaga chociaż nie jest to jedyna “religia”. Aby to zrobić trzeba poświęcić odpowiednią ilość czasu i potu aby wyprodukować dobry kawałek kodu.

If you want to go fast, go well. — Robert C. Martin

Klepanie na hurrraaa… na wczoraj…. na jutro… zazwyczaj kończy się bardzo źle.

Nie będziesz refaktoryzował – będziesz miał dług

Refaktoryzacja – ot kolejne popularne słowo…. nie zupełnie. Pisząc software nie zawsze dokładnie wiemy jak on będzie wyglądał i co finalnie będzie robił – tzn. w danej chwili (zdefiniowanym kwancie czasu, żeby brzmieć mądrzej) zawsze wiemy co będzie robił, tylko z dalszej perspektywy mentalnej – tj. po dłuższym okresie może się okazać, że robi coś zupełnie innego niż początkowo zakładaliśmy. Oczywiście nie ma w tym nic złego, przecież wszyscy jesteśmy teraz agile jednak nie wiedząc co finalnie będzie robił kod, nie zawsze dobrze możemy zaprojektować jego architekturę. Nie wszyscy nie potrafią, John Skeet potrafi, ale to nie podlega dyskusji.

Architektura powoli ewoluuje i żeby nie skończyć z kupą gruzu, należy o nią dbać. Tym zajmuje się refaktoryzacja właśnie. Tzn. nie tylko tym ale między innymi. Jeśli nie dbamy o kod, nazewnictwo, strukturę i cały ten bajzel to powoli małymi kroczkami zbliżamy się do katastrowy. Ten proces niestety jest bardzo powolny – niestety ponieważ przez to łatwo go zaniedbać. Wymówki w stylu “poprawię później”, “później to zmienię”, “później wydzielę do osobnej klasy” częściej pozostają w sferze NIGDY niż kiedykolwiek się materializują a ponieważ nic się nie dzieje z tego powodu to nie ma czym się martwić. Do czasu aż uświadomimy sobie, że grzebanie w tym konretnym kodzie to straszne bagno ale wtedy jest już za późno. Dlatego o kod trzeba dbać, trzeba być jak skaut, zawsze zostawiać kod odrobine lepszym niż się go zastało.

W refaktoryzacji bardzo pomagają testy i to takie testy, które dają nam jedyne słuszne pokrycie kodu – 100%. Jeśli mamy 100% pokrycie kodu testami, to (teoretycznie) wiemy w 100% co robi nasz kod. Jeśli zaczniemy zmieniać jego strukturę, dostaniemy informację czy nasz kod działa tak samo jak przed zmianami czy nie. W sytuacji idealnej możemy zmienić całkowicie architekturę przyspieszając np. działanie programu bez jakiejkolwiek widocznej zmiany dla użytkownika. Jednak aby to zrobić musimy mieć 100%-owe testy. Przez 100%-towe testy rozumiem, takie, które dają 100% pokrycia kodu (czyli nie ma jakiejkolwiek niesprawdzonej linii kodu) i dają 100% bezpieczeństwo, że psująca zmiana zostanie wyłapana. Mając takie testy mamy komfort wprowadzania zmian. Możemy całość przerobić na CQRSy czy inne wynalazki i mieć pewność, że nic nie zepsuliśmy.

Jeśli nie mamy takich testów to może się okazać, że przy refaktoryzacji coś przypadkiem zepsuliśmy. Jeśli to będzie się zdarzać często to coraz rzadziej będziemy refaktoryzować. To zaś będzie prowadziło do psucia się kodu. Ot koło zamknięte.