Posts tagged ‘design’

O pożytkach i nieużytkach UML-a

2008-01-26 17:45

Stare chińskie przysłowie mówi, że klient zazwyczaj nie wie dokładnie, czego tak naprawdę chce. (Inne przysłowie mówi też, że jeśli nie znamy pochodzenia danej sentencji, to najlepiej powiedzieć, że jest to stare chińskie przysłowie). Dlatego też często nie potrafi swoich potrzeb przełożyć na opis wymagań co do oprogramowania. Jest to jeden z problemów, przy rozwiązywaniu których ma pomagać Unified Modelling Language, czyli UML. W założeniu jest to notacja, umożliwiająca rozpisanie całego procesu tworzenia aplikacji na szereg różnego rodzaju diagramów, na których znaczenie poszczególnych symboli jest ściśle określone – na pewno ściślej niż języka naturalnego. Założenie jest szczytne i bardzo ambitne, a z praktyką jest jak zwykle nieco gorzej :)

Symbol notacji UMLNiewykluczone, że jedną z idei przyświecających twórcom UML-a było przynajmniej częściowe zasypanie tej przepaści między dwoma etapami tworzenia: kiedy wiemy, co mamy zrobić, ale jeszcze nie mamy wielkiego pojęcia o tym, jak to zrobić. Przeskoczenie dystansu pomiędzy tymi punktami jest bowiem często kwestią odpowiedniego pomysłu, który najlepiej realizuje całą koncepcję systemu. Jak zaś wiadomo, pomysły biorą się głównie z niezbadanych obszarów pewnego organu znajdującego się między uszami i ich jakość zależy głównie od tego, do kogo ów organ należy. UML stara się więc usystematyzować programistyczną kreatywność, aby zaprojektowanie dobrze działającego systemu nie było tylko wypadkową wymysłów kłębiących się pod czaszką analityka.
Trzeba przyznać, że wychodzi mu to dość średnio. Różne rodzaje diagramów, jakie mamy do dyspozycji, nie bardzo pomagają w płynnym przechodzeniu od wymagań funkcjonalnych do projektu, który te wymagania ma spełniać. Mam raczej wrażenie, że ich celem jest głównie spoglądanie na aplikację z coraz to nowych punktów widzenia. Patrzymy więc na projekt z perspektywy użytkownika zewnętrznego (diagram przypadków użycia), zmieniających się stanów obiektów (diagramy stanów), przepływu danych i obiektów między “miejscami” w programie (diagramy interakcji), i tak dalej. W sumie widzimy coraz więcej pojęć, związków, relacji i zależności, przez co projekt – zamiast upraszczać się, co z pewnością kieruje nas bliżej implementacji – komplikuje się jeszcze bardziej.

Większość z tych konstrukcji nie jest zresztą widoczna w wynikowym kodzie. Nic więc dziwnego, że spośród całego UML-a zdecydowanie najpopularniejsze i najczęściej stosowane są diagramy klas i diagramy sekwencji (przepływu zdarzeń). Odpowiadają one bowiem niemal bezpośrednio strukturze klas i ich składowych oraz przepływowi sterowania w metodach i funkcjach. W ich przypadku przyznaję, że schemat graficzny jest bardziej przejrzysty niż tekst w języku naturalnym czy kod. Co więcej, używana przy okazji notacja jest też najszerzej znana. Autorzy wielu książek dotyczących języków programowania bardzo często bowiem “przemycają” w nich zwłaszcza diagramy klas, z nieśmiertelną strzałką w górę jako symbolem dziedziczenia.
Jeśli zaś chodzi o resztę diagramów, to ich użyteczność wydaje mi się wątpliwa. Mówiąc wprost, uważam je póki co za zwyczajne zawracanie głowy :)

Tags:
Author: Xion, posted under Computer Science & IT, Thoughts » 5 comments

Niemal standardowe nazwy klas

2008-01-02 13:48
Model-View-Controller
Model-View-Controller (MVC),
bardzo modny ostatnio ‘wzorzec’

