niedziela, 19 październik 2008

Python – pierwsze starcie

"Języki typowane dynamiczne - wolność w popełnianiu błędów"


Jestem znany z przywiązania do języków typowanych statycznie, jednak żeby się nie ograniczać postanowiłem poszukać czegoś dla siebie wśród języków z typowaniem dynamicznym*. Ostatecznie przekonało mnie do tego nowe podejście firmy Microsoft, która postanowiła zainwestować trochę kasy w integrację języków dynamicznych z platformą .NET. O innych względach przemawiających za językami dynamicznymi pisałem już w „Czy języki dynamiczne wyprą języki typowane statycznie?” (http://nandrew.blogspot.com/2007/10/czy-jzyki-dynamiczne-wypr-jzyki.html).


* - pozwolę sobie tutaj zauważyć, że język dynamiczny != język z typowaniem dynamicznym. Nie każdy zdaje sobie z tego sprawę ;)


Wbrew pozorom wybór języka okazał się dość prosty. Postawiłem przed nim niezbyt wygórowane wymagania:

a) normalna, czytelna składnia;

b) duża społeczność (eliminacja niszowych rozwiązań);

c) duża ilość stabilnych bibliotek (żadnych nowości);

d) opinia o języku;


Takie sito wyeliminowało: D (b,c), Groovy (b, c, d), Lisp (a), Perl (a), PHP (a, d), Ruby (a, c), Tcl (a, d).

And the winner is: Python.


Zaraz przejdę do subiektywnego opisu wad i zalet tego języka, najpierw jednak mała dygresja. Pamiętam jak X lat temu Java zdobywała popularność. Jednym z częstszych argumentów wysuwanych przeciw niej była szybkość programów pisanych w tym języku. Zresztą, to był także jeden z powodów, dla których pozostałem przy C++. Teraz jednak, gdy języki dynamiczne zdobywają popularność nagle okazuje się, że nikomu ta szybkość nie jest potrzebna. A przecież taki PHP czy Ruby jest wolniejszy od Javy o rząd wielkości. Jest wiele opinii o przyczynie tego stanu rzeczy, jednak na razie nie będę o tym pisał...


Zalety języka Python

a) ciekawe, czytelna i szybka składnia; bardzo duża społeczność użytkowników; bardzo dużo dojrzałych bibliotek i framework'ów; bardzo dobra opinia o języku (to tyle jeżeli chodzi o „sito”).

b) szybkość w porównaniu do PHP, Ruby, Groovy (Python jest częściowo kompilowany do bytecodu);

c) sporo typów wbudowanych i bardzo duże możliwości manipulowania nimi;

d) IronPython – czyli port języka na platformę .NET. Bardzo duży plus, ponieważ... to nie wymaga nawet uzasadnienia :)


Wady

Trochę tego uzbierałem w trakcie nauki języka.

a) Brak stałych

Bardzo dziwna sytuacja, gdy można bez problemu nadpisać wartość PI (math.pi = 4)! Coś takiego może rozłożyć program na łopatki, a szukanie takiego bug'a to horror, bo kto by pomyślał, że któryś z zaimportowanych modułów nadpisał PI (czyt. jakąś wartość uznawaną za stałą).

Na upartego można opakować stałe w metody typu getXXX(), jednak jest to jawne spowalnianie programu oraz burzenie przyzwyczajeń programistycznych.


b) Niezbyt czytelne elementy składni

Jak już pisałem, składnia Pythona powszechnie jest uważana za „ładną”, jednak zawiera parę elementów, które ją psują:

- prywatne pola klasy oznaczane przez „__” (double underscore) – no jakby jedna twarda spacja nie wystarczyła...;

- parametr self w metodach klas – w większości języków jest on ukryty, co ZMNIEJSZA konieczność bezsensownego klepania w klawiaturę. Tu jednak go postawili na piedestale, po co, dlaczego?! Nie wiem. Gruba przesada.

- tworzenie obiektów ma składnię taką samą jak wywołanie metody – przez co na pierwszy rzut oka nie widać o co chodzi. Dziwne, że pożałowali „self”, a „new” było dla twórców języka zbyt explicit :-\ Brak konsekwencji...


c) Wolny

