Refactoring. Trudne do okiełznania, lecz przydatne bydle.
13:44, 13 września 2010 roku
| Notka, TutorialOstatnimi czasy zabraliśmy się w Zeno.pl za refactoring kodu którego używamy do pisania naszych aplikacji – ZenoFuel i ZenoApp.
Staramy się to robić raz na kilka projektów, bo nic tak nie pokazuje ewentualnych pól do poprawy jak używanie kodu w praktyce. Tym razem postanowiliśmy jednak zrobić kilka sporych zmian – co oznacza również przegląd kodu od początku do końca.
W skrócie: nasz framework oparty jest luźno na Zend Framework ( podobny jest podział na pliki, wykorzystujemy kilka z jego modułów – np. do wysyłki poczty czy łączenia się z bazą danych ), napisany jest zgodnie z duchem MVC, wykorzystuje OPT do generowania HTMLa.
Składa się z kilku modułów: zestawu kilku Routerów ( do wyboru do koloru ), które decydują o tym jakie tworzyć Kontrolery, które to mają hooki pozwalające np. przed wykonaniem akcji na sprawdzenie uprawnień użytkownika. Zestawu klas do tworzenia Formularzy opisanych za pomocą XMLa. Abstrakcyjnych Modeli pozwalających na łatwy i uniwersalny dostęp do bazy. Prosty Cache oparty na APC ( który ma być wbudowany w PHP od wersji 6.0 ). Obsługę Sesji w bazie danych i plikach. No i całej masy klas pomocniczych do różnych zadań specjalnych.
Część tego kodu nie zmieniała się od kilku lat. Stał się on swoistym ‘blackboxem’ który po prostu działa. A skoro działa, to nie ma co w nim grzebać. Zmiany natomiast wprowadzamy gdy w jakiś sposób kod nas ogranicza lub widać miejsce które mogło działać by szybciej, lepiej, zgrabniej. Na bieżąco staramy się też dodawać komentarze lub uwagi na przyszłość.
Tyle słowem wstępu a propos materiału na którym oparte są te przemyślenia.
Czym jest refactoring ( tudzież refaktoring tak z polskiego ;-) )?
Refaktoryzacja to zmiania kodu programu, nie zmieniając jednocześnie zbytnio jego funkcjonalności. Oznacza to, że nie wprowadza się przy tym nowych featuresów, tylko dba się o to, aby nazwy metod, parametrów, funkcji itp. itd. były odpowiednie. Dodaje się komentarze tam, gdzie jest to wymagane. Rozbija się kod na mniejsze porcje – dzieląc na nowe metody i funkcje. W niektórych przypadkach można nawet w ogóle wydzielić część funkcjonalności do osobnej klasy.
Co oznacza “odpowiednie”?
Samo opisujące się. Idea jest taka, żeby przeczytanie nazwy metody dało nam już pełen ogląd na to, co owa robi i co zwraca. W praktyce trudno się operuje potem metodami które złożone są z siedmiu słów, nawet mimo podpowiadaczy we współczesnych edytorach.
Czyli tak naprawdę sprowadza się to do połączenia ze sobą komentowania i odpowiednich nazw – szczególnie, że taki Eclipse potrafi pokazać co metoda robi poprzez pokazanie nam,oprócz nazwy, również jej dokumentacji w auto-uzupełniaczu.
Umiejętność odpowiedniego wypoziomowania tych dwóch podejść przychodzi z praktyką.
Refaktoryzacja jest fajna i przydatna z kilku względów. I można robić ją na wielu poziomach.
Po pierwsze ułatwia zrozumienie kodu po dłuższej rozłące. Nie oszukujmy się, gdy wracamy do kodu po dwóch trzech miesiącach trochę czasu zajmuje nam połapanie się o co w nim w ogóle chodzi i czemu robi to co robi i jakim prawem to w ogóle działa?! :O Dobry podział plików, nazwy klas, metod, funkcji, odpowiednie zależności klas od siebie, hermetyzacja i odpowiednie komentarze/dokumentacja pozwalają skrócić czas ponownego zrozumienia.
Po drugie dobrze podzielonym kodem łatwiej zarządzać. Jeżeli w kodzie panuje chaos, to nie wiemy czy zmiana działania danej metody sprawi, że w innym miejscu nie pojawią się błędy. Porządek pozwala utrzymać panowanie.. nad kodem i nas swoimi nerwami ;-)
Po trzecie zmiana kodu na lepszy często wiąże się ze wzrostem wydajności. I tak, mimo, że komputery są coraz potężniejsze – oszczędzanie wydajności bardzo się przydaje. Podobna zasada tyczy się generacji HTMLa i CSSów – należy oszczędzać transfer.
Po czwarte jest to niezły ubaw. Z dwóch względów – widzimy jak się rozwinęliśmy, gdy coś co kiedyś potrzebowało kilku ifów i dziwnych funkcji teraz potrafimy zrobić połową ilości poprzedniego kodu. Niektóre nasze stare komentarze dla potomnych również potrafią rozbawić do łez ;-)
Jak pisać kod by potem dużo nie refaktorować?
To jest pytanie na zupełnie osobny wpis, ale odpowiedź może być prosta: pisać dużo kodu. Wracać do starego kodu, myśleć co by się w nim zmieniło i robić to :-)
Ja, gdy mam pomysł na rozwiązanie jakiegoś problemu, to najpierw sprawdzam go w praktyce, później dopiero, ale chwilę po sprawdzeniu czy działa, porządkuje go. Jeżeli może okazać się, że pomysł jest zły, to nie ma sensu poświęcać czasu na napisanie go od razu poprawnie. Jeżeli kod rzeczywiście działa, to trzeba sprawdzić, czy nasza pisanina nie ma zbyt rozciągłych czy zduplikowanych zmiennych, za dużo pętli i czy coś do poprawy nie rzuca się od razu w oczy. Nie ma co szukać na siłę, jeżeli wszystko wydaje się ok i spełnia swoją rolę, to nich tak zostanie.
Lepiej wrócić do takiego kodu za jakiś czas, gdy już osiądzie w swoim kontekście i będzie kilka razy wykorzystany – wtedy łatwiej zobaczyć co mógłby robić lepiej lub inaczej. Czy nie lepiej go podzielić, wydelegować niektóre czynności na zewnątrz. Czy część z tego co robi nie mogła by być wykorzystana w innym miejscu – czyli czy nie lepiej jej wydzielić obok. A może należy coś dopisać?
Może tablica z czynnościami do wykonania na porcji danych powinna zostać wydzielona do osobnej kolekcji, może trzeba dodać dodatkową parametryzacje, zrobić kod bardziej ogólny? W niektórych przypadkach kod jest na tyle samodzielny, że powinien stać się osobną klasą.
Co zmieniliśmy?
W Zeno, po przejrzeniu kodu ZenoFuel, liczącego sobie około 600 plików i 400 klas, interfejsów i abstrakcji, do zmiany poszło około 40% ( licząc również drobne poprawki kosmetyczne, w stylu ładniejsze ułożenie kodu, wywalenie nadmiarowych białych linii itp. itd. ).
Zmieniło się nazewnictwo kilkudziesięciu klas, niektóre zostały wyodrębnione w osobne miejsca – np. filtry danych z klas do formularzy uzyskały samodzielność, można je teraz bez obaw wykorzystywać zupełnie obok. Oznacza to również, że poszły z drzewa katalogów Formularzy do swojego własnego osobnego ( wyprowadziły się z domu ). Kontrolery straciły pewną część nadmiarowych metod i składowych ( pól ) – z kilku rozwiązań tych samych problemów wybraliśmy najlepsze. Niektóre klasy trzymają w sobie więcej danych aby zmniejszyć obciążenie bazy danych dodatkowymi zapytaniami – okazało się, że i te dane są często wykorzystywane w praktyce w naszych aplikacjach. Zmieniły się też odpowiedzialności klas, np. teraz to Kontrolery zajmują się przetwarzaniem parametrów które dostały od Routera – wcześniej to on nimi zarządzał. Niektóre metody skróciły się o dobre 70%, w kilku miejscach doszła rekurencja – jako jednak lepszy wybór.
Po tym etapie wszystko działa dokładnie tak samo jak działało wcześniej, jednak w ten nowy fajniejszy i lepszy sposób niż kiedyś – i to jest właśnie refaktoryzacja.
Klasy zostały też uporządkowane tak, aby móc przyjąć na siebie nowe funkcjonalności które dla nich planujemy, np. eventy ( zależności ) w Formularzach, bardziej rozbudowany Cache, elastyczniejszą Konfigurację, generowanie HTMLa Formularzy zdefiniowanych w XMLu, itp. itd. A wszystko w duchu wzajemnej nauki.
Oczywiście i te nowe featuresy dostąpią zaszczytu refaktoringu za kilka miesięcy, gdy w praktyce okaże się, czy spełniają dokładnie swoją rolę ;-)
Dopisz się do RSS 2.0.
Możesz skomentować, albo trackbackować z Twojej strony WWW.
tom-as | 13-września-10 at 3:52 po południu | Permalink
hmm.. najlepszego z okazji dnia programisty :P
CooKiE | 14-września-10 at 11:54 przed południem | Permalink
Ja bym jeszcze dodał, że mimo tego iż w definicji refactoring’u nie ma zasadniczo mowy o dodawaniu nowych funkcjonalności to w praktyce często z refactoring’iem rozwijanie kodu idzie w parze. Dzieje się tak dlatego, że:
1) często np. chcąc dodać jakieś nowe funkcjonalności używając starych, dochodzimy do wniosku, że lepiej by było wyprać cuchnące skarpety, które będziemy zakładać do naszego nowego pachnącego garnituru. Dzięki temu później oszczędzamy czas, gdyż w końcu i tak i tak ten refactoring byśmy przeprowadzili, a robiąc go od razu mamy mniejszą ilość kodu na którym musimy go wykonać (potem trzeba np. zrefactoringować sposób używania tych starych modułów w nowych – a często bywa niestety tak, że przy refactoringu zmieniają się trochę interfejsy – gdyż wymaga tego zakres “porządków).
2) albo przeprowadzając refactoring, bez założeń o tworzeniu jakiś nowych ficzerów, dochodzimy do wniosku, że pewne rzeczy trzeba by było dopisać od razu, bo przydadzą się w przyszłości.
Tak, więc sam refactoring występuje zazwyczaj tylko w przypadku naprawdę dobrze przemyślanego kodu, który nie musi już być w dużej mierze rozwijany (a tylko dobrze utrzymywany). A nawet jak jest dobrze utrzymany i przemyślany to często się zdarza, że trzeba z czymś puść do przodu z powodu nowych pojawiających się rozwiązań czy technologii.
Adam Gladkowski | 14-września-10 at 4:00 po południu | Permalink
Pisał Pan o DB2 na linuxie swego czasu. Czy jest Pan z nim związany zawodowo, a i może byłby zainteresowany ofertą pracy właśnie z administracją takiej bazy związaną? Poproszę o kontakt, najlepiej na adam.gladkowski@teamquest.pl
Piotr 'Zenobius' Baranowski | 14-września-10 at 4:35 po południu | Permalink
@tom-as – dzięki :-)
@CooKiE – generalnie zanim się wprowadza coś nowego to dobrze jest kod uporządkować. Więc wtedy refactoring jest zanim się dodaje featuresy. Więc też jest osobną iteracją pisania kodu.
No i tak, często nowe pomysły przychodzą przy refactoringu. To też jest plus o którym zapomniałem napisać.
Wg mnie można to wszystko rozłożyć na dwie operacje.
@Adam Gladkowski – nie, nie mogę powiedzieć, że znam DB2 na tyle dobrze, by móc się nim zajmować zawodowo. Ale dziękuję za zainteresowanie :-)
CooKiE | 14-września-10 at 5:06 po południu | Permalink
@Zenobius, no fakt że często to się rozkłada na dwie osobne iteracje, ale mi chodziło, że zazwyczaj “chodzą w parze” te dwie iteracje.
Coleen Sosa | 23-grudnia-10 at 7:50 po południu | Permalink
Pisał Pan o DB2 na linuxie swego czasu. Czy jest Pan z nim związany zawodowo, a i może byłby zainteresowany ofertą pracy właśnie z administracją takiej bazy związaną? Poproszę o kontakt, najlepiej na adam.gladkowski@teamquest.pl