Wzorce projektowe (design patterns) to w założeniu ogólne modele związków pomiędzy klasami (i samych klas), jakie mogą pojawiać się w projektach. Każdy taki wzorzec jest przeznaczony do dość ściśle określonych okoliczności, dokładnie opisany i, przede wszystkim, nazwany. Na pewno każdy średnio zaawansowany programista miał okazję spotkać się z takimi terminami jak Iterator, Fabryka czy Singleton. Są one już na tyle długo używane, że w większości przypadków nie ma problemów ze zrozumieniem tego, co w danej sytuacji oznaczają.

Wzorce nie są oczywiście doskonałe. Ze względu na względnie dużą ścisłość nie mogą opisywać wszystkich rozwiązań, jakie mogą być konieczne, jeśli myślimy o zaprojektowaniu dowolnego programu. Nie jest więc tak, że każda klasa, jaka przyjdzie nam głowy, może być od razu wpasowana w jakiś gotowy szablon.
Przeglądając gotowe kody i różnego rodzaju dokumentacje stwierdziłem jednak, że często powtarzają się w nich różnego rodzaju “pseudowzorce”. Objawiają się one głównie używaniem pewnych słów w nazwach klas, dzięki którym można mniej więcej domyślić się, jaka jest rola poszczególnych typów, do czego służą, jak – z grubsza – działają oraz jakie wykazują zależności z innymi klasami. Naturalnie mogą występować spore różnice pomiędzy poszczególnymi bibliotekami i językami, ale przynajmniej dla dwóch największych zbiorów klas, jakie są obecnie w powszechnym użyciu (czyli .NET Framework i JDK), rozbieżności nie są zbyt duże. Co więcej, ponieważ wielu programistów używa któregoś z tych dwóch narzędzi, często przejmują oni te wzorce nazewnictwa (świadomie lub nie) i stosują je we własnych kodach. Kto wie, może dzięki temu przeciętna czytelność kodu produkowanego przez statystycznego programistę (jeśli w ogóle istnieje ktoś taki :]) ma szansę choć odrobinę wzrosnąć?…

Jakie są więc te nieformalne “wzorce”? Otóż znalazłem kilka następujących:

  • Manager to egzemplarz występujący bardzo często i niestety chyba najmniej jednoznaczny z nich wszystkich. W dosłownym tłumaczeniu jest to ‘zarządca’ czegoś i właściwie niewiele więcej można o nim powiedzieć. Nazywanie wszystkiego ‘menedżerem’ jest popularne zwłaszcza wśród programistów gier: mamy więc menedżery zasobów, tekstur, stanów, czcionek – i tak dalej. Zastanawiam się tylko, skąd to upodobanie to tego akurat terminu. Czyżby programiści z sentymentem wspominali w ten sposób Menedżera Programów, czyli sławetną “powłokę” systemu Windows w archaicznych wersjach 3.x? :)
  • Handler (ew. listener) jest już znacznie precyzyjniejszym pojęciem. Zazwyczaj jest to bowiem obiekt, który ma wykonywać jakieś działanie w reakcji na określenie zdarzenie – czyli ma je ‘obsługiwać’. Najczęściej oznacza to, że typ handlera jest tylko abstrakcyjną klasą bazową lub interfejsem, po którym powinniśmy dziedziczyć, a następnie zaimplementować jego metody i wreszcie “podpiąć” powstały obiekt pod mechanizm obsługi zdarzeń. Typowym zastosowaniem takiego rozwiązania jest reakcja na zdarzenia interfejsu użytkownika (klikanie w przyciski, wciśnięcia klawiszy, itd.) albo powiadamianie o przebiegu operacji asynchronicznych (np. komunikacji sieciowej).
  • Provider jest z kolei obiektem, który ma ‘dostarczać'(lub ‘zapewniać’) jakąś funkcjonalność. Od strony technicznej często działa on tak samo jak handler (czyli w oparciu o polimorfizm metod wirtualnych), tyle że jego przeznaczenie i sposób użycia są nieco inne. Takiego providera podajemy w funkcji lub w obiekcie, dzięki czemu może ona wykonać przy jego pomocy jakieś czynności. Prostym przykładem jest chociażby sortowanie, któremu podajemy obiekt komparatora, odpowiedzialny za dokonywanie porównań. Tak naprawdę jest to więc po prostu wzorzec Strategia (Polityka – policy), któremu ktoś z niewiadomych powodów nadał bardziej “profesjonalną” nazwę.
  • Context oznacza najczęściej taki obiekt, który definiuje jakieś ‘środowisko’, w którym działają inne obiekty. Są one zależne od obiektu reprezentującego kontekst, ale nie można powiedzieć, aby ten się z nich składał (może on nawet “nie wiedzieć”, że jest przez nie wykorzystywany). Po prostu dzięki niemu obiekty “potomne” mogą wykonywać swoje czynności.