Nie mówię o szybkości działania odziedziczonej ze „skryptowości” języka, lecz o tym, że przez wbudowane konstrukcje bardzo ułatwione jest wykorzystywanie operacji „czaso i zasobo chłonnych”. Bardzo częste jest wykorzystanie tupli (tworzonych w dużych ilościach), operacji na listach typy zip(), itp. Nawet kompilacja do exe nie pomoże przy nieoptymalnym kodzie. Oczywiście mając na uwadze wydajność przy programowaniu można temu zaradzić.


d) Słaby private

Jak już pisałem składniki prywatne oznacza się jako „__”. Jednak jest to bardzo słaba ochrona i rzeczywiste odwołanie się do takich pół nie stanowi większego problemu (ale lepsze to niż nic).


e) Brak modyfikatora typu protected dla metod

Zastosowanie tego modyfikatora przy skomplikowanych modelach obiektowych jest bardzo częste. Aż dziwne, że zabrakło go w Pythonie (w sumie może nie jest to aż tak dziwne, skoro ledwo private zaimplementowano).


f) Brak klas i metod abstrakcyjnych

Jest to kolejna wada i to dość duża odnosząca się do sposobu implementacji programowania obiektowego. Pod znakiem zapytania staje możliwość zastosowania niektórych wzorców projektowych. Szybkie przeszukiwanie google dało podpowiedź jak zaimplementować klasy abstrakcyjne, jednak wymaga to pisania około 100 linii kodu...

Ergo: ten język chyba nie służy do tworzenia skomplikowanych modeli obiektowych (biorąc pod uwagę punkty d, e, f);


g) Brak bloku try/except/finally

Jest to dziwne, ponieważ są konstrukcje try/except oraz try/finally, jednak try/except/finally jest niedozwolone. Trochę ogranicza to programistę.
Od wersji 2.5 blok try/except/finally jest zaimplementowany. Ach te 3 letnie książki... ("Beginning Python - From Novice to Professional", cyt. "Note that you cannot have both except clauses and a finally clause in the same try statement").


h) Brak porządnego IDE

Dobre środowisko jest nieodzowne przy profesjonalnych zastosowaniach (wspomaganie debuggingu, refactoringu (!), tworzenie GUI graficznie (a nie tekstowo) itp.). Zdaje się, że Python takiego nie posiada. Wprawdzie jest PyDev, jednak wiele mu brakuje do ideału (czyt. Visual Studio), np. IntelliSense działa tylko częściowo (bo zmienne nie maja typów, ale jest to raczej spowodowane cechą języka).

ALE jeżeli weźmiemy pod uwagę IronPython to sytuacja się trochę polepsza, bo VS będzie go wspomagał w przyszłości tak jak każdy inny standardowy język na platformę .NET.


i) Mała popularność wśród firm hostingowych

Język ten ciągle nie jest tak popularny jak PHP jako platforma dla aplikacji web. Przez to trudniej znaleźć hosting komercyjny, o darmowym nie wspominając (nie ma nic sensownego, gdzie by można pobawić się z językiem).

W prawdzie jest darmowy „Google App Engine”, jednak narzuca on wiele ograniczeń i udziwnień, które powodują, że aplikacje nie są przenośne – żaden inny hosting tego nie obsługuje.


Summary

Ze wszystkich języków dynamicznych najlepszy dla mnie okazał się Python. Ma on parę zalet i sporo unikalnych wad. Dopiero próba czasu (czyt. jakiś porządny projekt testowy) pokaże czy da się go użyć w sensownej aplikacji biznesowej (czyli w obszarze moich zainteresowań).


Testowy projekt oparty będzie na Django (czyt. dzengo). Framework webowy wybierałem o wiele dłużej niż język. Jednak tego wyboru nawet nie będę się starał uzasadniać...

piątek, 25 lipiec 2008

Podsumowanie postów o zasadach projektowych

"Software is not limited by physics, like buildings are. It is limited by imagination, by design, by organization. In short, it is limited by properties of people, not by properties of the world. 'We have met the enemy, and he is us' "

-Ralph Johnson


Ten post jest podsumowaniem poprzednich tekstów o zasadach projektowych, czyli mini przewodnik. Cała seria odpowiada na pytanie "co zrobić z językiem UML oraz narzędziami CASE" :)

Podstawy
Naukę projektowania oprogramowania na pewno warto rozpocząć od książki „Applying UML and Patterns”. Opisuje ona wzorce GRASP, nie są to wzorce w powszechnym słowa tego znaczeniu, a raczej wskazówki czy pomoce projektowe. Pomagają one opanować sztukę projektowania obiektowego.

