Głównymi przesłaniami tej książki to:
- waga nazewnictwa klas, funkcji, zmiennych składowych, zmiennych lokalnych i wszystkich pozostałych bytów (pakietów, przestrzeni nazw itp.)
- wszystkie byty (klasy, funkcje, struktury danych itp.) powinny być maksymalnie proste i krótkie. Powinny przedstawiać jedną rzecz, realizować jedną czynność, której istota jest zawarta w opisowej nazwie (punkt pierwszy)
Od tych dwóch zaleceń wywodzi się szereg innych dotyczących zasad budowania klas, funkcji, zależności między modułami i wiele innych. Nie będę ich tutaj oczywiście przytaczał, zainteresowanych odsyłam do tej pozycji.
Idąc śladami autora tej książki, przebuduję mój kod który już wcześniej zamieściłem w notce "Wielostronicowe formularze". Napisałem testy jednostkowe do tej klasy (pisząc plugin, testów nie napisałem ;]), po każdej wprowadzonej zmianie odpalałem testy aby sprawdzić czy klasa nadal działa tak jak powinna.
Nazewnictwo
Pierwszą rzeczą którą zrobiłem to zmiany nazw niektórych metod oraz składowych:- zmiana get/setPage() na get/setCurrentPageNumber, validatePage na validatePageNumber
- zmiana getPages() na getNumberOfForms()
- zmiana składowej globalNamespace na useGlobalNamespace, analogicznie zmiana nazwy metod dostępowych
Zawahałem się przy metodzie setForms, okazało się że nazwa tej metody wprowadza w błąd, gdyż nie ustawia ona formularzy, a jedynie nadpisuje formularze dla pierwszych stron (błąd logiczny - jeśli przekazaliśmy mniejszą liczbę formularzy niż było ich wcześniej, to na końcu tablicy zostaną formularze z przed wywołania metody). Zdecydowałem się na napisanie dodatkowej funkcji o nazwie addForms, która dopisuje tablicę formularzy. Metoda setForms, tak jak nazwa wskazuje, nadpisuje wcześniej dodane formularze.
Wprowadziłem również inne drobne zmiany nazw (np. zmiennych lokalnych) i wydają się już one być dobre, teraz czas zająć się metodami.
Metody / funkcje
Najważniejsze jest to aby metoda operowała na jednym poziomie abstrakcji, realizowała jedną czynność i była maksymalnie krótka. Na pierwszy rzut oka większość metod spełnia te warunki, ale jest kilka które należałoby poprawić.Na pierwszy ogień poszła metoda addForm.
[PHP]
public function addForm(sfForm $form) { $form->setOption('page', $index+1); $this->forms[$index] = $form; if(!$this->isUseGlobalNamespace() && $key = $this->getFormKeyName($form, true)) { $format = $form->getWidgetSchema()->getNameFormat(); $form->getWidgetSchema()->setNameFormat($format); } else { $form->getWidgetSchema()->setNameFormat($this->getNameFormat()); } }
Robi ona co najmniej trzy rzeczy: oblicza indeks formularza oraz kolejny numer strony, dodaje formularz do tablicy oraz zmienia nazwę formatu widgetu. Nie operuje ona również na jednym poziomie abstrakcji, gdyż przykładowo wyznaczanie numeru indeksu oraz numeru strony jest zadaniem niższego poziomu niż samo dodawanie formularza do tablicy. Rozbiłem tą metodę na kilka mniejszych, odpowiedzialnych za jedną czynność. Wyodrębniłem metodę wykonującą dodawanie formularza do tablicy na odpowiednim miejscu, konwertującą indeks formularza do numeru strony, czy też zmieniającą nazwę formatu formularza.
[PHP]
public function addForm(sfForm $form) { $this->doAddForm($form); $this->setFormNameFormat($form); } private function doAddForm(sfForm $form) { $index = $this->getNumberOfForms(); $form->setOption('page', $this->convertIndexToPageNumber($index)); $this->forms[$index] = $form; } private function convertIndexToPageNumber($index) { return ($index+1); } private function setFormNameFormat(sfForm $form) { if($this->shouldBeUsedGlobalNamespace($form)) { $form->getWidgetSchema()->setNameFormat($this->getNameFormat()); } else { $format = $form->getWidgetSchema()->getNameFormat(); $form->getWidgetSchema()->setNameFormat($format); } }
Jak widać kod jest nieco dłuższy, ale głównie ze względu na sposób formatowania kodu. Na tej operacji zyskaliśmy za to czytelność oraz wyodrębnienie przydatnej metody konwertującej indeks do numeru strony.
Z metody getForm wyodrębniłem metodę konwertującą numer strony do indeksu formularza, więc zyskaliśmy na spójności mając dwie metody konwertujące te wartości między sobą. W miejscach gdzie taka konwersja była dokonywana wykorzystałem te metody.
Kolejną metodą, którą trzeba wyremontować jest metoda bind. Jest to najdłuższa metoda całej klasy.
Obecnie metoda ta wykonuje następujące czynności: przypisuje parametry do składowych klasy, resetuje stan formularza (wartości, flagę isBound, tworzy nowy obiekt przechowujący błędy), binduje kolejne formularze zapisując przy okazji ewentualne błędy, tworzy tablicę wartości formularza.
Wyodrębniłem metodę resetFormState, wyodrębniłem metodę dodającą wartości z formularza składowego do głównego formularza stronicowanego (metoda addValuesFromForm) oraz bindującą jeden formularz składowy.
Metoda getFormKeyName tworząca klucz dla formularza wg formatu nazwy widgetu również była zbyt duża, ilustruje to poniższy listing.
[PHP]
private function getFormKeyName(sfForm $form, $first = false) { { return $this->formsKeys[$form->getOption('page')]; } $format = $form->getWidgetSchema()->getNameFormat(); if($first) { } { } else { $key = false; } return $key; }
Zmieniłem w niej warunek początkowy tak aby wartość zwracana była w jednym miejscu oraz wyodrębniłem metodę buildFormKeyName. Drugi parametr był tak zwanym parametrem znacznikowym, zmieniał on działanie metody. Istnienie takiego parametru sugeruje nam podział metody na dwie mniejsze, co zresztą uczyniłem.
[PHP]
private function getFormKeyName(sfForm $form) { $formIndex = $form->getOption('page'); { $format = $form->getWidgetSchema()->getNameFormat(); $this->formsKeys[$formIndex] = $this->buildFormKeyName($format); } return $this->formsKeys[$formIndex]; } private function buildFormKeyName($format) { return $key; } private function hasNameFormat(sfForm $form) { $format = $form->getWidgetSchema()->getNameFormat(); }
Ostatnia metoda która wymaga przebudowy to getValuesForForm. Pierwsze co rzuciło mi się w oczy to warunek w pierwszej instrukcji warunkowej - już go gdzieś widziałem... Trzymając się zasady DRY, kod z instrukcji warunkowej umieściłem w nowo utworzonej metodzie. Wyodrębniłem również osobną metodę do budowania tablicy wartości formularza z określonej strony gdy jest używana wspólna "przestrzeń nazw" (dane są przechowywane jako tablica jednowymiarowa) dla wszystkich formularzy.
Zmieniłem również specyfikatory dostępu przy składowych z chronionych na prywatne aby zwiększyć hermetyzację danych. Operacje na składowych w podklasach powinny się odbywać tylko i wyłącznie poprzez metody dostępowe. Każda metoda, która powstała na skutek przebudowy innej jest prywatna.
Komentarze
Kolejną ważną kwestią są komentarze, pisząc tą klasę starałem się ją dosyć dobrze opisać, wręcz przesadziłem z tym. Jest sporo nadmiarowości w tej kwestii, a więc kilka komentarzy należy wyrzucić. W php nie ma twardego typowania, więc dla wygody zostawię komentarze dokumentujące, które zawierają typ parametrów, składowych, wartości zwracanych, aby w IDE działało podpowiadanie składni.Komentarze dosyć szybko tracą na ważności, przykładowo metoda getPages miała komentarz "Zwraca ilość stron" (swoją drogą nadmiarowy/głupi - niepotrzebne skreślić ;P). Obecnie metoda nazywa się getNumberOfForms, więc komentarz ma się nijak do nazwy oraz wykonywanej czynności przez tą metodę. Należy go bezwłocznie wyrzucić, aby nie wprowadzał w błąd. To jest bardzo poważny minus komentarzy, dlatego powinno się je pisać tylko w sytuacjach gdy są niezbędne, a kod sam siebie niezbyt jasno komentuje. Jeśli uważamy że dany fragment kodu nie jest zbyt jasny, to przed umieszczeniem komentarza należy się zastanowić czy nie lepiej napisać kod bardziej czytelnie, tak aby komentarz był zbędny.
Ostatnią kwestią którą się zajmę to kolejność metod w pliku źródłowym. Kod źródłowy w miarę możliwości powinien dać się czytać z góry do dołu. Każda metoda prywatna powinna być bezpośrednio za metodą, która ją po raz pierwszy wywołuje - tak też uczyniłem.
Podsumowanie
Poniżej załączam kod wyjściowej oraz oczyszczonej klasy wraz z testami jednostkowymi dla tej drugiej klasy (zmieniło się lekko api więc i testy uległy zmianie). Otrzymany kod jest krótszy o kilka wierszy, głównie za sprawą usunięcia nadmiarowych komentarzy. Oczywiście jest możliwy dalszy refaktoring, kod rzadko kiedy jest idealny. Znacznie więcej na temat czystego kodu znajdziecie w książce o tym tytule, polecam.Kod źródłowy: pobierz

