Nagłówki i przestrzenie nazwNiestety, przestrzenie nazw w C++ nie mają takiego wdzięku jak pakiety w Javie czy assemblies w .NET. Zwłaszcza w połączeniu z plikami nagłówkowymi potrafią one sprawić nieco problemów. Jeśli bowiem nie będziemy uważali, to możemy dość łatwo zniwelować korzyści, jakie płyną z ich używania.
Ale po kolei. Jeśli mamy do czynienia z modułem kodu (plikiem .cpp), to możemy bez żadnych skrupułów umieszczać w nim deklaracje using lub using namespace. Jeśli tylko nie prowadzi to do niejednoznaczności, wszystko jest w porządku, bowiem zasięg tych deklaracji ogranicza się tylko i wyłącznie do tego jednego pliku.
Gorzej jest niestety z plikami nagłówkowymi. Ponieważ włączane są one tu i ówdzie przy pomocy dyrektywy #include, nie można tak po prostu wpisywać w nich deklaracji using namespace. Zostałyby one bowiem rozpropagowane do wszystkich plików, które dany nagłówek włączają, efektywnie niwelując wszelkie korzyści zamknięcia fragmentów kodu w przestrzeń nazw. Bo co to za namespace, który wszyscy “rozpakowują” i przenoszą jego zawartość do przestrzeni globalnej?…
Dlatego nie ma rady: w plikach nagłówkowych można używać wyłącznie nazw kwalifikowanych – poprzedzonych wszystkimi nazwami przestrzeni, jak np. XC::Base::GC::BlockInfo. W przeciwnym wypadku na pewno zaśmiecimy sobie którąś z przestrzeni (najczęściej globalną) identyfikatorami, które do niej nie należą – co będzie widoczne w systemach podpowiadania takich jak IntelliSense.
Trzy funkcje semaforówPodobno programowanie równoległe jest trudne: fakt, że kilka czynności może być wykonywanych "jednocześnie", wprowadzać ma duże zamieszanie i komplikować życie. Nie zgadzam się! Nawet jeśli procesy lub wątki działają w tym samym czasie, to jeszcze nie jest powód do zmartwień. Prawdziwe problemy zaczynają się przecież dopiero wtedy, gdy trzeba ich działanie zsynchronizować :-)
A do tego przydają się na przykład semafory. Przy ich pomocy których można zresztą zrealizować większość popularnych mechanizmów blokowanej synchronizacji, jak choćby sekcje krytyczne. Jednak cała sztuka polega zawsze na tym, żeby użyć ich w sposób odpowiedni do danego zastosowania i nie nabawić się przy tym zagłodzenia, zakleszczenia (deadlock) lub innych "równoległych" błędów.
Istnieją oczywiście tzw. schematy synchronizacji, lecz ich przydatność jest mniej więcej kilkukrotnie mniejsza niż chociażby wzorców projektowych dla programowania obiektowego. Zauważyłem jednak, że sytuacje, w których używa się semaforów (lub podobnych obiektów) można zakwalifikować do jednej z trzech grup, z których każda następna jest ugólnieniem poprzedniej:
W ten sposób tworzy się oczywiście sekcja krytyczna, bo tylko jeden proces uzyska do naszego zasobu dostęp. Wygodne jest tutaj myślenie o semaforze jako obiekcie ochraniającym zasób, a nie fragment kodu. Oczywiście na samym początku taki semafor jest zwykle opuszczony.
Bo programowanie równoległe - którego synchronizacja jest nieodłączną częścią - to jednak ciężki kawałek chleba ;]
Wiosenne porządki #2 – Ujednolicanie interfejsuJedną z bardziej denerwujących cech bibliotek, z jakimi można się zetknąć, jest ich wewnętrzna niespójność pod względem interfejsu. Dotyczy to na przykład drobnych kruczków związanych z postacią wywołań funkcji i metod. Znanym przedstawicielem tego gatunku "kwiatków" są chociażby standardowe funkcje C(++) od zapisu do plików - fprintf i fputs:
Obie działają podobnie (z dokładnością do formatowania w fprintf), lecz różnią się irytującym szczegółem: miejscem parametru określającego plik (typ FILE*), do którego zapisujemy. Nietrudno się tu pomylić i to niekoniecznie wtedy, gdy korzystamy z obu funkcji w tym samym kodzie.
Podobna niejednolitość jest na szczęście w miarę łatwa do wykrycia i usunięcia - oczywiście pod warunkiem, że mówimy tutaj o bibliotece, której nie wykorzystuje jeszcze nikt postronny. Oprócz niespójnych prototypów funkcji powinniśmy jeszcze zwrócić uwagę na:
Get/SetSomething, IsEnabled, itd. Podobnie metody z nazwami zawierającymi czasowniki "znaczące", takie jak load, create, read, write powinny przede wszystkim robić to, co owe czasowniki wyrażają.Nanoszenie podobnych poprawek jest oczywiście dość skomplikowaną operacją, ale wciąż nie ingeruje ona ani w projekt biblioteki, ani - z grubsza - w sposób, w jaki końcowy programista ma jej używać. Mimo to podejrzewam, że w przypadku mojego siln... tj. kodu będzie to dosyć czasochłonne. Całkiem często zmieniałem bowiem swoje upodobania odnośnie kwestii, które wymieniłem powyżej (i których lista nie jest też pewnie kompletna).
Ale cóż, wiosna dopiero się zaczyna, więc czasu - przynajmniej teoretycznie - jest dużo ;-)
Magazyn o Warsztacie
k_b wcielił w życie bardzo ciekawy pomysł, polegający na wydawaniu co jakiś czas magazynu opisującego wydarzenia dziejące się na Warsztacie. Chodzi tu na przykład o postępy w amatorskich projektach, które są z tym community związane, interesujące screeny, ciekawostki z forum i inne warte odnotowania fakty. W o wiele skromniejszej formie coś takiego już występowało w postaci tzw. Community News, ale ostatecznie niezbyt się to przyjęło.
Miejmy nadzieję, że z WMagiem - bo tak w skrócie się to wydawnictwo nazywa - będzie inaczej. Pierwszy numer nie grzeszy może zachwycającą szatą graficzną, ale i tak warto mu się przyjrzeć. Do czego zachęcam, prezentując poniższe linki:
Wielkanocne jajaWprawdzie Google ostatecznie rozstrzyga rywalizację między Gwiazdką a Wielkanocą na korzyść tej pierwszej, ale to zwyczaj z okolic święta wiosennego użyczył nazwy dla pewnej zabawnej praktyki programistycznej. Chodzi o easter eggs, czyli ukryte napisy, ekrany, a czasem całe pod-aplikacje, obecne w programie acz niedostępne niewtajemniczonym użytkownikom. Mimo tego, wiele spośród tych 'jaj' stało się wyjątkowo znanych.
Dotyczy to naturalnie tych aplikacji, które są szeroko znane - czyli na przykład tych wyprodukowanych przez znaną i (nie)lubianą firmę z Redmond. Różne wersje pakietu Office zawierają przykładowo przynajmniej kilka różnych gier, począwszy od prostego flippera (Word 97) przez symulator lotu aż po... grę FPP (we wczesnej wersji Excela). W Windows poniżej wersji XP można było też wykorzystać ciekawostkę ukrytą w jednym z wygaszaczy ekranu, który mógł wyświetlać kolejno nazwy znanych wulkanów na świecie.
Spora ilość easter eggs jest zawarta w okienkach About programów - co jest całkiem zrozumiałe, zważywszy że prawie nikt tam nie zagląda :) Do bardziej znanych należy chyba odgłos wydawany po kliknięciu w nos na fotografii twórcy programu mIRC.
Jaja możemy jednak znaleźć nie tylko w programach użytkowych, ale także w grach. Kto nie zna słynnego "krowiego poziomu" (cow level) z Diablo II? Blizzard ma zresztą więcej takich kwiatków w zanadrzu - jak choćby zabawne dźwięki wydawane przez jednostki w obu znanych RTS-ach (WarCraft i StarCraft). W World of Warcraft ilość różnych smaczków liczy się co najmniej w tysiącach.
Niestety, przed easter eggs przyszłość rysuje się ponuro. Możliwość umieszczenia w kodzie programu nieudokumentowanego kodu jest bowiem potencjalnym zagrożeniem i może prowadzić do powstania tzw. bomb logicznych. To oczywiście zmartwienie koncernów, które mogą nie ufać swoim programistom. Amatorskich produkcji siłą rzeczy to nie dotyczy.
Tak więc umieszczajmy w swoich grach i aplikacjach jak najwięcej jajcarskich pomysłów, póki jeszcze możemy :) Wesołych świąt!
Wiosenne porządki #1 – Odprzedrostkowywanie klasPrzez ostatnich kilkanaście tygodni nie zaglądałem właściwie do kodu swojego siln... tzn. biblioteki (;P). I chociaż entropia kodu, którym nikt się nie zajmuje, nie powinna w zasadzie rosnąć, od czasu do czasu przydadzą się większe lub mniejsze porządki. Zwłaszcza jeśli w międzyczasie zmieniły się nasze poglądy dotyczące tego, jak programować trzeba, a jak nie należy.
Mniej więcej coś takiego przytrafiło mi się jakiś czas temu. Dość długo byłem zwolennikiem tzw. notacji węgierskiej. Jest to pewna reguła odnosząca się do nazw wprowadzanych do kodu źródłowego - takich jak nazwy zmiennych, klas, funkcji, itd. - która postuluje, aby poprzedzać je pewnymi przedrostkami. Prefiksy te mają dostarczać pewnych informacji, mówiąc na przykład, czy mamy do czynienia z klasą, strukturą czy ze zmienną. W tym ostatnim przypadku dodatkowo rozróżniamy też zmienne lokalne od pól klas, a także wprowadzamy przedrostki identyfikujące ich typy. Produktem tych zasad mogą być ostatecznie nazwy typu CFoo::m_pstrVar.
Prawdopodobnie cała ta zabawa była przydatna wtedy, gdy nie dysponowało się zintegrowanymi środowiskami programistycznymi (IDE). Teraz nietrudno jest poznać typ zmiennej: wystarczy najechać kursorem na dowolną nazwę w kodzie, a w podpowiedzi dostaniemy jego deklarację. Proste i praktyczne.
Podejrzewam, że nadmierna sympatia do notacji węgierskiej wzięła się u mnie stąd, że stykałem się z nią dawno temu w kodach napisanych w C i C++, z których - nie ukrywajmy - niewiele wtedy rozumiałem :) Tym więc mogę usprawiedliwić fakt, że bezmyślnie przejąłem ten poroniony w gruncie rzeczy sposób nazewnictwa. Na szczęście nigdy nie jest za późno, aby nawrócić się na właściwą drogę, co też niniejszym czynię ;]
Wyrzucenie wszystkich prefiksów chociażby z nazw zmiennych byłoby sporym zadaniem, zwłaszcza że mówimy o kodzie liczącym przecież 30 tysięcy linii (co jest, przy okazji, bardzo dziwne, jeśli się weźmie pod uwagę jego mizerię funkcjonalną :)). Dlatego rozpoczynam skromnie: od poprawienia nazw wszystkich klas i struktur tak, aby nie były opatrzone żadnymi "ozdobnikami". Nie ukrywam, że w ten sposób staram się upodobnić do konwencji stosowanej w .NET i JVM. Bo przecież dobre wzorce są po to, aby je naśladować...
Przekierowanie standardowych strumieniDawno, dawno temu miałem dość oryginalny pomysł na program użytkowy. Miała to być (o wiele) lepsza wersja konsoli Windows, której wyższość miała się objawiać w wyglądzie oraz możliwościach; pewne inspiracje czerpałem tutaj z terminali linuksowych oraz basha (nie, nie chodzi tu o serwis z cytatami z IRC-a ;P). Jak większość "genialnych" pomysłów, także i ten nie doczekał się realizacji. Przyczyną było to, iż nie za bardzo wówczas wiedziałem, jak można zapewnić, aby aplikacje konsolowe funkcjonowały w ramach projektowanego terminala tak, jak to robią w domyślnym oknie konsoli w Windows. Większość problemu rozbijała się o to, jak pobierać dane przez ów program produkowane i jak przekazywać do niego te wpisywane przez użytkownika.