Wzorce GRASP opisałem w postach: Zasady projektowania obiektowego GRASP – część I i Zasady projektowania obiektowego GRASP – część II.

Techniki średnio-zaawansowane
Następny level to zasady projektowania obiektowego zebrane przez C. Martin'a w książce „Agile Principles, Patterns, and Practices in C#”. Opisane w niej zasady są podstawą udanych systemów oraz szczęśliwych projektów informatycznych :) Dlaczego szczęśliwych? Bo stosowanie tych zasad pozwala na wczesnym etapie programowania stworzyć kod, który w przyszłości będzie można dostosować do wymagań, które początkowo nie były znane. W ekstremalnych przypadkach stosowanie tych zasad pozwala w ogóle skończyć projekt ;)

Cała seria tych zasad została przeze mnie omówiona w poście Zasady projektowania obiektowego – part1 oraz Zasady projektowania obiektowego – part2.

Finish
Ostatni post "Pozostałe zasady projektowania obiektowego" omawia zasady spoza wszelkich uporządkowanych zbiorów - mix mający zwieńczyć serię postów o zasadach projektowych. Takich "luźnych" zasad na pewno znalazło by się więcej (chodźby "Separation of Concerns", "Information Hiding", itd) i może kiedyś...

Co dalej
Po poznaniu zasad projektowych można przejść do wzorców projektowych oraz wzorców aplikacyjnych. IMHO znajomość zasad GRASP i Martin'a pozwala lepiej zrozumieć, a co za tym idzie poprawniej stosować, pozostałe wzorce programistyczne.

Wzorców projektowych można szukać w "najsławniejszej" książce czyli "Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku", GoF.
Natomiast wzorce aplikacyjne zebrane zostały w książce "Architektura systemów zarządzania przedsiębiorstwem. Wzorce projektowe". Bez znajomości tej ostatniej pozycji nie ma co podchodzić do pisania systemów biznesowych (dlaczego tak sądzę opisałem w treści posta o PoEAA).

środa, 23 lipiec 2008

Pozostałe zasady projektowania obiektowego

Zasady projektowe opisane przeze mnie w poprzednich postach były dość uporządkowanymi zbiorami, które można znaleźć w książkach do OOA/D. Istnieją też inne zasady (lub bardziej wskazówki czy strategie) które można do nich - tak jakby - dokleić. Mimo że nie tworzą całości z wzorcami GRASP czy Martina też są znane i często można znaleźć odwołania do nich.


Tym razem poniższe zestawienie zawiera w większości zasady intuicyjne, proste, ale o dziwo mające swoje nazwy. Ktoś mógłby się więc spytać, po co je opisywać ;) Pomijając moją chęć dokończenia kolekcji zasad projektowych, to


Low Representational Gap (LRG)

Treść: Nadawaj elementom modelu obiektowego nazwy inspirowane przez dziedzinę problemową, którą model opisuje.

Opis: Jest to jedna z ważniejszych wskazówek, którą można się posłużyć przy projektowaniu obiektowym, a w sumie przy projektowaniu oprogramowania w ogóle. Trzymanie się tej wskazówki powoduje, że model jest bardziej zrozumiały i łatwiejszy do ogarnięcia. Idea jest banalna i generalnie wszyscy o tym wiedzą, ponieważ wszystkie książki do programowania milcząco trzymają się tej zasady. Jednak mogą istnieć sytuację wątpliwe, w których odwołanie się do zasady LRG może uzasadnić konkretną decyzję projektową.

Wykorzystywana głównie w modelowaniu domenowych (Domain Model), tworzona semantyka przejdzie do programu (klas) poprzez modelowanie projektowe (Design Model), które przecież jest inspirowane bezpośrednio przez model domenowy.

Oczywiście trzymanie się LRG nie jest zawsze dobrym wyborem. Trzeba także zwracać uwagę na inne zasady np. GRASP (zwłaszcza Information Expert).

Przykład: najprostszy przykład – nawet nieświadomego – stosowania LRG to przy modelowaniu np. systemu ERP stworzenie klasy Materiał i nadanie mu atrybutów Nazwa, Indeks, Jednostka, itp.