Prawdopodobnie dałoby się wyróżnić jeszcze kilka pozycji (chociaż część byłaby dokładnym odpowiednikiem klasycznych wzorców projektowych), ale, jak widać, w sumie chyba nie jest ich zbyt dużo. To w gruncie rzeczy całkiem dobra wiadomość, gdyż nietrudno zauważyć, że wszystkie te nazwy są raczej “magiczne” i na pierwszy rzut nie przywołują jakichś natychmiastowych skojarzeń – zwłaszcza, jeśli nie jesteśmy do nich przyzwyczajeni. Ale taki już urok projektowania zorientowanego obiektowo, polegającego na tworzeniu dziwnych bytów i jeszcze dziwniejszych zależności między nimi :)

Tags: ,
Author: Xion, posted under Programming » 4 comments

Kartka papieru

2007-12-03 19:02

Teoretycznie wszystko zakodować można “na żywioł”, po prostu siadając do ulubionego środowiska programistycznego, pisząc i kompilując. Proste dzieła może i można w ten sposób stworzyć, ale do czegoś bardziej skomplikowanego zawsze pomaga chociaż pobieżny projekt.
W jakiej formie? To już nie jest sprawą taką prostą. Obecnie mamy sporo różnych narzędzi, umożliwiających formowanie naszych pomysłów w miejscu ich docelowego egzystencji – czyli programów komputerowych. I nie chodzi mi tu o aplikacje wysoce wyspecjalizowane, bo do zaprojektowania programu może się okazać przydatny edytor tekstu typu Notatnika. Każde z tych narzędzi będzie użyteczne dla określonego celu i każde ma też swoje ograniczenia.

Kilka kartek papieruAle mamy również inny sposób. Możemy odejść (albo przynajmniej się odwrócić) od komputera, wziąć kartkę, długopis i… do dzieła. Być może wkrótce posługiwanie się tak “archaicznymi” sprzętami będzie nieco staroświeckie, ale pewnie jeszcze długo będzie miało niezaprzeczalne zalety:

  • Nieograniczone możliwości formatowania :) Tekst w formie zawijasa w środku obrazka? Proszę bardzo. Diagram ze strzałkami biegnącymi między komórkami tabelki? Nie ma problemu. Treści możemy układać tak, jak chcemy i jak tylko pozwala nam na to zręczność palców – zapewne poważnie nadwerężona ich regularnym stukaniem w klawisze ;]
  • Swoboda formy. Programy komputerowe potrafią edytować tylko dokumenty w określonych formatach, jak teksty, arkusze kalkulacyjne, diagramy UML, itd. Tutaj zaś nie mamy takich ograniczeń i możemy tworzyć schematy, zestawienia, wykresy i inne pomocnicze rysunki, które nie śniły się teoretykom inżynierii oprogramowania :)
  • Dostępność. Wprawdzie teraz komputer można zabrać ze sobą niemal wszędzie, ale i tak w tej kategorii trudno o sensowne porównywanie. Papier jest jednak zdecydowanie poręczniejszy.

Każdy jednak wie rzecz jasna, jakie są jego wady. Niektóre alternatywy – jak na przykład skrobanie po tabletowym komputerze – niwelują część z nich, zbijając przy okazji niektóre zalety. Największym mankamentem jest chyba jednak to, że rysunkowe bazgroły na papierze zawsze już pozostaną bazgrołami: nie da się ich przetworzyć na użyteczną formę schematów czy tabel zrozumiałą dla komputera, a i rozpoznawanie czystego tekstu też nie jest jeszcze doskonałe.
Wygląda więc na to, że poczciwa kartka papieru jeszcze długo będzie podstawowym środkiem tworzenia przynajmniej wstępnych – nomen omen – szkiców, zostawiając bardziej szczegółowe projektowanie innym narzędziom. Warto zatem wciąż kultywować starożytną umiejętność ręcznego pisania i rysowania :)

