26 lis 2008

Agile OOD w skrócie

Dziś trochę o jakości kodu. Oczywiście jasne jest, że powinna być jak najwyższa. Tylko nie jest już tak jasne jak to osiągnąć. Zwinne techniki, głównie pochodzące z XP zalecają TDD jako mechanizm wspierający jakość kodu. I faktycznie tak rozwijany kod ma dużo większe szanse na powodzenie (poprawność, utrzymywalność, itp) m.in. ze względu na jego refaktoryzację, a więc wielokrotne myślenie o tym samym kodzie (często przez wiele osób przy programowaniu w parach i współwłasności kodu) i poprawianie jego jakości. Tylko skąd wiedzieć, że refaktoryzacja przez nas stosowana faktycznie poprawia jakość kodu? Jak przewidzieć czy kod będzie dzięki temu bardziej utrzymywalny, łatwiejszy do zrozumienia, elastyczniejszy w użyciu? No więc tu przychodzi nam z pomocą parę podstawowych zasad strukturyzowania kodu obiektowego. No to po kolei:

Zasada pojedynczej odpowiedzialności (single responsibility principle):
Każda klasa powinna mieć tylko jedną odpowiedzialność. Wszystkie usługi które udostępnia powinny być z nią ściśle związane. Czasem mówi się, że to oznacza, że powinien być dokładnie jeden powód do zmiany klasy. Na przykład jeśli mamy klasę zarządzającą użytkownikami, która definiuje ich uprawnienia i dokonuje odpowiednich wpisów w bazie danych, to są przynajmniej dwa powody do jej zmiany (dwie odpowiedzialności): struktura użytkownika i mechanizm komunikacji z bazą danych. W takim przypadku powinniśmy mieć oddzielne klasy: regulującą uprawnienia i realizującą komunikację z bazą danych.

Zasada otwartości-zamknięcia (open-closed principle):
Klasy powinny być otwarte na rozszerzenia i zamknięte na modyfikacje. Z tym związane są podstawowe zasady obiektowości: hermetyzacja - to dzięki niej możemy zamknąć klasę na modyfikacje (metody prywatne) - oraz dziedziczenie, dzięki któremu możemy klasę rozszerzyć.

Zasada podstawiania Liskov:
Klasy dziedziczące muszą móc być wykorzystywane tak jak ich klasa bazowa. Z tego wynika, że klasy dziedziczące nie powinny zmieniać kontraktu metod przy ich przeładowywaniu (redefiniowaniu w klasie dziedziczącej). To może spowodować, że klient klasy bazowej nieświadomy tego jaką jej implementację dostaje może czynić jakieś założenia co do jej funkcjonowania (i się rozczarować...) Jak tego uniknąć mówi kolejna zasada:

Design by Contract:
Warunki wstępne (pre-conditions) dla przeładowywanych metod w podklasie (czyli założenia co do wartości ich parametrów) być takie same albo słabsze niż dla metody która jest przeładowywana w nadklasie. Odwrotnie dla warunków wyjściowych (post-conditions - wynik metody, wyjątki) - te powinny być takie same bądź mocniejsze/węższe niż dla tej metody w nadklasie. Dzięki temu metody podklasy wykorzystywane przez klienta jako metody nadklasy (przez polimorfizm) zawsze będą spełniać kontrakt ich oryginałów w klasie bazowej, nie spowodują więc nieoczekiwanych efektów po stronie klienta.
Zasada odwrócenia zależności:
Tam gdzie to tylko możliwe bądź zależny od abstrakcji a nie konkretów. Czyli jeśli chcesz mieć w klasie pole typu lista, to nie deklaruj LinkedList, ArrayList ani nic takiego, tylko po prostu List. To ułatwi ci refaktoryzację jeśli okaże się, że trzeba skorzystać z innej implementacji.