Innym przykładem (z książki Larmana) jest sytuacja, gdy mamy klasę Register oraz Sale (sprzedaż produktów) oraz dylemat „kto tworzy obiekty klasy Sale”? Według LRG powinna być to klasa Register, ponieważ w świecie rzeczywistym sprzedaż rozpoczyna się od kasy.


Do It Myself

Treść: Obiekt programowy wykonuje te rzeczy, które w rzeczywistości wykonywane są na rzeczywistym obiekcie, którego dany obiekt programowy jest abstrakcją.

Opis: Jest to zasada, czy strategia, opisana przez Petera Coada, ale podobnie jak LRG jest to zasada intuicyjna. Jednocześnie jest to praktycznie synonim wzorca Information Expert (GRASP). Larman nazywa to także zasadą ożywienia (ang. animation principle).

W świecie programowania obiekty „ożywają” i przyjmują na siebie różne odpowiedzialności i działania.

Przykład: W rzeczywistym świecie człowiek obsługuje kasę sklepową (Register), jednak przy modelowaniu obiektowym to kasa sama przyjmuje na siebie odpowiedzialność tworzenia rachunku, obliczania bieżącej sumy, itp. Podobnie koła, trójkąty, czy inne figury geometryczne nigdy w świecie rzeczywistym się same nie rysują, jednak w klasach Kolo, Trójkąt tworzy się metodę Rysuj() właśnie w myśl zasady Do It Myself.

Wracając do systemu ERP, normalnie stan materiałów zna „magazyn” (czy dokumentacja). Ale korzystając z Do It Myself można przypisać znajomość stanu materiału samej klasie Materiał.


Law of Demeter (LoD) (aka Don't Talk to Strangers; or Principle of Least Knowledge)

Treść: Niech obiekt rozmawia tylko z bliskimi współpracownikami.

Opis: To już jest mniej znana zasada, lecz bardzo ważna. Chodzi w niej o to, żeby obiekt nie komunikował się (wywoływał metod, pobierał wartości pól) z obiektów w dalekiej odległości w grafie powiązań między obiektami. Oznacza to, że obiekt powinien mieć minimalną wiedzę o innych obiektach, ograniczoną do minimum. Unika się dzięki temu „kruchych” połączeń, co ogranicza wpływ miejscowych zmian w projekcie na resztę projektu. W szczególności komunikacji obiektu powinna być ograniczona do:

  • niego samego;

  • parametrów wywołanej metody tego obiektu;

  • innych obiektów stworzonych wewnątrz wywołanej metody;

  • do obiektów będących atrybutami obiektu (zmiennych klasowych) oraz elementów kolekcji;

LoD to przypadek szczegółowy zasady Protected Variations (GRASP).

Przykład: bardzo dobitny przykład to następujące odwołanie w metodzie klasy Order:

Unit u = this.GetProduct().GetLastOperation().GetMainMaterial().GetUnit().”.

Stworzyła się w ten sposób zależność między klasą Order, a wszystkimi (!) klasami pośrednimi, aż do klasy Unit włącznie (4 klasy). Lepszym rozwiązaniem byłoby wprowadzenie takiej metody, aby móc pobrać jednostkę od „współpracownika”:

Unit u = this.GetProduct().GetMainMaterialUnit();” :)

Efekt ten sam, a powiązań o 50% mniej.


First Law of Distributed Object Design

Treść: „Don't distribute your objects!” [W systemie rozproszonym nie rozpraszaj obiektów]

Opis: Jest to zasada – jakby to powiedzieć – z innej beczki niż poprzednie ;) Dotyczy projektowania systemów rozproszonych i pochodzi z książki PoEAA (została wymyślona przez Fowlera ;)

Celem tej zasady jest uświadomienie, że umieszczanie obiektów intensywnie współpracujących ze sobą na różnych serwerach – wbrew pozorom - nie ma sensu. To tylko spowalnia system. Oczywiście ta zasada jest sprzeczna z reklamami większości systemów umożliwiających rozpraszanie obiektów :) Używanie np. technologii CORBA między wszystkimi obiektami w systemie i umieszczanie ich na różnych maszynach jest złym podejściem.

Poprawnym rozwiązaniem jest klastrowanie - wszystkich obiekty powinny być "uruchomione" razem. W celu zwiększenia wydajności na innych maszynach można uruchomić kopię całości. (Aby to poprawnie działało oczywiście potrzebne są dodatkowe mechanizmy np sterowania obciążeniem).

sobota, 12 lipiec 2008

Zasady projektowania obiektowego – part2

Skoro opisałem poprzednio podstawowe zasady projektowania obiektowego (zebrane w książce „Agile Principles, Patterns, and Practices in C#”), to wypadałoby opisać pozostałe ;) Kolejne sześć dotyczą głównie podziału aplikacji na komponenty (czy pakiety, jak kto woli).


Jeszcze zanim przejdę do meritum mała uwaga: przy stosowaniu tych zasad nie ma znaczenia język programowania, czy framework. Mimo iż opisane zostały w książce, w której kod źródłowy był w języku C# to ich pochodzenie (lata '80, '90) jednoznacznie wskazuje, że język C# nie jest z nimi związany. Tak więc czy C#, Java, czy PHP (5) – zasady dają się zastosować, a ich ignorowanie spowoduje te same problemy nie zależnie od języka ;)


Reuse/Release Equivalence Principle (REP)

Treść: Jednostka ponownego użycia jest równoważna jednostce wydania*. (ang: The granule of reuse is the granule of release).

Opis: Zasada ta ma bardziej „polityczny” charakter niż programistyczny. Chodzi w niej o zapewnienie, że ponownie użyty kod będzie utrzymywany (konserwowany) przez autora dla klientów. Jeżeli ktoś korzysta z biblioteki, to oczekuje, że znalezione bug'i będą naprawiane czy dodawane będą takie rozszerzenia, które nie powodują problemów z kompatybilnością. Implikuje to przypuszczenie że można oczekiwać, że starsze wersje będą także utrzymywane, mimo pojawienia się nowych.

Oczekiwania te wymuszają na autorach bibliotek/komponentów:

  • nadawania numerów wersji do wydań komponentów;

  • gromadzenie w jednej bibliotece komponentów używanych razem;

  • pozwolenie użytkownikom na wykorzystanie starszych wersji;

(takie szybkie podsumowanie zrobiłem ;)


* - moje tłumaczenie jest może nie najlepsze, dlatego uwagi do niego są mile widziane ;)


Common Reuse Principle (CRP)

Treść: Przy ponownym użyciu (reuse) komponentu, wszystkie klasy są wykorzystywane razem.

Opis: Celem tej zasady jest zidentyfikowanie klas, które powinny być umieszczone w jednym pakiecie/komponencie. Klasy, które ściśle współdziałają ze sobą, muszą być umieszczone razem w komponencie. Takie klasy powinny być nierozłączne. Ale klasy, które nie są ze sobą powiązane, nie powinny być umieszczane razem.

Trzymanie się tej zasady zmniejsza ilość testów, które trzeba wykonać na klasach klienckich po nowym wydaniu komponentu (wersji).


Common Closure Principle (CCP)

Treść: Komponent nie powinien mieć wielu powodów do zmian. Inaczej: klasy, które zmieniają się razem, powinny być razem.

Opis: Jest to odpowiednik zasady SRP w odniesieniu do pakietów/komponentów. Klasy, które zazwyczaj zmieniają się z tego samego powodu razem, powinny być umieszczone razem w jednym komponencie. Dzięki temu przy ewentualnych zmianach liczba komponentów, które będzie trzeba wydać (release) i testować będzie ograniczona.

Oczywiście „powody do zmian” nie są już na tym samym poziomie co w zasadzie SRP, tylko na bardziej ogólnym.


Acyclic Dependencies Principle (ADP)

Treść: unikaj wszelkich cykli w grafie zależności komponentu.

Opis: Jest to dość oczywista zasada, czasami samo środowisko wymusza ją, gdy nie może sobie poradzić z linkowaniem. Niestosowanie się do tej zasady (gdy jest to możliwe w ogóle) prowadzi do bardzo dużych problemów ze złożeniem programu, który składa się z wielu modułów. Jest to znane pod określeniem „morning-after syndrome”.

Jak rozpoznać wystąpienie ADP? Po prostu moduł pośrednio zależy sam od siebie, przy czym ilość klas „pomiędzy” (w danej gałęzi grafu) nie ma znaczenia.

Tip: rozwiązywanie ADP możliwe jest na parę sposobów, między innymi: zastosowanie zasady DIP, zastosowanie zasady CRP lub zwykła reorganizacja pakietów.


Stable-Dependencies Principle (SDP)

Treść: Twórz zależności w kierunku pakietów stabilnych.

Opis: Zasada ta mówi, że aby pakiet mógł być łatwo rozszerzony/zmieniony, to nie powinien zależeć od niego żaden pakiet stabilny – który bardzo trudno ulega zmianom. W takiej sytuacji wszelkie zmiany musiałyby uwzględniać ten stabilny pakiet, czyli stary interfejs. A to powoduje, że zmiany byłyby trudne do wprowadzenia. Dlatego zawsze powinno się tworzyć zależności do pakietów stabilnych, które z małym prawdopodobieństwem ulegną zmianom.


Stable-Abstractions Principle (SAP)

Treść: Stabilny komponent powinien dążyć do abstrakcyjności.

Opis: Według tej zasady istnieje zależność między stabilnością a abstrakcyjnością. Mianowicie stabilny komponent powinien także być abstrakcyjny, aby jego stabilność pozwalała mu na rozszerzenia/modyfikacje. Abstrakcyjność oznacza składanie się interfejsu z klas abstrakcyjnych. Ale komponent niestabilny powinien składać się z klas konkretnych.

wtorek, 8 lipiec 2008

Zasady projektowania obiektowego – part1

Dawno temu opisałem zasady projektowania klas według wzorców GRASP, opisujących sposoby przydzielania odpowiedzialności do klas. Teraz czas na bardziej zaawansowane zasady projektowania obiektowego – zebrane i opisane w książce „Agile Principles, Patterns, and Practices in C#” (dostępne jest już wydanie polskie).
Ze względów praktycznych opis podzielony jest na dwa posty.

Single-Responsibility Principle (SRP)
Treść: Klasa powinna mieć tylko jeden powód do zmian.
Opis: Zasada ta jest silnie powiązana z zasadą skupienia (high cohesion) ze wzorów GRASP. Treść może wydać się trochę myląca, ale generalnie chodzi o to, żeby klasa miała tylko jedną odpowiedzialność (resposibility), ponieważ odpowiedzialność jest potencjalnym źródłem zmian. Jeżeli klasa ma więcej niż jedną odpowiedzialność to zmiana jednej z nich będzie wpływać na implementację reszty - a jest to potencjalnym źródłem błędów.
Przykład: prostym przykładem złamania tej zasady jest klasa, która jednocześnie zawiera logikę (np. obliczanie powierzchni figury) oraz kod związany z grafiką (np. odpowiedzialny za rysowanie tej figury). Trochę lepszym przykładem złamania zasady SRP jest klasa zawierająca jednocześnie kod wybierający dane z bazy oraz logikę odpowiedzialną za np. obliczanie rabatów (typowy i częsty przykład pomieszania logiki biznesowej z mapowaniem O/RM).

Open/Closed Principle (OCP)
Treść: Elementy oprogramowania powinny być otwarte na rozszerzenia, ale zamknięte na modyfikacje.
Opis: Podstawą dla tej zasady jest spostrzeżenie, że każdy kod (klasa, moduł, itp.) zmienia się z czasem i ma więcej niż jedną wersję. Dobre stosowanie tej zasady powoduje, że wprowadzenie rozszerzeń w jednej klasie nie spowoduje całej kaskady zmian w innych klasach.
Trzeba zatem tworząc kod od razu mieć na uwadze możliwość przyszłych rozszerzeń. Trzeba przewidzieć możliwość tworzenia rozszerzeń i zmiany zachowania kodu, ale nie powinno się to odbywać poprzez zmianę już istniejącego kodu.
Można powiedzieć, że OCP to główna zasada projektowania obiektowego, która pozwala spełniać jego slogany reklamowe (flexibility, reusability, maintainability).
Przykład: stosowanie się do tej zasady zazwyczaj oznacza bazowanie na abstrakcji (klasy abstrakcyjne) i stosowanie polimorfizmu (oczywiście nie tylko do tego). Dobrym przykładem złamania tej zasady jest tworzenie logiki zawierającej instrukcje „switch..case” rozróżniającej typy obiektów.

