poniedziałek, 11 listopada 2013

Classic Books on Software Development - TOP 10 of must read

Często ktoś mnie pyta, jakie książki o programowaniu bym polecił na poziom wyższy niż klepanie kodu. Dlatego właśnie stworzyłem niniejszą listę. Jest to kompilacja podobnych enumeracji z Internetu, ale głównie opiera się na własnym doświadczeniu.

Poniższe książki to klasyka gatunku - przedstawiają one tak zwany Core*, czyli to, co jest stabilną częścią wiedzy w Inżynierii Oprogramowania. Uważam, że najlepsza jakościowo wiedza zgromadzona jest właśnie w tych książkach i powinni je poznać wszyscy, którzy interesują się swoim rzemiosłem. Nie chodzi o to, żeby ślepo stosować opisane praktyki - nie taka jest idea. Moim zdaniem są one podstawą do rozwijania własnego doświadczenia w dobrym kierunku.
(* - artykuł "Software Engineering Principles", From the Editor, IEEE Software, Vol. 16, No. 2, March/April 1999)

Classic Books on Software Development - TOP 10 of must read
(czyli najlepsze książki dla programistów o programowaniu ogólnie pojętym - w proponowanej kolejność czytania)

1) The Pragmatic Programmer: From Journeyman to Master - Hunt Andrew, Thomas David
(pl: Pragmatyczny programista)

2) Code Complete: A Practical Handbook of Software Construction - McConnell Steve
(pl: Kod doskonały)

3) Applying UML and Patterns - Craig Larman
(to nie jest książka o UML ani wzorcach! to książka o tym jak wykorzystać programowanie obiektowe, o design w OOD. Słowa UML i Patterns zostały wykorzystane jako haczyki marketingowe. Poza tym ta książka to podstawa.)

4) Agile Software Development, Principles, Patterns, and Practices - Robert C. Martin
(albo) Agile Principles, Patterns, and Practices in C# - Robert C. Martin
(pl: Zwinne wytwarzanie oprogramowania. Najlepsze zasady, wzorce i praktyki)

5) Head First Design Patterns, Eric Freeman

6) The Art of Unit Testing, Roy Osherove

7) Rapid Development: Taming Wild Software Schedules - Steve McConnell

8) Peopleware: Productive Projects and Teams - Tom DeMarco, Timothy Lister

9) Refactoring: Improving the Design of Existing Code - Kent Beck

10) The Mythical Man-Month: Essays on Software Engineering - Frederick P. Brooks
(pl: Mityczny osobomiesiąc)

Kiedyś twierdziłem, że przeczytanie jednej książki jest jak zdobycie doświadczenia z udziału w jednym projekcie. Teraz bym powiedział, że może nawet jak dwa do pięciu "przebytych" projektów. 

Za podium, ale warto wspomnieć:

11) Clean Architecture, Robert C. Martin (Czysta architektura)

12) Clean Code, Robert C. Martin (Czysty kod)

Disclaimer: niektóre z tych książek (głównie PoEAA) dotyczą mainstreamowych systemów. Programiści systemów embedded dużo z nich nie wyniosą.

niedziela, 19 października 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 lipca 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 lipca 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 lipca 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 lipca 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 ;)

piątek, 26 października 2007

Jak zawalić projekt - link do artykułów

Niedawno w pociągu przeczytałem sobie wydrukowany artykuł "Jak zawalić projekt informatyczny. Analiza wymagań" ze strony Mareka Rafałowicza - dot. prowadzenia projektów informatycznych. I w sumie artykuł uprzyjemnił mi czas ;) Niby w nim nic nowego (no prawie ;), a jednak daje do myślenia. Dlatego polecam do przeczytania.

Pozwolę sobie wypisać tu kluczowe elementy odpowiedzialne według tego artykułu za fiasko projektu:
- Brak zaangażowania [przyszłych] użytkowników
- Błędna identyfikacja użytkowników [systemu]
- Oddanie decyzji w ręce użytkowników
- Oddanie decyzji w ręce deweloperów
- Brak jasnych celów projektu
- Brak pisemnej wizji projektu
- Akceptacja wszystkich życzeń
- Brak priorytetów
- Dominacja papierów
- Grupowe podejmowanie decyzji (brak lidera)
- Brak use-case'ów i prototypów
- Brak narzędzi do zarządzania wymaganiami
- Pominięcie oczywistych wymagań
- Pominięcie wymagań niefunkcjonalnych
- Zbytnia oszczędność na analizie wymagań
- Zbytnie wpływy polityki
- Brak formalnego zatwierdzenia ustalonego zakresu.

Po szczegóły odsyłam do artykułu ;)

Jeżeli już jesteśmy przy dobrych praktykach prowadzenia projektu i mamy analizę wymagań za sobą, to warto by wspomnieć o "Teście Joela", czyli metodzie obliczania jakości tworzonego oprogramowania na podstawie analizy pracy zespołu.

I znowu przytoczę kluczowe elementy (pytania z testu):
1. Czy stosujesz system kontroli wersji?
2. Czy możesz zbudować wersję w jednym kroku?
3. Czy stosujesz codzienne budowanie wersji?
4. Czy używasz system zarządzania błędami?
5. Czy usuwasz błędy zanim napiszesz nowy kod?
6. Czy masz harmonogram aktualizowany na bieżąco?
7. Czy masz specyfikację?
8. Czy programiści mają komfortowe warunki pracy?
9. Czy używasz najlepszych dostępnych narzędzi?
10. Czy masz testerów?
11. Czy kandydaci piszą programy podczas rozmowy kwalifikacyjnej?
12. Czy praktykujesz korytarzowe testy wygody użytkowania?

Według autora testu wynik poniżej 10 oznacza kłopoty. Podobno Microsoft wyrabia 12, ale jakoś nie chce mi się w to wierzyć ;) Szczegóły testu i omówienie znajdują się pod podanym linkiem.