Dzisiaj w programowaniu aplikacji obowiązują dwie proste i fundamentalne zasady. Po pierwsze, programujemy obiektowo i kod zamykamy w klasy z polami i metodami. Po drugie, tworzymy programy działające w środowisku graficznym, z okienkami, przyciskami, polami tekstowymi i innymi klasycznymi elementami interfejsu.
Jednak jeden plus dwa równa się kłopoty, przynajmniej w C++. Już raz narzekałem zresztą na to, że w tym języku obiektowość i GUI to nie jest najlepsze połączenie. Zgrzyta tu mechanizm obsługi zdarzeń generowanych przez interfejs graficzny.
Wcześniej napisałem, że możliwym rozwiązaniem problemu jest zasymulowanie w jakiś sposób delegatów, czyli – z grubsza – wskaźników na metody obiektów. To jedna z dróg radzenia sobie z kwestią obsługi zdarzeń. Ale też inna, wykorzystująca mechanizm metod wirtualnych i polimorfizmu. Polega to na zdefiniowaniu jednolitej klasy bazowej dla obiektów, które mają odbierać zdarzenia. Zwie się je zwykle event handlers, co jak zwykle nie ma dobrego tłumaczenia. Taki handler wyglądać może na przykład tak:
Mając jakiś element interfejsu użytkownika, np. przycisk, przekazujemy mu nasz własny obiekt implementujący powyższy interfejs. Metody tego obiekty są następnie (polimorficznie) wywoływane w reakcji na odpowiednie zdarzenia.
Tak oczywiście można robić w C++, ale nie jest to zbyt wygodne. Tym czego znów brakuje, to niestatyczne klasy wewnętrzne, obecne choćby w Javie, których brak w C++ nie da się do końca zastąpić wielokrotnym dziedziczeniem.
Albo delegaty, albo sposób opisany przed chwilą – zapewne nie ma żadnej innej drogi obiektowej obsługi zdarzeń. Niestety, żaden z tych sposobów nie jest obecnie wspierany w C++ i nie zanosi się, by miało się to wkrótce zmienić.
Chyba czas się opatentować. Rozumiem, że mogę mieć czasem kłopoty z doborem przeróżnych loginów czy innych identyfikatorów, że o domenach czy adresach e-mail nie wspomnę. Wiadomo, że Internet zatacza coraz szersze kręgi. To wszystko można zaakceptować, ale co da się powiedzieć o tym:
Pic na wodę, fotomontaż? Nie, autentyczne zdjęcie pewnego krakowskiego muru, wykonane przez kolegę Asmodeusza. Sam nie wiem, co mam o tym myśleć :)
Kiedyś nazywało się ich mecenasami, a dzisiaj częściej sponsorami. Składali oni u artystów zamówienia na określone dzieła, a w zamian potrafili porządnie sypnąć groszem. W ten sposób powstała duża część podziwianych do dzisiaj wytworów sztuki, z których większość nosi widoczne ślady dostosowywania się do wymagań dawnych sponsorów.
Programowanie może i nie jest sztuką (choć tutaj można by długo dyskutować), ale podobne relacje chyba i tutaj obowiązują. Przede wszystkim chodzi tutaj o komercyjne wytwarzanie oprogramowania, kiedy to klient określa swoje oczekiwania i płaci później za gotowy produkt, jeśli ten je spełnia. Ale nie tylko; otóż przypadkowo odkryłem też zupełnie inną formę “sponsorowania”, gdzie pieniądze w ogóle nie wchodzą w grę…
Sprawa dotyczy programu ColorShop, który to w przypływie natchnienia popełniłem ponad cztery lata temu. Kilka miesięcy wpadłem na pomysł jego poważnego udoskonalenia i sporządziłem nawet kawałek czegoś, co można nazwać wstępną dokumentacją… Ale, jak wiadomo, koder ma dziesięć pomysłów na sekundę, zatem także i ten został odłożony na półkę i szansa na jego realizację nie była zbyt duża.
Aż tu nagle dowiaduję, że w ramach jednego z przedmiotów w bieżącym semestrze (o wiele mówiącej nazwie Programowanie w technologii .NET) jest napisanie średniej wielkości aplikacji, wykorzystującej takie-a-takie elementy .NET Framework. Zamiast więc pisać kolejną pseudobazę jakichś niby-danych, pomyślałem więc, że mógłbym wziąć ten właśnie projekt. I tak zrobiłem.
Nie obyło się jednak bez większych i mniejszych modyfikacji i usprawnień, koniecznych aby program spełniał wymagania co do użytych technologii. Trudno powiedzieć, jak wpłynęły one na jego ostateczną postać, ale jedno jest prawie pewne: bez tego program miałby spore szanse nigdy nie powstać. Mogę więc spokojnie powiedzieć, że uczelnia w tym momencie zasponsorowała go – nie pieniędzmi, ale właśnie motywacją potrzebną do jego ukończenia. W końcu nic tak nie zachęca do pracy jak perspektywa zaliczenia jakiegoś przedmiotu ;)
A co do samego programu, to właściwie pozostało już tylko kilka mało programistycznych czynności końcowych, potrzebnych aby ColorShop 2.2 mógł zostać opublikowany w sieci – co powinno się niebawem stać.
W każdym języku programowania potrzebny jest system wejścia/wyjścia. To zresztą bardzo często wykorzystywana jego część, więc powinna charakteryzować się wysoką jakością. Chcielibyśmy mianowicie nie tylko tego, aby I/O było szybkie. Powinno być też elastyczne i proste w obsłudze. Czasami udaje się te wymagania pogodzić w całkiem zadowalający rezultat, a czasem nie.
Weźmy na przykład Javę. Posiada ona bardzo rozwinięty system wejścia/wyjścia, umożliwiający odczyt i zapis z wielu różnych źródeł: ekranu, plików, gniazdek sieciowych, potoków łączacych wątki, itp. Ponadto komunikacja może odbywać się na wiele sposobów: mamy na przykład dość “surowe” strumienie, nieco bardziej użyteczne czytacze (readers) i zapisywacze (writers), a także kanały (channel) i bufory (buffers).
Cały system wydaje się zatem bardzo zaawansowany. Niestety, w praktyce jest on zdecydowanie przerośnięty, a poza tym charakteryzuje się pewną ‘ciekawą’ cechą – nazwijmy to – projektową. Osobiście uważam, że twórcy Javy w tym momencie przedobrzyli i chcąc zastosować bardzo elastyczny w założeniu wzorzec Dekorator, stworzyli interfejsowy koszmarek. Wspomniany wzorzec polega na kolejnym “opakowywaniu” obiektów tak, aby rozszerzać ich możliwości; obiekt ‘zewnętrzny’ nie musi przy tym wiedzieć dokładnie, czym jest obiekt ‘wewnętrzny’. I tutaj rzeczywiście tak jest, lecz na nieszczęscie sami musimy zawinąć obiekt w te wszystkie warstwy.
Przykład? System.in
, czyli strumień standardowego wejścia, w swej pierwotnej postaci jest niemal zupełnie bezużyteczny. Żeby zrobić z nim cokolwiek sensownego (np. odczytać linię tekstu), musimy najpierw opakować go do postaci odpowiedniego czytacza:
Podobnie jest chociażby z plikami. Za każdym razem musimy ubrać nasz obiekt na cebulkę, aby był on przydatny, przechodząc przy okazji przez cały arsenał bardzo różnych klas, od których jest wręcz gęsto w JDK.
Trzeba aczkolwiek przyznać uczciwie, że System.IO
z .NET też zawiera całe mnóstwo różnych klas. Tam konieczność podobnego opakowywania zachodzi jednak o wiele rzadziej, gdyż interfejsy tych klas są trochę inteligentniejsze.
A co ze “staroświeckimi” językami, jak C++ czy Delphi? No cóż, w nich operuje się głównie pojęciem uniwersalnych strumieni i w zasadzie niczego więcej. Nie trzeba ich jednak niczym otaczać, bo fabrycznie potrafią już chociażby operować na podstawowych typach danych, a nie tylko ciągach bitów. Niby to mniej elastyczne i nie tak “obiektowo czyste”, ale o ile przyjemniejsze w użyciu.
Podobno nie ma głupich pytań, są tylko głupie odpowiedzi. Być może to rzeczywiście jest prawda. Jako moderator forum Warsztatu już nieraz widziałem wątki, które na pierwszy rzut oka nadawały się do eliminacji, ale akurat jakimś dziwnym zrządzeniem losu nie przykuły uwagi żadnego przedstawiciela służb porządkowych i zdążyły się rozrosnąć. Jaki był często skutek? Otóż zamieniały się one w całkiem rzeczowe dyskusje, chociaż – trzeba to przyznać – zazwyczaj trochę niezwiązane z początkowym tematem. Na pewno jednak zasługiwały na dalsze istnienie.
Z tej przyczyny jestem teraz chyba nieco bardziej tolerancyjny niż kiedyś. Zdarza się, że nawet jeśli mam uzasadnione podejrzenia (jak to ktoś ładnie nazwał: “przypuszczenie graniczące z pewnością” ;)), iż z danego wątku nie wyniknie nic dobrego, staram się nie kierować tym pierwszym wrażeniem. Aczkolwiek już ponad rok zajmuję się moderowaniem forum Warsztatu i pewnie daje mi to jakieś doświadczenie pozwalające dokonywać takich intuicyjnych przewidywań.
Zamiast (przed)wczesnych interwencji skłaniam się więc raczej do postawy wyczekującej. Można mi więc zarzucić, że zawsze czekam, aż problemami zajmie się ktoś inny. Albo że – mówiąc dosadniej – pośrednio pozwalam, by forum zalała fala lamerstwa. Ale to chyba nie jest takie proste. Prawdopodobnie bowiem każdy ma inne wyobrażenie tego, jakie wątki powinny się na forum pojawiać, a jakie nie, i oczywiste jest, że wszystkim dogodzić nie można. Plan minimum to eliminacja tych niepożądanych zachowań, co do których nikt nie ma żadnych wątpliwości. Lecz by robić coś więcej, potrzebna jest odpowiednia równowaga między restrykcyjnością a tolerancją.
Niełatwo ją znaleźć. Ale gdyby było inaczej, nie mógłbym co jakiś czas utyskiwać nad ciężką dolą moderatora ;]
Uczcimy dzisiaj chwilą ciszy pamięć naszych rozpoczętych projektów, które nigdy nie doczekały się ukończenia. Sporo ich było, prawda?… Potrzeba by naprawdę dłuższej chwili żałoby, żeby wspomnieć wszystkie. Tak się bowiem nieprzyjemnie składa, że wiele znaków na niebie i ziemi wskazuje, że prawdziwe jest stwierdzenie, iż:
O ile w grę nie wchodzą silne czynniki zewnętrzne, każdy rozpoczęty projekt ma bardzo duże szanse na to, by nigdy nie doczekać się realizacji.
Takim silnym czynnikiem nie jest jednak presja wynikająca z pracy w zespole. Jest to czynnik jak najbardziej wewnętrzny, a projekty grupowe mają chyba takie same szanse popadania w stagnację i wreszcie w kompletny zastój. Zapewne jedynie sankcje natury edukacyjnej (gdy chodzi np. o tak zwane projekty ‘na zaliczenie’) czy finansowej (kiedy mówimy o komercyjnym wytwarzaniu oprogramowania) są w większości przypadków wystarczającą “zachętą”. A pewnie i nie zawsze…
Zaś w przypadku projektów totalnie amatorskich i całkowicie dobrowolnych powyższe przygnębiające prawo ma, jak sądzę, wielkie prawdopodobieństwo okazać się trafne. Prawie zawsze znajdzie się jakiś powód – czasem istotny, a czasem zupełnie błahy, a często nawet trudny do określenia – żeby wszystko stanęło i przestało się rozwijać. A im dłuższa jest przerwa, tym trudniej kontynuować potem przerwaną pracę. I wreszcie przekraczamy ten punkt krytyczny, czasową granicę, po przejściu której do danego projektu po prostu się nie wraca.
Okropnie to pesymistyczne, więc może teraz spróbujmy się choć trochę pocieszyć. Otóż nawet jeśli większość naszych przedsięwzięć koderskich nie doczekała się przybrania ostatecznego kształtu, to przecież czas włożony w ich rozwój nie był do końca stracony. Prawdopodobnie tylko w taki sposób można poszerzać swoje umiejętności i zyskiwać niezbędną wiedzę – nie tylko o tym, że kończenie projektów jest bardzo, bardzo trudne :)
A ponadto te niedokończone pomysły nie są przecież zakopywane pod ziemią – chyba że znikają w płomieniach formatowanych dysków czy zostają pożarte przez komputerowe robaki. Rozsądni programiści robią jednak kopie zapasowe i pieczołowicie gromadzą takie “odpady”. A nuż bowiem coś się jeszcze przyda? Jakiś fragment, ciekawe rozwiązanie czy nawet kilka linijek, które kiedyś wyprodukowaliśmy i które akurat teraz są nam potrzebne. To prawda, że po dłuższym czasie możemy być już w zupełnie innym miejscu, pisać w zupełnie innym języku programowania i korzystać z całkowicie innych bibliotek czy innych narzędzi. W kodowaniu pewne rzeczy są jednak uniwersalne i czasem pogrzebanie w zarzuconych projektach może okazać się pożyteczne.
Kto wie, może dzięki takiemu recyklingowi uda się powstrzymać wzrost tej góry śmieci…
Myślę, że większość programistów miała przynajmniej przelotny kontakt z wyrażeniami regularnymi. Zwykle używa się do bardziej zaawansowanego wyszukiwania podciągów w tekście – nie określonych sekwencji liter, ale fragmentów określonych raczej pewnymi warunkami. Innym ich zastosowaniem jest też sprawdzanie, czy podany łańcuch pasuje do pewnego określonego formatu. Przy pomocy poniższego wyrażenia:
można na przykład sprawdzić poprawność adresu e-mail (czy zawiera on znaczek @, czy nazwa domeny jest przynajmniej formalnie poprawna, itd.). Nietrudno zauważyć jednak, że sama postać wyrażenia jest dość odstręczająca, a poza tym sporo elementów się w nim powtarza – choćby sekwencja dopasowująca pojedynczy znak alfanumeryczny. Poza tym dokładna składnia wyrażeń w danej bibliotece może być niekiedy specyficzna, chociaż podstawy (czyli np. elementy pokazane powyżej) powinny być w zasadzie wszędzie takie same.
Mimo to czasem warto użyć tego narzędzia również w bardziej zaawansowanym celu: parsowania ciągów znaków w celu wydzielenia i odczytania z nich określonych fragmentów. Zapewne nie da się w ten sposób przetworzyć na przykład dokumentu XML czy innego skomplikowanego formatu, lecz w prostszych przypadkach może się to okazać szybsze i wygodniejsze. Alternatywą jest oczywiście samodzielne napisanie parsera, tokenizera czy innego tego typu obleśnego automatu stanów :)