Liskov Substitution Principle (LSP)
Treść: [W hierarchii dziedziczenia] podtypy muszą być „podstawialne” za typy bazowe.
Opis: Zasada ta umożliwia wykrycie źle stworzonej hierarchii dziedziczenia, w szczególności źle zaimplementowaną klasę pochodną. Chodzi o to, żeby wszystkie podtypy zachowywały się tak jak zakłada się, że zachowuje się typ bazowy. Zazwyczaj domyślnie się zakłada, że za typ bazowy można podstawić klasę pochodną i zachowanie będzie zmodyfikowane, ale będzie poprawne z punktu widzenia „klientów” klasy bazowej.
Zasada ta jest nawet silniejsza niż podstawowa zasada dziedziczenia „is-a” (jest).
Przykład: „bardzo” jawnym złamaniem LSP byłoby stworzenie kontrolki dziedziczącej po np. ComboBox i zmiana zachowania metody Hide() na odrysowanie zamiast ukrycie kontrolki. W takim wypadku program, który by działał poprawie z ComboBox'em miałby problem, ponieważ zakładałby, że po wywołaniu Hide() kontrolka nie jest widoczna i np. można na jej miejscu pokazać coś innego.
To jest oczywiście bardzo naiwny przykład, ale moim zdaniem pozwala zrozumieć meritum ;)
Uwaga: w tym przykładzie LSP nie byłoby [według mnie] złamane, jeżeli w dokumentacji do klasy ComboBox byłoby stwierdzenie, że w klasach pochodnych metoda Hide() może czasami nie ukrywać kontrolki. Wtedy już przewidywania co do działania klasy bazowej byłyby zupełnie inne.

Dependency-Inversion Principle (DIP)
Treść: Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Oba powinny zależeć od abstrakcji. Abstrakcje nie powinny zależeć od detali. Detale powinny zależeć od abstrakcji.
Opis: Główną ideą tej zasady jest wymuszenie, aby moduły czy klasy nie zależały od bardziej niskopoziomowych elementów, a raczej, aby niskopoziomowe klasy czy moduły bazowały na klasach abstrakcyjnych, czy wyższych modułach. Dzięki temu niskopoziomowe zmiany nie będą miały wpływu na wyższy poziom aplikacji. Zastosowanie tej zasady najważniejsze jest przy podziale aplikacji na warstwy, ale także powinno się ją stosować przy projektowaniu klas.
Przykład: Rozważmy przykład aplikacji obliczającej średnie zarobki w firmie. Jeżeli w GUI umieszczony jest przycisk, do którego przypisany jest kod, który z grida na formie pobiera liczby i oblicza średnią to jest to [bardzo częsty] przykład złamania DIP. W tym przypadki logika biznesowa (czyli coś co jest zawsze na najwyższym poziomie) zależy bezpośrednio od niskopoziomowego framework'a GUI (pobieranie danych z grid'a). Aby zasada nie była złamana powinno być tak, że warstwa BLL (logiki biznesowej) w ogóle nie „świadoma” istnienia GUI.
Załóżmy teraz, że powyższa aplikacja została poprawiona i średnia obliczana jest w warstwie BLL. Odbywa się to poprzez wywołanie polecenia SQL.... no właśnie i znowu [bardzo częsty] przykład złamania DIP. Logika biznesowa (czyli najwyższa warstwa) nigdy nie powinna polegać na niskopoziomowej bibliotece dostępu do danych (ani ADO.NET, ani NHibernate)! Zależność powinna być odwrócona tak, żeby BLL w żadnym stopniu nie był świadomy źródła danych, a warstwa DAL polegała na abstrakcji zdefiniowanej w BLL.

Interface Segregation Principle (ISP)
Treść: klient nie powinien być zmuszony do zależności od metod, których nie używa.
Opis: jest to chyba najrzadziej łamana zasada ze wszystkich przedstawionych w tym poście. Postuluje ona, aby interfejsy były jak najbardziej skupione (jest to podobne do zasady high cohesion dla klas) na jednym celu, a tym samym nie były „przeładowane” metodami. Jeżeli klasa implementuje interfejs, który ma bardzo wiele metod to możliwe jest, że nie wszystkie będzie wykorzystywać, a co gorsze będzie musiała, co spowoduje złamanie SRP.
Przykład: najprostszym złamaniem ISP byłoby umieszczenie w jednym interfejsie metod służących do operacji graficznych (np.: Draw(), Rotate(), Move()) oraz odpowiedzialnych za trwałość (np.: Save(), Load()).

W tym poście tylko przybliżyłem pierwsze 5 z 11 zasad projektowania obiektowego. Jednak opis siłą rzeczy (a raczej formy) jest dość zwięzły. Dokładniejsze omówienie oraz sposoby „walki” z łamaniem zasad znajdują się w wymienionej przeze mnie książce.

Tak na zakończenie wspomnę tylko, że te zasady w większości pochodzą z końca lat '80 XX wieku – i ciągle są aktualne ;)

czwartek, 29 maj 2008

Rozwiązanie błędu "Named parameter does not appear in Query" w NH

Podczas tworzenia systemu wykorzystującego NHibernate często można napotkać na różne problemy typu „no przecież powinno działać”. Jednym z nich jest użycie procedury w „Named Query” - mimo wykonania wszystkiego zgodnie z dokumentacją można dostać wyjątek „Named parameter does not appear in Query".

Przykład

Rozważmy Named Query zdefiniowane w pliku HBM:



<sql-query name="GetOrderX">
<return-scalar column="Amount" type="Int32"/>
<![CDATA[
BEGIN
declare @param_productId char(32) set @param_productId = :productId;
....
</sql-query>

W ostatniej linii pokazanego fragmentu procedury SQL (nieskładowanej) następuje przypisanie parametru zewnętrznego („:productId”) do parametru wewnętrznego dla procedury „@param_productId”.
Wykorzystanie takiego nazwanego zapytania odbywa się poprzez kod:

IList<Object[]> list = session.GetNamedQuery("GetOrderX")
.SetParameter("productId", productId)
.List<Object[]>();

Jednak przy tak zdefiniowanym zapytaniu – mimo iż wydaje się poprawne – zgłoszony będzie wyjątek "Named parameter does not appear in Query".


Problem jest spowodowany przez sposób w jaki NH parsuje kod SQL w poszukiwaniu parametrów. Wykrywa stringi rozpoczęte od „:” do białego znaku... no właśnie znak „;” (umieszczony po nazwie parametru) nie jest białym znakiem i dla NH jest częścią nazwy parametru.


Rozwiązanie

Aby wyeliminować błąd musimy dodać biały znak za nazwą parametru, w tym przypadku najlepiej spację:


declare @param_productId char(32) set @param_productId = :productId ;
/\

Konkluzja

Jak dla mnie jest to bug w NH, ponieważ średnik także powinien być traktowany jako możliwy znak zakończenia nazwy zmiennej.

piątek, 2 maj 2008

Konkatenacja wartości w wierszach w SQL Server 2005

Dzisiejszy post dotyczy dość specyficznego zagadnienia, mianowicie konkatenacji wartości w wierszach, będących wynikiem zapytania. Takie „coś” jest raczej rzadko przydatne, jeżeli z bazą współpracuje aplikacja, ponieważ ta może sobie sama przetworzyć otrzymane dane, ale jeżeli wynik zapytania jest podstawa do tworzenia raportu to problem konkatenacji wierszy może wystąpić.


Problem

Dla jasności napiszę dokładnie o co chodzi. Przypuśćmy, że mamy resultset (tabele lub zapytanie typu select), które wygląda następująco:


KATEGORIA | ILOSC
-----------------
„Kat A” | 12
„Kat A” | 20
„Kat A” | 33
„Kat B” | 15
„Kat B” | 30

A chcemy otrzymać „połączenie” wartości w kolumnie (wierszach):


KATEGORIA | LISTA
-----------------
„Kat A” | 12; 20; 33;
„Kat B” | 15; 30;

Kod SQL

W dbms MS SQL Server 2005 można wykonać następujące zapytanie:


SELECT DISTINCT z.Kategoria, ( SELECT z2.Ilosc + '; '
FROM Zestawienie z2
WHERE z2.Id = z.Id
FOR XML PATH('') ) AS Lista
FROM Zestawienie z;

Korzysta ono z funkcjonalności XPath więc niestety na innych dbms'ach to nie zadziała (może są odpowiedniki?).


Dalsze informacje

Rozwiązanie to znalazłem na stronie: http://www.projectdmx.com/tsql/rowconcatenate.aspx .

Jest tam wiele różnych sposobów na uzyskanie efektu konkatenacji wartości wierszy, ale ten wydał mi się najszybszy do zastosowania.