Komentarze (9)
#1 Anonim (www) , 18.04.2010 13:46
ksiazka jest skierowana do programistaow php? w ksiezce sa podane przyklady tylko w tym jezyku?
#2 Piotr (www) , 18.04.2010 13:50
Niestety nie, wszystkie przykłady w tej książce są napisane w Javie. Większość treści książki jest uniwarsalna (pasuje do większości języków), ale są też rozdziały stricte pod javę (np. wielowątkowość).
#3 sf (www) , 18.04.2010 15:18
@Anonim: po tym, że książka omawia pakiety, przestrzenie nazw oraz struktury danych można było się domyślić, że książka nie jest o PHP ;)
#4 Tomasz Kowalczyk (www) , 18.04.2010 16:42
Jak by się uprzeć to z wymienionych przez Ciebie rzeczy tylko pakietów nie ma w PHP, bo nie wiem, co rozumiesz, przez struktury danych - typedef struct z C? ;]
#5 Tomasz Kowalczyk (www) , 18.04.2010 22:23
Przepraszam za podwójny komentarz, ale w sumie archiwa PHAR można jakoś podciągnąć pod pojęcie pakietu [chociaż ich zastosowanie jest nieco inne], więc w sumie już wszystkie 3 rzeczy mielibyśmy w komplecie. ;]
#6 Konradzik, 19.04.2010 10:14
Brawo Tomek, wybroniłeś się przed strasznym zarzutem nieuważnego czytania :]
Ja nie ukrywam, że rzuciłem tylko okiem na artykuł i powiem tak: rozbicie na dużo kilkulinijkowych jest słuszne i piękne. Trudno się z tym podejściem nie zgadzać jeżeli człowiek widział potwory po 300+ linii. Jednak, nie należy popadać w szaleństwo. Jeżeli widzę, że ktoś wyciąga z publicznej metody 10 prywatnych i daje je pod tą publiczną metodą z sugestywnym wcięciem (mówiącym: "będziecie użyte tylko w jednej metodzie") to czuję, że nastąpiło przegięcie w drugą stronę.
Zapewne książka o której mowa uzna to za niewybaczalny grzech, ale ja lubię sobie czasem dać komentarz /*A tutaj robię to*/ i napisać kilka linii kodu które może i powinny trafić do oddzielnej metody (jednokrotnego użycia).
#7 Tomasz Kowalczyk (www) , 19.04.2010 19:01
@Konradzik: Mam nadzieję, że to nie był jakiś sarkazm z Twojej strony, po prostu skomentowałem komentarz. ;]
Co do samego artykułu, to ja też staram się, żeby kod był "samo-dokumentujący", ale nie podobają mi się metody w stylu getNumberOfPages() - są dla mnie zbyt długie i opisowe. W taki wypadku stosuję raczej konteksty, czyli jeśli klasa nazywa się CPerson i ma atrybut items, to raczej wolę metodę countItems() [bo wiemy, że te itemsy ma człowiek / osoba] niż getNumberOfItems().
#8 Zyx (www) , 19.04.2010 19:37
Tzw. samodokumentowanie to utopia sprawdzająca się tylko w rzeczach oczywistych, jak stronicowanie. Jeśli pod spodem ukryty jest skomplikowany algorytm, który operuje własnymi pojęciami, nazwa metody w stylu `addSwitchable()` tak naprawdę guzik nam powie, dopóki nie wyjaśnimy gdzieś po ludzku, czym właściwie jest to tajemnicze "switchable".
#9 Piotr (www) , 19.04.2010 20:06
@Konradzik: to co w tym artykule zaprezentowałem jest niczym przy tym co pokazywał autor tej książki (rozbicie 5-6 wierszowej metody na 3). Pisząc, że niektóre zasady z tej książki są kontrowersyjne miałem też na myśli takie przegięcia. Wiadomo, nie należy popadać ze skrajności w skrajność.
Funkcje nie dzieli się na mniejsze dla samego rozmiaru - dzieli się aby kod w jednej funkcji był na jedym poziomie abstrakcji. Przykładowo popatrz na 2 ostatnie listingi z artykułu - z której wersji tej samej metody łatwiej przeanalizować sposób tworzenia klucza dla formularza? W pierwszej gdzie algorytm tworzenia tego klucza jest wymieszany z mechanizmem "cachowania", czy w drugim gdzie dwa różne poziomy abstrakcji (cachowanie klucza oraz jego tworzenie) znajdują się w osobnych funkcjach?
@Zyx - w wielu przypadkach kod może sam siebie wystarczająco komentować, są też takie nierzadkie przypadki gdzie komentarz jest niezbędny. Chodzi głównie o to aby nie przesadzać z oczywistymi komentarzami oraz starać się nie pisać komentarzy, które tracą na ważności.