Konsolka mała i ciasna, ale własna
Jako że było to bardzo dawno temu, nie znałem wtedy jeszcze takich terminów jak 'standardowe wejście/wyjście', 'proces potomny' czy 'pipe', które byłyby tutaj bardzo pomocne. Dowiedziałem się o nich więcej dopiero znacznie później, przy czym wiedza ta w większości odnosiła się do systemu zgoła innego niż Windows :)
Pomyślałem jednak, że do problemu można by wrócić - zwłaszcza, że pytanie o to, jak uruchomić program z przekierowanym wejściem i wyjściem, pojawia się stosunkowo często. Okazało się, że nie jest to specjalnie trudne i sprowadza się właściwie do trzech kroków - z których tylko pierwszy może być nieco zakręcony. Cała procedura (w Windows) może wyglądać na przykład tak:
CreatePipe), które posłużą nam do odbierania wyjścia od i dostarczania wejścia do procesu potomnego. Należy przy tym zwrócić uwagę na dwie rzeczy:
SECURITY_ATTRIBUTES (tak, tej którą w 95% przypadków się ignoruje) i przekazując ją do funkcji tworzącej pipe.CreateProcess). Musimy mu podać właściwe końce rurek w strukturze STARTUPINFO oraz poinstruować Windows, by były one wykorzystywane (flaga STARTF_USESTDHANDLES). Ponadto musimy wskazać, że chcemy dziedziczyć uchwyty i że utworzony proces konsolowy nie powinien pokazywać okienka (CREATE_NO_WINDOW) - to już przekazujemy w parametrach CreateProcess.WaitForMultipleObjects. Natomiast organizacja wejścia zależy już od naszej aplikacji. Warto pamiętać, że w standardowej konsoli jest ono buforowane wierszami, zatem i my powinniśmy wysyłać coś do procesu potomnego dopiero wtedy, gdy zbierzemy całą linijkę danych wejściowych.Łatwe, prawda? ;-) W innych systemach operacyjnych robi się to minimalnie inaczej (pipe, fork, dup2, ...), ale ogólna idea jest podobna. Jak widać, kilkoma małymi rurkami można zdziałać całkiem sporo :]