Tags: , ,
Author: Xion, posted under Programming » 10 comments

Bolączki C++ #1 – Pimp(l) my code

2007-08-25 12:13

Kiedy programuje się w języku, który jest – jak to ładnie mówią Amerykanie – ‘standardem przemysłowym’ w danej dziedzinie, chcąc nie chcąc trzeba się do niego przyzwyczaić. A to oznacza, że musimy nauczyć się żyć z jego wadami, które niekiedy mogą być tylko irytujące, a niekiedy bardzo nieprzyjemne. Ważne, by mieć świadomość ich istnienia i w miarę możliwości sobie z nimi radzić.
C++ jako ustandaryzowany język ma już prawie dziesięć lat, więc lista jego niedoskonałości w porównaniu z nowszymi językami siłą rzeczy staje się coraz dłuższa. Takie listy są jednak w dużym stopniu subiektywne, zatem i ten, który rozpoczynam poniżej, absolutnie nie pretenduje do miana ostatecznej wyroczni :) Na pewno inni usunęliby z niego część pozycji, niektóre uznali za mniej lub bardziej dotkliwe, a także dodali własne propozycje.

Dla mnie sprawą, która w C++ jest powodem największego bólu zębów, jest sposób organizacji kodu zaproponowany w tym języku. Jest on bodaj jedynym mi znanym (poza swoim poprzednikiem C), w którym występuje podział plików z kodem na dwa rodzaje: pliki nagłówkowe (*.h, *.hpp) i moduły kodu (*.cpp). W tych pierwszych teoretycznie umieszczany jest interfejs klas i funkcji, czyli informacje potrzebne do ich użycia w innym miejscu programu. W tych drugich jest zaś zawarta implementacja rzeczonych klas oraz funkcji.
Tyle teorii. W praktyce osiągnięcie idealnej separacji obu tych elementów wymaga sztuczki nazywanej szumnie wzorcem projektowym, o nazwie Pimpl (skrót od private implementation). Wygląda on na przykład następująco:

  1. // -- foo.hpp --
  2. class CFoo_Impl;   // deklaracja zapowiadająca
  3.  
  4. // właściwa klasa
  5. class CFoo
  6. {
  7.     private:
  8.         CFoo_Impl*  m_pImpl;
  9.  
  10.     public:
  11.         CFoo();
  12.         ~CFoo();
  13.         void SomeOperation1();
  14. };
  15.  
  16. // -- foo.cpp --
  17. #include "foo.hpp"
  18.  
  19. class CFoo_Impl
  20. {
  21.      // implementacja
  22. };
  23.  
  24. CFoo::CFoo() : m_pImpl(new CFoo_Impl) { /* ... */}
  25. CFoo::~CFoo() { delete m_pImpl; }
  26. void CFoo::SomeOperation1() { m_pImpl->SomeOperation1(); }
  27. // itd.

Wtedy faktycznie nie widać, co siedzi w środku naszej klasy, ale cena takiej hermetyzacji to konieczność stworzenia dodatkowej klasy i przekierowania do niej metod. Może nie jest to dwa razy więcej roboty, ale przynajmniej 10-20%.
Z drugiej strony większość języków nowszych niż C++, jak Java, C# czy Python, w ogóle zna takich pojęć jak ‘prototyp funkcji’ czy ‘deklaracja zapowiadająca’. Tam kod piszemy od początku do końca: funkcję zawsze z jej treścią, a klasę zawsze w całości łącznie ze wszystkimi polami i kodem wszystkich metod. Nie ma podziału na pliki z ‘interfejsem’ i z implementacją.
A w C++ ten podział istnieje i tak naprawdę służy on tylko i wyłącznie… wygodzie kompilatora. Dzięki temu, że treść plików nagłówkowych jest taka, a nie inna (i np. zawierają one deklaracje prywatnych pól klas, które są przecież częścią implementacji), mogą być one zwyczajnie dołączane do modułów przy pomocy arcyprymitywnej dyrektywy #include. Dla kompilatora jest to najprostsze rozwiązanie, bo może on pracować nad każdym modułem osobno i nie musi się martwić żadnymi niejawnymi zależnościami między plikami. A dla programisty oznacza to wybór między wygodą kodowania, a jakością i “odpornością” stworzonego kodu.

C++ stoi więc w pewnym sensie pośrodku i wcale nie jest to złoty środek. Z jednej strony mógłby pójść w kierunku wyznaczonym przez wspomniane nowsze języki, czyli wprowadzenia jednego typu plików źródłowych, zawierających kod bez podziału na deklaracje i implementacje. To by było jednak mało oryginalne :) Bardziej interesujące byłoby, jak sądzę, polepszenie istniejącego rozwiązania w odwrotnym kierunku; być może automatyczne stosowanie wzorca Pimpl dla każdej klasy jest jednym ze sposobów.
Niestety, patrząc na obecny roboczy szkic standardu C++0x, w którym nic na ten temat nie znajdziemy, nie możemy raczej oczekiwać, że coś tu się zmieni w przewidywalnej przyszłości.

Tags: , ,
Author: Xion, posted under Programming » 3 comments

Wyjście = format + ujście

2007-08-19 11:58

Dla odmiany zająłem się ostatnio czymś nieco prostszym niż GUI, a mianowicie systemem logującym. To zdecydowanie niezbędne narzędzie, które docenia się zwłaszcza wtedy, kiedy coś innego psuje. Jak dotąd aczkolwiek nic poważnego nie zdążyło się jeszcze, ale w programowaniu jest to, jak wiadomo, tylko kwestią czasu ;)

Do logowania istnieje mnóstwo podejść, które różnią się głównie tym, gdzie i jak zapisujemy nasze informacje. Bo jeśli o to, co mamy logować, to odpowiedź jest prosta: wszystko albo jak najwięcej.
Dość obszerny przegląd możliwych wyjść loggera przedstawił TeMPOraL w jednej swoich notek. Jakkolwiek długa ta lista by nie była, pewne jest jedno: na pewno nie może być ona zapisana “na sztywno” w kodzie głównej klasy systemu logującego. Same ‘wyjścia’ należy bowiem opakować w osobne klasy, wywodzące się ze wspólnej bazy i wykorzystać zalety polimorfizmu. W ten sposób można kierować komunikaty zarówno na konsolę, do zwykłego pliku tekstowego, jak i do funkcji OutputDebugString.

Pomyślałem jednak nad rozszerzeniem tego pomysłu, który wyraża się w tytułowym równaniu: wyjście = format + ujście. Postanowiłem oto podzielić wyjście logowania na dwie części:

  • format odpowiadający za wygląd pojedynczego wpisu. W zależności od rodzaju obiekt ten przerabia komunikat logowania na zwykły tekst, XHTML, XML albo inny, dowolnie wybrany format danych.
  • ujście (drain), zajmujące się zapisaniem sformatowanego komunikatu w określonym miejscu. Tym miejscem może być standardowe wyjście diagnostyczne (czyli zwykle konsola), lokalny plik czy nawet plik na zdalnym serwerze.

Schemat systemu logującego

Nie jest to oczywiście żadna rewolucja. Czasami też obie części muszą być do siebie ściśle dopasowane, aby osiągnąć pożądany efekt (np. wypisywanie kolorowych komunikatów w asynchronicznej konsoli Windows). Zwykle jednak można je zestawiać dowolnie i otrzymywać ciekawe rozwiązania. Najważniejsze, że tą dodatkową elastyczność da się osiągnąć małym kosztem.

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Wyjście = format + ujście

System GUI #3 – Zdarzenia

2007-08-14 11:42

Graficzny interfejs może (i powinien) ładnie wyglądać, ale jego najważniejszą cechą jest oczywiście interaktywność. GUI musi przede wszystkim umożliwiać reakcję na poczynania użytkownika, a to jest możliwe przez odpowiednią obsługę zdarzeń.

Informację o tym, że kliknięto gdzieś myszką lub wciśnięto klawisz, pozyskać jest oczywiście bardzo łatwo. W Windows wystarczy w tym celu obsługiwać komunikaty systemowe przychodzące do okna. O wiele większym wyzwaniem jest przetłumaczenie informacji “kliknięto w punkt (458,89) obszaru klienta” na “kliknięto w obrębie kontrolki TextBox o nazwie Text1“. Komunikat należy bowiem przekazać do odpowiedniej kontrolki (-ek) – tej, której on dotyczy.

Jest pewnie wiele sposób na zrealizowanie takiego przekazywania, jednak najbardziej sensowne wydają mi się dwa. Pierwszym z nich jest propagacja zdarzenia na kolejne poziomy drzewa systemu GUI. Kliknięcie jest więc przekazywane do okna i od tej pory cała odpowiedzialność za jego obsłużenie spada na to właśnie okno. Ono sprawdza, czy myszka trafiła w którąś z kontrolek potomnych; jeśli tak, to znów zdarzenie jest przekazywane właśnie do tej kontrolki i okno się już nim nie zajmuje. Proces ten przebiega aż dojdziemy do najniższego możliwego poziomu, czyli kontrolki nie zawierającej już żadnych innych. Tam następuje właściwa obsługa zdarzenia.

Przepływ zdarzeń w systemie GUI - wariant pierwszy

Drugi sposób zakłada, że zdarzenie nie będzie “rozłazić” się po całym drzewie, tylko od razu trafiać do tej właściwej, docelowej kontrolki. Naturalnie nie zawsze da się tak zrobić. W przypadku wciśnięć klawiszy możemy pamiętać, która kontrolka ma tzw. fokus i do niej kierować komunikaty. Natomiast obsługa myszki wymaga wysłania swego rodzaju sondy wgłąb drzewa kontrolek w celu znalezienie tej najmniejszej, w którą trafił kursor (operacja ta jest znana jako hit test).

Przepływ zdarzeń w systemie GUI - wariant drugi

Generalnie zdarzenia z punktu widzenia ich obsługi można podzielić na dwie grupy: na zdarzenia myszki i na… wszystkie inne :) To właśnie te pierwsze przysparzają najwięcej kłopotów. Nie tylko wymagają rekurencyjnego przeszukiwania drzewa kontrolek, ale też mogą mieć globalne konsekwencje, jak np. zmiana fokusu. Zajmowanie się tymi konsekwencjami jest łatwiejsze, gdy wszystko odbywa się “na górze” – na poziomie głównych klas systemu GUI, a nie pojedynczych kontrolek. Jest to jeden z powodów przemawiających za wyborem drugiego sposobu przekazywania informacji o zdarzeniach.

Tags: , , ,
Author: Xion, posted under Programming » 1 comment

System GUI #2 – Model klas

2007-08-06 11:40

Po kilku dniach niezbyt intensywnego zastanawiania się nad strukturą systemu GUI zdołałem wysmażyć coś, co można nazwać schematycznym modelem klas. Jest on wybitnie poglądowy i przedstawia się następująco:

Schemat modelu klas systemu GUI

Na pierwszy rzut oka może wydawać się nieco pokręcony, ale z odrobiną wysiłku można go odcyfrować :] To co jest w nim chyba najważniejsze, to dość wyraźny podział na część rysującą (lewa strona) i tę zawierającą kontrolki. Łączy je “most” złożony z głównej klasy systemu, nazwanej bardzo oryginalnie: System :) Taki podział zapewni między innymi prostsze rysowanie samych kontrolek, które będą renderowane przy pomocy takich poleceń jak: narysuj ramkę, narysuj wciśnięty przycisk, itp. To rozwiązanie zaczerpnąłem z Windows.Forms, gdzie jest zresztą ono posunięte o wiele dalej.
Lista kontrolek jest, jak widać, skromna i odpowiada tej, którą wcześniej ustaliłem jako minimalną. Oprócz abstrakcyjnej klasy dla wszystkich kontrolek wprowadziłem też osobną dla takich, które mogą zawierać i grupować inne kontrolki. Póki co jedynym obiektem tego rodzaju jest okno, ale GUI zna ich więcej: jak np. panel czy pole grupy (groupbox).

Tags: , ,
Author: Xion, posted under Programming » 1 comment
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.