Zasada segregacji interfejsów:
Klasa powinna udostępniać drobnoziarniste interfejsy dostosowane do potrzeb jej klienta. Czyli, że klienci nie powinni mieć dostępu do metod których nie używają. Ta zasada powoduje, że kod klienta twojej klasy będzie bardziej czytelny. Nie koniecznie należy wiązać te interfejsy z tym co oznacza interface w javie (z resztą zasada jest ogólna, a nie tylko dla javy, więc dotyczy również języków w których nie ma interface'ów). Często dużo lepszym interfejsem specyficznym dla klienta jest klasa dziedzicząca po tej którą chcesz udostępnić i definiująca odpowiednie metody lub składająca parę metod swojej bazowej w jedną. W językach które udostępniają poza dziedziczeniem kompozycję (np. scala poprzez mechanizm trait) można też wykorzystać ten mechanizm do składania klas z elementów specyficznych dla klienta (w ten sposób odwracając ten mechanizm - nie udostępniamy różnych interfejsów dla tej samej klasy, ale składamy tę klasę z interfejsów (i oczywiście implementacji) - trait'y w scali są do tego idealne).

Prawo Demeter:
Mówi ono, że metoda ma prawo wołać metody należące do:
- tej samej klasy
- obiektów będących polami własnej klasy
- własnych parametrów
- obiektów które tworzy
ale
- nie wołaj metod bezpośrednio na obiektach, które otrzymałeś w wyniku innego wywołania
- nie wołaj metod globalnych obiektów
Choć takie podejście ułatwia późniejszą refaktoryzację, czasem jego złamanie jest niezbędne. Poza tym przestrzeganie tej zasady powoduje utrzymywanie wysokiej spójności klas (i minimalizacji zależności).

Mów, ale nie pytaj (Tell, don't ask):
To dodatkowa zasada z tej samej grupy co Prawo Demeter. Mówi ona, że jest jak najbardziej ok żeby pobierać stan obiektu (wartość jego pól), ale nie wolno wykonywać żadnych funkcji związanych z tym obiektem (tym stanem) poza tym obiektem. Wszystkie decyzje dotyczące obiektu bazujące wyłącznie na jego stanie powinny być podejmowane wewnątrz niego samego.


Takich zasad można stworzyć pewnie więcej. Całe tony tych i podobnych (wraz ze szczegółowymi wyjaśnieniami i odniesieniami do literatury) spisane są na wiki Warda Cunninghamma (http://c2.com/cgi/wiki) które polecam do czytania.
Teraz już poprawianie jakości kodu powinno być dla Ciebie trywialne... :)

25 lis 2008

50 in 50

W tym roku na konferencji JAOO w Århus w Danii miałem okazję być na dość niezwykłykłej prezentacji w wykonaniu Guy'a L. Steele i Richarda P. Gabriela znanych (?) ekspertów od języków programowania. Obaj przez lata zajmowali się Lispem, Steele stworzył Scheme, brał udział w specyfikacji Javy, Common Lispa, C i jeszcze pewnie paru innych języków (aktualnie prowadzi zespół tworzący język Fortress - następcę Fortrana.) Prezentacja 50 in 50 dotyczy właśnie tego teamtu - języków programowania. Panowie w niesamowitej happeningowo-postmodernistycznej prezentacji przeszli przez 50 lat programowania, 50 języków od mainstreamowych (Java, C) po najbardziej ezoteryczne (befunge, shakespeare). Prezentacja miała trwać oczywiście 50 min, ale troszeczkę się im wyciągnęła, co akurat biorąc pod uwagę temat (przynajmniej dla mnie) jest czymś dobrym :) Na zakończenie Panowie oddali hołd wszystkim chyba zmarłym naukowcom zajmującym się językami programowania. Całość była naprawdę zaskakująca - czasem śmieszna (kod w języku shakespeare w formie filmu, piosenka God wrote in Lisp code, czy rapujący Guy Steele), czasem pobudzająca do refleksji (panteon ludzi którzy swoją pracą i zaangażowaniem stworzyli świat w którym sie poruszamy zawodowo) i przez cały czas ściśle techniczna. Choć videocast to nie to samo co obecność na miejscu, polecam spędzić najbliższą godzinkę na oglądaniu: