piątek, 30 stycznia 2009

ORM - o co ta walka?



Jako weteran hibernowania obiektów (w służbie od wersji 2.0) pozwolę sobie dziś na nieco prowokującego posta z lekkim dystansem do mainstreamu...:)

Pomiędzy światem relacyjnym a obiektowym istnieje oczywista niezgodność paradygmatów. Wynika z tego szereg (powiedzmy delikatnie) niezręczności - dosyć wyczerpująca lista tu: Object Relational Impedance Mismatch.

/*
 * Na marginesie dodam, że c2.com jest nieprzebraną
 * kopalnią wiedzy - co prawda ascetyczną,
 * ale pełną materiałów uzupełnionych
 * wnikliwymi przemyśleniami, polecam.
 */


Żeby wymienić te bardziej istotne "niezręczności":
- Enkapsulacja - a właściwie jej brak:) Nie ma to jak przykładowa kolekcja pozycji na zamówieniu: order.getItems() wybebeszona na wierzch aby można było ją radośnie modyfikować bez wiedzy obiektu order. Czy ktoś dziś pamięta jeszcze od dobrej praktyce zwracania Iteratora? W naszym przypadku mielibyśmy order.getItemsIterator(), który pozwala jedynie bezpiecznie przejrzeć pozycje. Natomiast jakakolwiek modyfikacja musi być wykonywana przez zamówienie, np: order.addItem(Item). Co na to daje? Ano obiekt zamówienie lepiej od nas powinien wiedzieć co z tym fantem zrobić (gdzie dodać, a może coś przeliczyć):P

Dobrze, że mapery od jakiegoś czasu wspierają prywatne pola bez getterów/setterów. Można coś w ten sposób powalczyć.

- Typy danych (grafy vs krotki) - w OO mamy wskaźniki/referencje, natomiast w świecie relacyjnym iloczyny kartezjańskie wartości. Krotki jak to krotki - nie zawsze muszą mieć sens;)

Przy okazji: czy ktoś może mi podać racjonalny powód, dla którego w JPA muszę zawsze używać słowa DISTINC w zapytaniach aby uchronić się przed produktem iloczynu? Przykładowo gdy chcę pobrać listę zamówień a wraz z nimi dociągnąć pozycje to otrzymam zduplikowane obiekty zamówień - z uwagi na kartezjan z pozycjami. Pokutuje tu niestety myślenie relacyjne. Przecież wydaję zapytanie o graf obiektów a nie o jakiś zakichany iloczyn!

- Dane/odpowiedzialność - myślenie relacyjne jest nastawione na dane, obiektowe powinno być nastawione na zachowanie.




Zatem wydawać by się mogło, że sensowne mapowanie relacyjno-obiektowe wymagać musi nie lada akrobacji umysłowych. Jednak nie koniecznie:) Zamiast rozwiązać problem (np uznając, że się nie da) udajemy radośnie, że wszystko jest ok:)

Standardowe wykorzystanie ORM jest zawieszone niejako pomiędzy dwoma światami ze swymi żałosnymi namiastkami obiektów zredukowanymi do karykaturalnych paczek danych - Anemic Model.

Nie należy oczywiście popadać w paranoję i dążyć do tego, że każdy kod musi być obiektowy. Jeżeli nie ma takiej potrzeby/możliwości to pewnie, że nie. Ale po co się wówczas wydurniać z całym tym mapowaniem, które i tak jest nieudolne? Czy zamiast sztampowej klasy Person z polem firstName nie wystarczy hehe result[35]? No dobra, to przegięcie - niech będzie resultMap.get(Fileds.FIRST_NAME)? Po co się wydurniać i oszukiwać, że mamy jakieś OO.
O co walczyć w przypadkach gdy kod (encji jak i cała reszta) i tak nie jest obiektowy lecz proceduralny?
Czy nie będzie to tylko niepotrzebny narzut pasujący jak kwiatek do kożucha - że zakończę kontrowersyjnie jak obiecałem;P

//==============================

Oprócz filozoficznych zagadnień pojawiają się również całkiem praktyczne:

- modelować począwszy od bazy czy warstwy obiektów domenowych? Ja mam swoje zdanie sprawdzone w boju: Życia nie oszukasz - jeżeli tylko jest taka możliwość zacznij od obiektów (mapery tutaj akurat robią dobrą robotę generując automagicznie bazę z klas - stosować tylko w fazie "szału tworzenia")

- W kontekście Doman Drive Design: metody biznesowe w encjach oczywiście nie są fajne, gdy chcemy przesyłać encje z/do klientów. Pytanie czy na pewno chcemy je tam przesyłać? Naiwne mapowanie (zamapowane wszystkie powiązania z bazy) pozwala dodając zamówienie dodać razem z nim gdzieś tam w niezachermetyzowanych bebechach encję nowego usera z fajnymi uprawnieniami:)
W małych systemach na pewno wygodnie jest posługiwać się encjami we wszystkich warstwach, ale w większych jednak oldskulowe DTO pozwoli ukryć strukturę modelu biznesowego.

niedziela, 11 stycznia 2009

Dystans

W życiu trzeba mieć dystans - to podstawa. W programowaniu też - szczególnie do tego co mówi klient:)

Dalsze me dywagacje pierwej podeprę takim oto małym przykładzikiem z frontu (projektu w jakim do niedawna jeszcze brałem udział):

Emulując analityka radośnie przeprowadzałem analizę małego modułu do zarządzania drukami ścisłego zarachowania. Z analizy biznesowej wynikało jakoby:

W jednym dziale panie pracują z DPDANPB_1 (Drukiem Potrzebnym Do Arcy Nudnego Procesu Biznesowego 1); druki przychodzą z drukarni do działu w paczkach po parę tysięcy sztuk; użytkownicy dzielą paczki na bloczki po kilkadziesiąt sztuk.

W innym dziale panie pracują z DPDANPB_2, które to przychodzą z drukarni również w paczkach - tym razem po kilkaset sztuk. Użytkownicy w tym dziale nie dzielą sobie druczków na bloczki - po prostu radośnie wykonują swą jakże smutną pracę używając druczków DPDANPB_2.

W obu przypadkach system musi ogólnie rzecz ujmując nadzorować druczki (zliczać, kontrolować kto komu ile po co wydał itp) z uwagi na to iż są one jak już wspomniałem "ściśle zarachowane" - cóż za małostkowość swoją szosą;)


Zatem na tak zwany rzut tak zwanego oka widać, że mamy do czynienia z taką oto strukturą (wybaczcie ASCII art, ale w niedzielny poranek nie chce mi się odpalać kombajnu - do UMLa):

Paczka1 ------* Bloczek1 -------* DPDANPB_1
Paczka2 ------* DPDANPB_2

Wszystko jasne i oczywiste, odpalamy Eclipse, tworzymy 5 klasek, adnotujemy @Entity, radośnie klepiemy atrybuty i gotowe.

Za parę miesięcy ktoś będzie się martwić o zmiany bo na przykład: nowa kierowniczka działu 2 nie będzie chciała być gorsza i wprowadzi u siebie również dwustopniowy podział. Następnego dnia kierowniczka działu 1 chcąc pozostać lepszą (od tej ździry z dwójki) wprowadzi trójstopniowy podział. Albo - o zgrozo - będąc w fazie cyklu gdy w jej ustroju dominują męskie hormony wprowadzi 8 stopniowy podział - BO TAK!!!

Możemy oczywiście założyć, że nie zależy nam na przygotowaniu giętkiego systemu otwartego na nowe wymagania, np: atak kosmitów lub co gorsza nierokendrolowe dni kierowniczek. Nie chcemy wydawać kasy na dobry projekt, odwalamy chałę, wdrażamy metodykę RÓB! Takie założenie biznesowe jest w sumie fajne, bo zwalnia mózg developerów od myślenia a co za tym idzie chroni go przed śmiertelnym przegrzaniem - hehe krótkowzroczni biedacy nie widzą, że do czasu...

Jak się jednak okazało (zresztą nie tylko w tym przypadku), że chwila zastanowienia nic nie kosztuje i jest inwestycją pozwalającą zaoszczędzić niemałe pieniądze już w niedalekiej przyszłości.

Wystarczy się nieco wnikliwej przyjrzeć zachowaniu klienta - dokonać rzetelnej analizy. Użytkownicy z działu 1 wiedzeni wrodzonym instynktem niemal intuicyjnie podsuwają nam rozwiązanie: stary dobry model "plików w katalogu".

Uogólniając mamy po prostu jakieś dokumenty pogrupowane w drzewiaste struktury katalogów: katalog zawiera inne katalogi oraz dokumenty:

Katalog -------* Dokument
 |      *
 |----|

Jeszcze 2 kwestie z perspektywy projektanta OO:

W razie potrzeby konkretne druczki (DPDANPB_1, DPDANPB_2,...) mogą dziedziczyć po dokumencie jeżeli chcemy dodać im specyficzną odpowiedzialność biznesową - anemicznemu modelowi mówimy stanowcze NIE. Dziedziczenie jest akurat w tym wypadku usprawiedliwione ponieważ dokumenty będą traktowane polimorficzne na przykład w logice nadzorowania - zatem jesteśmy zgodni z zasadą podstawienia Liskov - możesz używać dziedziczenia tylko wówczas gdy spodziewasz się polimorfizmu.
A co do technicznych aspektów persystencji naszych dokumentów polecam artykuł Dawida Walczaka: JPA - modelowanie dziedziczenia.


Co natomiast ze szczególnymi typami katalogów (węzłów drzewa)? Tu akurat dziedziczenie niezbyt mi się podoba - nie wiem dlaczego.
Zamiast dziedziczenia zalecałbym przede wszystkim podpinanie pod nie strategii.
Czyli nasz katalog mógłby mieć np metodę validate(), która to deleguje proces walidacji do konkretnego walidatora - oczywiście stojącego za interfejsem strategii.

Oczywiście w razie gdy pewien rodzaj katalogu ma specyficzną odpowiedzialność, której nie mają inne to nie możemy pozwolić na "brudzenie" klasy katalogu jakąś ezoteryczną metodą. Drugim (nie wykluczającym rozwiązaniem) pozwalającym na "dodanie" nowych odpowiedzialności do klasy Katalogu jest wzorzec wizytatora. Czyli w tym przypadku katalog miałby metodę pozwolSieZwintestygowac(Visitor v).

//=============================
To był bardzo szczególny przykład - natomiast ogólna idea jest taka: klient zazwyczaj mówi do nas obiektami (instancjami) biznesowymi. Rola analizy jest między innymi taka aby odkryć klasy tych obiektów. W naszym przykładzie klient posługiwał się pojęciami paczka1, paczka2, bloczek. Naiwna analiza przekłada je na klasy. Natomiast w rzeczywistości są to pewne szczególne obiekty klasy Katalog.

W sumie to nie bez powodu w UML istnieje diagram obiektów.


Jeszcze bardziej ogólną analizę problemów analizy przedstawił Michał Bartyzel w swym poście "Metaprogramy w tworzeniu oprogramowania" (wytłuszczenia są moje):

[Klienci] Opisują to jednak w postaci procesów, natomiast analityk widzi system jako strukturę. Podążając za procesem łatwiej dokonywać w nim zmian. Z kolei zmiany wprowadzane w strukturze wymagają przeanalizowania całej koncepcji od nowa. Podczas zbierania wymagań klient często zastanawia się nad pożądanym sposobem pracy. Próbuje różnych alternatywnych przebiegów tego samego procesu i oczekuje od analityka pomocy w wyborze najlepszego rozwiązania. Jeśli analityk nie podąża w procesie za klientem, lecz percypuje system jako strukturę, to ma nie lada kłopot. Alternatywne ścieżki procesu klienta zazwyczaj mają istotny wpływ na domniemaną strukturę systemu i/lub implementację.


Polecam lekturę całego posta - jest arcy ciekawy, bo zanurzony w kontekście Metaprogramów myślenia, o których wspomniałem już 2 krotnie:
- The Unforgiven
- Twoja rola w zespole

Polecam też oczywiście całego bloga Michała Bartyzela - jest zanurzony w różnych innych ciekawych kontekstach.

czwartek, 25 grudnia 2008

Bo najważniejsza jest odpowiedzialność Synu...


...najbardziej trafną metaforą obiektów programistycznych jest organizm biologiczny. Podobnie jak komórki, obiekty "nie wiedzą", co dzieje się wewnątrz innych obiektów, ale komunikują się z nimi realizując bardziej złożone cele. W przeciwieństwie do takiego organizmu program monolityczny przypomina mechanizm zegarka, zawierający nieprzeliczalną (sic!) liczbę trybików. Trybiki nie mają żadnej inteligencji i są bezużyteczne poza mechanizmem, w którym pracują. Taki projekt nie ma szans powodzenia. Budując mechanizmy zegarowe, dochodzisz w końcu do takiej złożoności, że całość przestaje funkcjonować.


Heh czyli powiedzenie "działa jak w zegarku" nie jest komplementem lecz inwektywą:P

Cytat pochodzi z książki Projektowanie obiektowe - Role, odpowiedzialność i współpraca autorstwa Rebeki Wirfs-Brock i Alana McKean, którą to chciałbym w niniejszym poście zarekomendować.

/*
A na marginesie przypomnę, że Rebeka jest autorką ciekawej prezentacji krytykującej podejście do projektu polegające na rozpoczynaniu od zaprojektowania bazy danych - życia nie oszukasz.
*/

Na wstępie uprzedzam iż książka jest mocno abstrakcyjna a miejscami wręcz filozoficzna - raczej nie przypadnie do gustu tym, który idą z duchem czasu - czasu napier....

Książka Rebeki i Alana wprowadza nas w świat Responsibilty-Driven Design. Świat, w którym posłujemy się bytami o ściśle określonych rolach. Nie ma w nim miejsca na radosną twórczość, boskie klasy o wszechmocnej odpowiedzialności i tony spaghetti kodu. RDD z założenia ma na celu uporządkowanie chaosu i przygotowanie się na przyszłą i nieuniknioną ewolucję systemu.

W jaki sposób? O tym właśnie traktuje cała książka. Natomiast w skrócie sposób zasadza się na świadomym i konsekwentnym przypisywaniu tytułowych ról, odpowiedzialności i współpracy.

Czym jest współpraca i odpowiedzialność - myślę, że wszyscy intuicyjnie wiemy. Zasady GRASP są jasne w tych kwestiach:
- współpraca - utrzymujmy jak najmniej powiązań (Low coupling)
- odpowiedzialność - im niej tym lepiej (najlepiej niech klasa ma jedną odpowiedzialność) - w skrócie: dążymy do wysokiej kohezji klasy.


Natomiast tytułowe role to nic innego jak pewne zakresy odpowiedzialności. W RDD nie bawimy się już radośnie definiując na chybił trafił klasy a w nich metody (najlepiej statyczne hehe) typu załatwZprawę() albo zróbDobrze(). Nie, w ten sposób daleko nie zajdziemy - co najwyżej do wersji 1.0. W RDD myślimy nie na poziomie klasek ale na poziomie bytów pewnych typów zajmujących się ściśle pewnymi odpowiedzialnościami.

W RDD wyróżniamy zestaw głównych stereotypów ról, które mogą być grane przez nasze obiekty:
  • magazyn informacji (information holder) - przechowuje i dostarcza informacje
  • łącznik (structurer) - utrzymuje relację pomiędzy obiektami oraz informacje o tych relacjach
  • dostawca usług (service provider) - wykonuje zadania oraz, w ogólności, oferuje usługi obliczeniowe
  • koordynator (coordinator) - obsługuje zdarzenia przez przekazywanie zadań do innych obiektów
  • kontroler (controller) - podejmuje decyzje i bezpośrednio kieruje zadaniami innych obiektów
  • transformator (interfacer) - przekształca informacje i żądania pomiędzy różnymi częściami systemu
Role, odpowiedzialności i współpraca są głównym tematem książki lecz nie jedynym. Właściwie to są osią wokół której pojawiają się konteksty inny problemów projektowych, takich jak: style architektoniczne, elastyczność, analiza obiektowa, style obsługi błędów...

Można oczywiście zadać sobie pytanie: co mnie obchodzi jakiś RDD? To pewnie kolejna ściema dzięki której ktoś miał temat na książkę, kilka prezentacji w fajnych hotelach, temat na blogaska i oczywiście... źródło łatwych dochodów;P

Wg mnie warto jednak zastanowić się nad paroma koncepcjami zawartymi w RDD ponieważ wydają się one być uogólnieniem Domain Driven Design. W DDD mamy building blocks (o których już pisałem) służące do modelowania dziedziny: encje, VO, agregaty, polityki, servisy, repo i fabryki.

RDD unosi problem standardowych artefaktów na nieco wyższy poziom abstrakcji. Wspomniane stereotypy ról pokrywają building block z DDD. Ale stereotypy ról nie ograniczają się jedynie do klocków służących do modelowania dziedziny. Standardowe role pokrywają cały system - nie tylko warstwę biznesową.

//======================

A co by było gdyby język wspierał role na poziomie składni;)))? Gdyby zamiast public class TaxService{} deklarować public service Tax{} albo z innej beczki: public coordinatror EventManager{}. Wówczas może kompilator mógłby jakoś magicznie sprawdzać czy klasa nie robi tego co do niej nie należy i chronić zbyt cwanego programistę przed stworzeniem Potwora Spaghetti:)

Eeee... chyba wczorajsza dawka strawy z nadmierną ilością ryb i niedostatkiem wieprza, do której mój prasłowiański żołądek nie jest przystosowany, skutkuje dzisiejszym majaczeniem;)
Przecież na początek wystarczyłyby adnotacje w jakimś nowym, pięknym frameworku:)

niedziela, 14 grudnia 2008

UP-DDD in Action: Hermetyczne agregaty



Drogie dzieci - w dzisiejszym odcinku z serii UP-DDD in Action zobaczycie jak nasz bohater Bob Budowniczy rozprawia się z brakiem hermetyzacji i proceduralną ignorancją.



Kontynuując temat enkapsulacji rozpoczęty w poprzednim poście zobaczmy jak wygląda on w kontekście Domain Driven Design.

DDD opiera się na starych i sprawdzonych technikach obiektowych więc proceduralne łańcuszki typu "train wreck" zbesztane w poprzednim poście nie mają prawa pojawić się w kodzie aspirującym do miana DDD.

Analizując specyfikacje building blocks z DDD widać, że Encje, VO i Agregaty posiadające odpowiedzialność biznesową (nie będące standardowym anemicznym modelem) są w zgodzie z paradygmatem Abstrakcji.

Dodatkowe założenie odnośnie Agregatów jest takie, że hermetyzują one swą strukturę. Oznacza to, że nie powinny zdradzać szczegółów swych składowych. Agregat posiada root (główną encję), która publikuje niejako zestaw odpowiedzialności/zdolności w celu ochrony wnętrzności przed światem zewnętrznym. Jeżeli świat zewnętrzny nie wie nic o wnętrznościach agregatu, to wówczas implementacja agregatu może z czasem się zmieniać (aby nadążać za nowymi wymaganiami). Dzięki temu zmiany są lokalne i nie mają wpływu na świat zewnętrzny.

Weźmy na ten przykład arcy interesujący model zamówienia;) Niech zamówienie posiada datę, miejsce dostawy oraz listę zamówionych produktów. Zamówienie będące agregatem nie może na przykład udostępniać gettera do listy produktów ponieważ istnieje możliwość modyfikacji tejże listy "poza świadomością" samego zamówienia.
Zakładając iż tworzymy obiektowy model biznesu i jego reguł to chcielibyśmy aby każda operacja na produktach była wykonywana poprzez samo zamówienie, które jest swego rodzaju mózgiem biznesowym.

Obiekt zamówienia posiadając wiedzę o swych wnętrznościach może na ten przykład domówić dodanie do niego pewnego produktu jeżeli miejsce dostawy zawiera się w pewnej strefie, klient nie jest pełnoletni, albo data realizacji wykracza poza jakąś tam granicę pewnej akcji marketingowej.

W tym akurat przykładzie jakimś rozwiązaniem byłoby upublicznienie iteratora po iście zamówień. Ale załóżmy przypadek bardziej ogólny - nie chcemy zdradzać żadnych szczegółów i kropka.

No dobra, wystarczy tego teoretycznego bełkotu! W praktyce niestety często istnieje konieczność dobrania się do wnętrzności obiektu. Na przykład po to aby wyeksportować zamówienie do PDF lub XML. Że o takim szczególe jak jego wyświetlenie na GUI nie wspomnę;P

Podchodząc do zamówienia jak do struktury danych potrzebowalibyśmy procedur, które odpowiadają za przejrzenie wnętrzności zamówienia i stworzenie odpowiednio PDFa lub XMLa prezentującego dane o zamówieniu i jego wewnętrznej strukturze (GUI to problem osobny - wręcz filozoficzny, więc o nim osobny akapit później).

Na szczęście istnieje wzorzec projektowy Budowniczego. Idea wzorca jest prosta jak nie przymierzając budowa cepa. Zakłada ona istnienie dyrektora budowy i robotnika (budowniczego). Dyrektor wie CO trzeba zrobić a robotnik wie JAK to zrobić.



Na powyższym przykładzie klasa Order jest dyrektorem budowy. Gdy ktoś poprosi dyrektora o wyeksportowanie zamówienie wówczas on wydaje rozkazy delegując pracę do budowniczego. Dyrektor (zamówienie) wie skąd pobrać dane do eksportu - z własnych prywatnych wnętrzności. Natomiast robotnik (budowniczy) jest abstrakcją w najczystszej formie - interfejsem. Konkretny budowniczy wie jak zająć się surowcami przekazywanymi mu przez dyrektora budowy. Na przykład budowniczy PDFów zna doskonale jakiś silnik do generowania dokumentów. Budowniczy XMLi zna format w jakim należy zbudować plik tekstowy. Budowniczy DTO wie jak należy skonstruować obiekt transferowy.

Mamy tutaj doskonałe rozdzielenie odpowiedzialności: hermetycznych struktur biznesowych i wiedzy o ich semantyce od technicznych aspektów formatów danych eksportowych.

Powyższy przykład można rozszerzyć o mały aspekt optymalizacyjny. Bo co jeżeli dany budowniczy nie jest z jakiegoś powodu zainteresowany na przykład listą zamówień? Najprościej byłoby aby jego implementacja metody buildNextProduct() po prostu ignorowała przekazywane produkty. Niestety mamy wówczas niepotrzebną iterację. Zamiast tego możemy rozszerzyć interfejs budowniczego o metodę isProductListNeeded(). Iterujemy po liście jedynie wówczas gdy metoda zwróci wartość true.



Wspomniałem wcześniej o aspekcie GUI. Popularne frameworki zasadzają się na refleksji i wymagają by istniały gettery do składowych obiektu, które chcemy wyświetlić. Heh jednym ze sposobów na wyświetlenie zamówienie w JSF mogłoby być stworzenie takiej implementacji budowniczego, która tworzy jakiś UIComponent który to następnie dodajemy do widoku - niestety jak dla mnie jest to nakładanie gaci przez głowę.

Jakimś rozwiązaniem jest zastosowanie Data Transfer Objects. Czyli nasz agregat biznesowy może eksportować się do DTO, które to DTO jest bindowane z widokiem. DTO ma już gettery i settery więc doskonale pasuje do frameworków.

Rozwiązanie to ma oczywiście plusy dodatnie i ujemne.

Przede wszystkim dzięki przepakowaniu obiektów biznesowych do głupich DTO nie udostępniamy w warstwie UI metod biznesowych (pamiętajmy, że Agregaty je posiadają). Serwisy aplikacyjne posługujące się DTO nadają się wówczas do bycia Web Serwisami - nie chcemy przecież upubliczniać naszego modelu biznesowego oraz jego metod (odpowiedzialności). DTO mogą zawierać jedynie istotne z punktu widzenia usługi dane, prezentować je w spłaszczony (prostszy) sposób oraz stanowić warstwę interfejsu do systemu pod którą możemy swobodnie zmieniać nasz model bez obawy o ciągłe modyfikacje klientów.

W małych projektach zmniejsza to oczywiście w znaczny sposób produktywność (rapid developnent hehehe). Zamiast spokojnie stukać sobie przeglądarkę do bazy musimy nakładać gacie przez głowę i przepakowywać dane bawiąc się samemu ze sobą w kotka i myszkę:/


//====================

Swoją drogą to dziwne, że frameworki nie wspierają dobrych modeli programowania a skupiają się jedynie na przykryciu technikaliów takich jak na przykład HTTP, transakcje, rozproszenie obiektów, bindowanie danych warstwą abstrakcji...

Nawet frameowrki, które nazywają się dumnie frameworkami aplikacji nie wspierają żadnego modelu aplikacji a hermetyzują jedynie niskopoziomowe aspekty techniczne.

W którymś z następnych odcinków zobaczymy jak wygląda to w http://www.qi4j.org/

poniedziałek, 8 grudnia 2008

Flaczki (Kod obiektowy - zawsze smaczny i zdrowy)



Zacznijmy od oczywistych oczywistości...
Programowanie Obiektowe zasadza się na 4 paradygmatach:
  • Abstrakcja
  • Enkapsulacja
  • Polimorfizm
  • Dziedziczenie
O złym jak Charles Manson dziedziczeniu w ogóle i złudnym dziedziczeniu wielokrotnym już pisałem:
- Telefonistka dziedziczy po telefonie
- Strategia na wielokrotne dziedziczenie
teraz kolej na enkapsulację i abstrakcję.

Czy tego typu kod jest obiektowy?
czlowiek.getOtrzewna().getZoladek().getTresc().add(new Kielbasa(2));
Na pierwszy rzut oka tak. Mamy piękne obiekty złożone we wspaniałą strukturę. Niestety analizując nawet niezbyt dogłębnie definicje 4 paradygmatów wymienionych jako oczywiste oczywistości możemy o tym kodzie powiedzieć, że co najwyżej używa obiektów ale obiektowy nie jest. Ba, można nawet pokusić się o stwierdzenie, że żadnych obiektów nie używa.

Przykładowy kod jest niezgodny z paradygmatem Enkapsulacji ponieważ literalnie wypruwa flaki z biednego człowieka. Obiekt człowiek ujawnia swą wewnętrzną strukturę oraz poniekąd implementację. Taki obiekt jest kompletnie nieodporny na zmiany ponieważ na ten przykład próba uszczegółowienia anatomii w wersji 2.0 i dodania nowych narządów spowoduje katastrofę. Enkapsulacja nie polega na bawieniu się samemu ze sobą w kotka i myszkę: prywatne pola ale za to publiczne gettery - po co się w ogóle tak wydurniać?

Przykładowy kod jest niezgodny z paradygmatem Abstrakcji ponieważ literalnie człowiek nie ma nic do gadania w temacie karmienia go kiełbasą. Co jeżeli dana instancja człowieka jest już syta albo nie lubi podwawelskiej?

Przykładowy kod jest to tak zwany "train wreck" - kod charakterystyczny dla struktur danych w językach proceduralnych na których to strukturach operują zewnętrzne procedury. Owszem może było to dobre jeszcze w szalonych latach '70 ale problem polega na braku hermetyzacji.

W odpowiedzi na bolączki proceduralnego kodu powstała właśnie koncepcja OO. Koncepcja, która sugeruje aby naszego przykładowego człowieka wyposażyć w odpowiedzialność lub możliwość pożywiania się:
czlowek.zjedz(pokarm) - wewnętrzna implementacja człowieka zdecyduje co z tym zrobić. W szczególności instancja człowieka może uznać, że sugerowany pokarm należy natychmiast wypluć
Możemy pójść jeszcze dalej i wyposażyć człowieka w umiejętność brania: czlowiek.wez(pokarm) - instancja człowieka może uznać za stosowne schowanie go do kieszeni, plecaka, lub w nawet poddanie obróbce przed spożyciem.

Ten sam styl rozumowania aplikuje się do obiektów agregowanych w ramach człowieka. Nie wystarczy zadeklarować metodę czlowek.zjedz(pokarm) a w niej radośnie posługiwać się łańcuszkiem
this.getOtrzewna().getZoladek().getTresc().add(pokarm);
Kontynuując obrany tok rozumowania należny metodę czlowek.zjedz(pokarm) zaimplementować mniej więcej tak: this.ukladPokarmowy.przyjmij(pokarm);
i tak dalej...
A taki układ pokarmowy lub pojedynczy narząd może być całkiem za darmo reużyty dla innych ssaków:)

Z punktu widzenia kodu wchodzącego w interakcję z obiektem człowiek nie jest istotne co instancja człowieka zrobi ani jak to zrobi. Dopóki człowiek enkapsuluje to pod stabilnym interfejsem kod jest "otwarty na rozbudowę niczym kwiat lotosu o świcie i zamknięty na modyfikacje niczym kwiat lotosu o zmierzchu" (czy jakoś tak;)

//=====================

Train Wrecks są dosyć popularne lub wręcz lansowane przez standardowe frameworki i architektury. Począwszy od encji hibernate przez encyjne EJB po ich łatwe bindowanie z kontrolkami w warstwie prezentacji. Może jednak coś w tym jest;)

W następnym odcinku zobaczymy co na to Domain Driven Design.

sobota, 22 listopada 2008

Do it head first


Nie jest dobrze. Ba, jest źle. Regres cywilizacji zaczął się od prymitywnych form eskalacji przemocy:

Aby wreszcie zaatakować ostatni i jednocześnie najcenniejszy bastion dorobku cywilizacji białego człowieka - programowanie obiektowe:

Na szczęście w ramach lubelskiego JUGa powstała kontrofensywa. Jest jeszcze szansa...

Jak niesie plotka - dżawarze z krainy pszenicy i buraka jednoczą się aby zachować przed zapomnieniem i zdeptaniem kunszt programistyczny. Ich sztandarem i relikwią jednocześnie jest mityczna koszulka z napisem "Java developers do it head first", której jeden egzemplarz (jak głosi legenda: czarna, rozmiar XL) podobno zachował się jeszcze gdzieś na świecie...

//=========
Bez odbioru...

wtorek, 18 listopada 2008

UP-DDD in Action: Planowanie jest nieodzowne, Sir!

Prezydent Dwight Eisenhower tak oto wspominał czasy gdy był generałem armii amerykańskiej:
"In preparing for battle I have always found that plans are useless, but planning is indispensable."

Dziś kolejny post z serii Unified Process & Domain Driven Design in Action. Prace nad projektem wciąż w fazie analizy biznesowej więc post będzie raczej wypełniaczem (podtrzymującym serię) o charakterze "filozoficznym".

Jaką rolę w projekcie spełnia UML (dokumentacja w ogólności)? Są różne podejścia...

Może na przykład nie mieć żadnej roli. Organizacja może radośnie postępować według metodyki chałupniczo-garażowej. Metodyka ta zakłada 3-etapowy proces:

radosna twórczość - po podpisaniu kontraktu następuje pięciominutowa odprawa sześciopaku programistów po czym rzucają się oni na klawiaturę (jak nie przymierzając marynarz atomowego okrętu podwodnego na kozę w porcie) i klepanie na wyścigi. Nikt nie wiec po co, na co i jak, ale to nie szkodzi... byle się wyżyć.

kosy racławickie - wiadomo... burdel rośnie, projekt się sypie, co raz jakaś eskalacja. Najlepszym rozwiązaniem jest pospolite ruszenie: nadgodziny + "ochotnicy" do pomocy. Jak wiemy z doświadczenia jest to oczywisty antywzorzec zarządzania ponieważ nowi ludzie jedynie spowalniają pracę, ale kto by się nad tym zastanawiał - przecież każdy brakujący diagram UML można zastąpić skończoną liczbą studentów...

stawanie na kancie tego no... nosa! - tydzień do release. Ludzie funkcjonują w trybie zombie. Każdy brakujący diagram UML można zastąpić skończoną ilością sznurka do snopowiązałki...

Jeżeli opisana metodyka chałupniczo-garażowa sprawdza się wyśmienicie - na co wskazuje na przykład sam fakt istnienia organizacji (że o systematycznym zwiększaniu zysków nie wspomnę) to oczywiście jak najbardziej jest słuszna i nie powinno się jej zmienić bo jeszcze kruchy stan równowagi się posypie. Biznes to biznes - doskonale to rozumiem.

Na drodze logicznego rozumowania można z czasem uświadomić sobie, że jednak wydanie paru groszy na dokumentację zwróci się wielokrotnie w przyszłości. Metodyka chałupniczo-garażowa owszem przynosi świetne dochody, ale okazuje się, że mogłaby przynosić jeszcze większe gdyby nie tracić czasu na zmaganie się z burdelem i po prostu udokumentować wszystko. Pycha pomysł!

I tu pojawia się kolejna możliwa rola dokumentacji: kula u nogi. Po radosnym zaimplementowaniu trzeba jeszcze zrobić te cholerne rysuneczki i posiedzieć nad wordem. A tu nad głową stoi poganiacz i zadaje najgłupsze pytanie jakie istnieje: "na kiedy będzie gotowy następny moduł?". Na szczęście wspaniałe narzędzia mają funkcję wstecznej inżynierii - dwa kliki i zestaw pięknych prostokątów gotowy. Teraz jeszcze tylko wkleić to jakoś do worda, dokleić jakiś schemat bazy i z dyni. Hmmm nie tak szybko... za mało tej dokumentacji... ale wystarczy wkleić jeszcze trochę kodu źródłowego i bedzie. No dobra, nie odwalajmy chały - tylko same nagłówki klas i metod. Jak wciąż mało to można dokleić trochę reguł biznesowych z jakiś roolsów. Ktoś kazał robić dokumentację to się zrobi - "będzie Pan zadowolony"

Istnieją różne dewiacje w tym podejściu. Można na przykład analizą nazwać projekty ekranów - co prowadzi do ciekawej paranoi. Innym skrzywieniem jest traktowanie schematu bazy danych jako projektu systemu - również ciekawa paranoja.

W sumie to dokumentacja tworzona po fakcie ma jednak swoją wartość - oczywiście pod warunkiem, że ktoś ją aktualizuje:P



A gdyby tak potraktować diagramy UML jako narzędzie do eksperymentowania? Nie chodzi o sam fakt posiadania dokumentacji, ale proces jej tworzenia. Wysiłek umysłowy włożony w zastanowienie się o co w ogóle chodzi. Skupienie się na tym CO a nie JAK. Eksperymentowania, które polega na najpierw na notowaniu wyników analizy biznesowej. Później poddaniu ich wnikliwej i rzetelnej analizie systemowej, czyli odfiltrowaniu chaosu wprowadzone przez ekspertów biznesowych i wyciągnięciu z tego czystego ekstraktu. W wyniku otrzymamy diagramy Use Caseów i Obiektów Biznesowych - stąd już blisko do warstw logiki aplikacji i biznesowej. I nie chodzi bynajmniej o to aby jedynie przepisać bełkot klienta na jajeczka z ludzikami i cieszyć się, że mamy diagramiki Use Case. Tworząc diagramy warto się zastanowić nad każdą kreską i jajeczkiem - czy dany element ma sens, czy nie da się czegoś uogólnić i wyłonić reużywalnej abstrakcji, czy nie da się tego zrobić prościej - właśnie o to upraszczanie chodzi. Upraszczanie na etapie "eksperymentowania" w UML zwróci się na etapie kodowania.

Analogiczne eksperymenty w UML można przeprowadzać na etapie projektowania. Zanim zacznie się kodować warto przez chwilę popatrzeć z większej wysokości na całość bo można przypadkiem zauważyć uogólnienie prowadzące całkiem za darmo do reużywalności. Można też całkiem przypadkiem znaleźć miejsca dla jakiegoś wzorca projektowego i całkiem za darmo zapewnić w przyszłości większą giętkość systemu i jego otwartość na rozbudowę.

Z samego planowania może wyjść więcej dobrego niż z posiadania gotowych (wygenerowanych) planów...
No i jak by nie było - efektem ubocznym tworzenia dokumentacji jest jej posiadanie:)



//=================
UML sam w sobie jest po prostu jakimś tam pismem obrazkowym. Może ma nieco lepiej zdefiniowane reguły niż pismo jaskiniowców, ale wciąż - pismem obrazkowym;P

Sztuka stosowania UML nie polega na skrupulatnym rysowaniu prostokątów od ekierki i następnie ich kolorowaniu tak aby nie wyjść kredką poza kontur.

Nie chodzi o samą formę ale o proces myślowy, który zaszedł np. podczas obiektowej analizy domeny biznesowej. Prostokąty to tylko notatka z walki ze złożonością i chaosem.