Refaktoryzacja lekko i szybko

Refaktoryzacja bardzo często kojarzy się z czymś skomplikowanym i czasochłonnym, dziś trzy lekkie lekcje zadające kłam tej teorii. Wszystko w myśli tego zdania:

modyfikowanie elementów systemu w celu wpasowania ich w przyjęte standardy i wzorce

Youtube na końcu wpisu.

Część pierwsza

Tak samo

Spójrz na taki kod i postaraj się znaleźć wszystkie metody który Twoim zdaniem dodają coś do bazy danych.  (zignoruj na chwilę ogromną ilość metod w jednej klasie to temat na inny wpis)

Skupiając się tylko na części Create z CRUDa da się odnaleźć:

AddCommentRequestByUserForArticle, AddEventRssSkipped, AddEventRssViewed, AddFullArticleRequestByUserForArticle, AddNotReadRequestByUserForArticle, AddReadLaterRequestByUserForArticle, AddShareRequestByUserForArticle, AddVoteDownRequestByUserForArticle, AddVoteUpRequestByUserForArticle, AssignUserRole, CopyAllUnreadElementsToUser, CreateNewSociaLogin, CreateNewSubscription, CreateNewSubscriptionForUserAndChannel, CreateNewUser, MarkRssClicked, AddEventRssClicked, MarkRssNavigated, AddEventRssNavigated, InsertReadRssToRead, MarkAllReadForUserAndSubscription, MarkChannelRead, SaveChannelCreatedEventToDatabase, SaveContactAdministrationEventEventToDatabase, SaveContactAdministrationToDatabase, SaveEvent, SaveToDatabase, SaveExceptionToDatabase, Subscribe, AddEventPersonActivityClicked, AddEventPersonActivityNavigated, AddEventPersonActivitySkipped, MarkChannelUpdateClicked, MarkChannelCreateClicked

Widać różnie prefiksy który się pojawiają do opisania akcji, który ma za zadanie zapisanie danych do bazy danych: add/assign/copy/create/mark/save/. Metody powstawały w różnych okresach i pewnie jak by się przyjrzeć to odpowiednie prefiksy pasują do tych z warstwy wyżej. Na przykład jeśli metoda w serwisie nazywała się MarkClicked to nazwa na warstwie repozytorium też się tak nazywała. Może to byłe wtedy fajne i dobre. Tworzyłem kolejną metodę w serwisie o nazwie AddJakiśNowyEvent to pojawiała się metoda AddJakiśNowyEvent w repozytorium i było jasne że one ze sobą pracują.
Tylko że teraz, proszę ja ciebie gdy mam takie dwie metody MarkAsRead oraz MarkAsViewed, i obie przekazują dane z serwisu do repozytorium, praktycznie ten sam zestaw danych. Jedna z nich robi insert druga z nich robi update – zgadniesz która?

Dlaczego o tym mówię? Projekt tworzę sam, powinienem wszystko ogarniać, wszystko wiedzieć, móc wszystko 😉 Zdarzają się jednak niespodzianki czy duplikowanie kodu. Już odpowiadam dlaczego – ponieważ nazwy metod nie są spójne (i nie do końca jasne?). Jak wyczuć że MarkAsRead robi INSERT ale już MarkAsViewed robi UPDATE? Dopóki jest to dla mnie świeży temat to jasne – będę pamiętać. Ale gdy skończę daną funkcjonalność zapomnę i będę musiał sprawdzać, albo co gorsza poprawić bugi z tym związane. Gdybym od razu podszedł to tematu trzymając się na przykład założenia, że skoro moja aplikacja to nic innego niż fantazja nad CRUDem i że nazwy metod mogłyby zaczynać się od na przykład prefiksów create/read/update/delete miałbym wtedy jedno “miejsce” gdzie mógłbym szukać insertów do bazy danych. Mógłbym mieć metody w repozytorium który nazywały by się w takim stylu:

CreateEventRead, CreateRoleForUser, CreateRssForUser, CreateContactToAdministration, UpdateRssByWithClick, UpdateRssEntriesWithSkipEvent, ReadRssEnties, ReadUserPublicProfile, DeleteReadEvent, …

Takie podejście kosztuje chwilę czasu, aby lekko przeformatować swoje myśli, ale mam nadzieję że wprowadzi ono porządek do projektu, oraz ułatwi wcześniejsze wykrycie kodu który już jest, co powinno zmniejszyć poziom jego duplikacji.

Część druga

dLa sPrAwNeGo cZyTeLnIkA TeN TeKsT NiE PoWiNiEn sPrAwIć pRoBlEmU. Ddoponioe tne tkest ad sęi tżeak oydtaczć.
bUt tHe mOmEnT YoU StArT TyPiNg iN FoReIgN LaNgUaGe, ThInGs gEt mEsSy. Adn dno’t gte em satetrd wtih tsih apaporch.

Literówki

Słynne literówki, których ten blog jest pełen. Powodują że wyszukanie czegoś graniczy z cudem. Niby coś jest w kodzie, a nie można tego znaleźć, bo nie często popełnia się tę samą literówkę dwa razy (chyba, że jesteś mną). Czasami zdarza się zamienić literki miejscami, czasem się jedną zje, a czasem wybierze się odmianę amerykańską zamiast brytyjskiej, znam ludzi, który się o to czepiają.

color or colour

Należy to od razu się to naprawić i wrzucić do repozytorium. Mnie osobiście bardzo bolą literówki związane z bazą danych, a jeszcze bardzie literówka w bazie na produkcji- albo raczej jej naprawa, oj joj joj jak boli.

Kejsówki

Bo kto mi zabroni? Drobna rzecz a kłuje w oko, gdy nazwy w definicjach tych samych pól różnią się przyjętą w konwencji wielkością znaków. Bardzo często pojawia się notowanie identyfikatora obiektu przez ID lub Id. Czy UserID lub UserId. To kolejna rzeczy która może utrudnić szukanie (jeśli włączony jest case-sensitive), czy zaburzyć łagodny i delikatny flow aplikacji gdy trzeba porównać UserId z ID użytkownika. To co ważne tutaj: nie ma jedynego słusznego rozwiązania, ważna jest przyjęta konwencja, jeśli ID to wszędzie ID, jeśli Id to wszędzie Id.

KONWENCJA PLAŻO!

ps.
Spójrzcie podobnie na swoje UserViewModel lub UserViewmodel, czy nazwy zmiennych uvm, uservm, userVM, userVm czy może macie jeszcze inaczej? Może to też warto wyprostować i pilnować tego w projekcie.

Część trzecia

Klamry

Klamerki i ogólnie przyjęte formatowanie kodu. Dla lepszej czytelności i spójności kodu gdy widzisz, że gdzieś w kodzie ich brakuje zmień ten stan i dodaj je. Bez względu na przekonania takie czy inne, weź klawiaturkę w rękę i dodaj { a potem }. Spójrz na kod poniżej

Na pierwszy rzut oka się może wydawać, że kod jest płaski, a jego skomplikowanie niskie. Rajt? No to po dodaniu klamerek wygląda tak:

I gdy kod po dodaniu klamerek zaraz za ifem/elsem powoduje u Ciebie odruch wymiotny, bo przecież:

hadouken pattern
hadouken pattern

To zaciśnij usta mocno i pomysł skąd się bierze ten wzorzec? Czy dodanie { } to spowodowało, czy może on tam był ukryty, a dopiero dzięki klamrom wreszcie go dostrzegłeś? Dziękujemy wam o klamerki za wywabienie potwora z ciemności. Poprawimy to w sprincie technicznym, albo żyć będziemy w pogardzie dla samych siebie po wsze czasy.

Enter

Tak samo gdy znajdziesz długie jedno linijkowce, zachęcam by je lekko połamać i sprawdzić na ile właściwych linijek kodu się mogą zamienić. Spójrz na kod poniżej i pomyśl, który z nich prościej jest Ci przeczytać?

Poprzez użycie entera w krótkich punktach wskazujesz to co chcesz aby stało się z danymi pobieranymi z źródła danych

Enter znowu

Enter poza takim oczywistym połamaniem długich linijek oznacza jeszcze jedną ważną rzecz, jest on naturalnym rozgraniczeniem myśli/intencji. Możemy zapisać kod w taki sposób:

Ale możemy sobie trochę pomóc w czytaniu tego co piszemy i lekko zmodyfikować tę metodę, na przykład w taki sposób:

Widać teraz wyraźny podział metody na kilka odpowiedzialności (!). Płyną z tego dwie korzyści, po pierwsze sama metoda jest czytelniejsza, po drugie kolejny raz wychodzi, że kod który powstaje nie do końca zna regułę SRP. Na szczęście sprint techniczny mamy już zaplanowany.

Koniec

Jak się okazuje możemy wykonać taką refaktoryzację, która nie będzie kosztowała miliona cebul, nie będzię wymagać od nas skupienia się i szukania wzorców, z których moglibyśmy skorzystać aby uczynić nasz kod piękniejszym. Ot zwykła poprawa czytelności kodu.

Film do wpisu:

9 thoughts on “Refaktoryzacja lekko i szybko

  1. Do takich rzeczy są potrzebne *standardy* w projekcie i narzędzia które pomogą wymóc ich stosowania

    Do C# podstawowe problemy z edytowaniem (znak EOF, tab czy spacje) można rozwiązać przy pomocy wieloplatformowego .editorconfig http://editorconfig.org/

    Większość problemów dotyczących stylu pisania w JS zostało rozwiązane przez restrykcyjne i współdzielone w repozytorium definicji “jak pisać kod” w ramach lintera. Przykładowy zbiór zasad https://github.com/airbnb/javascript . I nie ma dyskusji czy “napisać z małej czy wielkiej” litery czy jakie litery, bo jeżeli programista nie napisze w wymagany sposób, to automat odrzuci podany kod.

    1. Masz racje. Ale to wszystko czasami może nadpisać przyjęta w projekcie *inna* konwencja. Poza tym jest jeszcze własny rozum, i czasem się chce zrobić coś inaczej (nie mówię czy lepiej czy gorzej)

  2. Dlatego lintery pozwalają sterować zasadami w ramach komentarzy w kodzie gdzie programista musi *zadeklarować* że jest świadomy łamania reguły i ewentualnie dodać komentarz dlaczego tak robi. Ewentualnie koledzy przeglądający kod zobaczyliby tą “jawną deklarację i zdecydowaliby czy przyjąć czy odrzucić, a nie cichaczem przemycać niespójny kod.

    Fajną rzeczą jest przy zmianie standardów, napisać kod/wskazać kod który zamieni Twój kod zgodny z przyjętym standardem tak jak robi https://github.com/facebook/jscodeshift

  3. Chyba poprzedni mój komentarz nie dodał się 😛 Bardzo fajny artykuł 🙂 Mam pytanie nie dotyczące samej refaktoryzacji, ale jej słuszności w niektórych sytuacjach. Weźmy taki scenariusz: otrzymujesz projekt, który jest jednym wielkim śmietnikiem, przekazywanym od osoby do osoby, tak około dwudziestu osób. Każda osoba dokładała od siebie coś nowego, ale nikt nie refaktoryzował niczego bo projekt już działa i nie ma na to czasu. Efektem tego mamy monstrualną klasę, z jedną gigantyczną metodą na 1200 linijek i kilka trochę mniejszych. Dokładać swoje rzeczy i liczyć na to że nic się nie sypnie i że komuś przekażesz ten gorący kartofel nie jest rozwiązaniem, ale na przeoranie kodu nie ma ani czasu, ani potrzeby ze strony zleceniodawcy, gdyż używa projektu więc z jego strony wszystko działa i raczej nie będzie dopłacać za coś czego nawet nie widzi. Co w takiej sytuacji uważasz za rozsądne, co byś w takiej sytuacji zrobił?

    1. Hej Adam,
      Jeśli to był jednorazowy wybryk to zrób zadanie tak aby były spójnie (wujowo, ale stabilnie). Kod lepiej się naprawia gdy bałagan który jest, jest wszędzie taki sam.

      Natomiast jeśli siedzisz w tym projekcie trochę dłużej to myślę, że dla siebie samego powinieneś zrobić porządek. Możesz zacząć od prostych rzeczy jak spójne nazwy, rozbicie dużej metody/klasy na mniejsze, kilka minut dziennie w ramach wykonywanego zadania. Trochę partyzantka, nikt nic nie wie, a coś się dzieje.

      Jeśli masz PM w projekcie to trzeba go uświadomić ile płacicie (tzn. klient płaci) za utrzymanie długu technologicznego, np. dodanie guzika to zamiast 5 minut zajmuje około godziny, dlatego że trzeba tu i tu i tu….. Przesunięcie guzika na UI zamiast minuty zajmuje godzinę bo trzeba to tu i tu i tu … Jeśli jest plan na utrzymywanie projektu to klient będzie płacić za to X razy więcej niż powinien.

      Jeśli pracujesz bezpośrednio z klientem (brak PM w projekcie) to niestety ale musisz mu to sam sprzedać. Ale zanim to zrobisz wcześniej musisz się uzbroić w cyferki. I to konkretne, a potem opowiedzieć ile czasu i pieniędzy może zaoszczędzić jeśli pozwoli wam na dzień/tydzień/miesiąc poprawek w kodzie. Jeśli cyfry wyjdą za małe np. wyjdzie że zaoszczędzi na tym 10k$ w ciągu miesiąca, to użyj skali i powiedz ile to pieniędzy w ciągu roku, trzech, pięciu – możesz to porównać do np. Tesli którą mógłby kupić.

      Można też w raportowaniu godzin rozpisać, że zrobienie zadania zajęło Ci 5 minut, ale sprawdzenie wszystkiego i poprawienie w innym miejscach (bo stary kod) dodatkowe n-godzin, a potem np. z braku testów kolejne m-godzin. Tak zaraportować kilka zadań, a następnie poprosić klienta aby to wszystko sam policzył.
      To co jest jeszcze arcy ważne, wszystko zrób w porozumieniu z pm/szefem – może nie wiem, ale firma ma taką politykę, aby liczyć klienta za godziny i Ty wszystko zepsujesz.

      Mam nadzieję, że trochę pomogłem. Jak coś to pisz, będziemy myśleć.

    2. Często pracuję z wieloletnim kodem w dużych projektach. Często też poprawa błędu czy dodanie nowej funkcjonalności wymaga zapoznania się z ogromną i nieczytelną metodą. Ekstraktowanie metod, zmiana nazw zmiennych (np skrótowców w stylu “trks”) czy opisanie magicznych liczb to mechanizmy, które są naturalną częścią każdego zadania. Na to nie potrzeba dodatkowego czasu (oczywiście bez narzędzi typu R# jest to trochę bardziej czasochłonne), bo mieści się w czasie poświęconym na złego kodu. Skoro już poświęciłeś czas na zrozumienie zastanego kod popraw go natychmiast. Następnym razem Ty lub ktoś inny będzie czytał go o tyle krócej. W efekcie wszyscy na tym zyskają. Jako profesjonalista powinieneś uwzględnić w estymacie zadania takie rzeczy jak implementacja, refaktoryzacja i testy. To jest definicja zrobionego zadania i bez tego nie można go oddać.

      1. To o czym piszesz, to że nie używamy “magic number” tylko dobrze opisaną zmienną to warsztat osoby z doświadczeniem – taka osoba zrobi dobry kod w czasie pisania swojego zadania. Osoby który są młode w branży, czy nie posiadają doświadczenia (czytaj: magic number nie kopnął ich wystarczająco dużo razy w tyłek) nie napiszą takiego kodu i zahardkodują 42. Dlatego potem Ty czy np. Adam przychodzą do projektu i muszą mieć dodatkowy czas na rozszyfrowanie i poprawienie kodu który jest.

  4. Dzięki wielkie, argumentacja z większym nakładem czasu na poprawki oraz cykliczne poprawianie tu i tam robi robotę. Zacząłem schematem: piszę testy na jedną funkcjonalność, wydzielam ją i przenoszę do osobnej metody, grupuję i przenoszę metody do osobnych mniejszych klas. Myślę że dzięki temu nawet szybciej zaznajomię się z całą strukturą projektu. Jeszcze raz dzięki

  5. Pingback: dotnetomaniak.pl

Leave a Reply to AdamCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.