Wprawdzie niniejszy blog jest devlogiem (a przynajmniej takie są założenia), ale chyba mogę być usprawiedliwiony, jeżeli czasem zajmę się czymś innym. Przecież podobno nie samym programowaniem człowiek żyje i załóżmy przynajmniej na potrzeby dzisiejszej notki, że jest to prawda :)
Chciałbym mianowicie powiedzieć co nieco o pewnej zupełnie niekoderskiej książce, jaką miałem okazję ostatnio przeczytać, czyli o
Wróćmy jednak do Kroniki, uważanej za największe jak dotąd osiągnięcie Murakamiego. Fakt ten od razu czytelnikowi coś narzuca, lecz zanim wyrażę jakiekolwiek opinie, powinienem chyba przedstawić pokrótce fabułę.
Głównym bohaterem i narratorem powieści jest pewien młody mężczyzna, Toru Okada, mieszkający na przedmieściach Tokio. Razem z nim w niewielkim domu z ogrodem mieszka też jego żona Kumiko oraz kot. Wszystko zaczyna się od tego, iż ten właśnie zwierzak zdaje się zniknąć i to na dobre, a ten z pozoru niezbyt ważny fakt staje się początkiem całej serii niecodziennych zdarzeń. Wkrótce także Kumiko odchodzi z niewyjaśnionych przyczyn i za pośrednictwem brata Noboru Watai żąda rozwodu, zaś w życiu Okady zaczynają się pojawiać się kolejne dziwne postaci. On sam postanawia odnaleźć i odzyskać żonę, lecz sposób, w jaki się do tego zabiera, wydaje się być zupełnie pozbawiony sensu…
Zasadniczo można się stąd domyślić, że sam przebieg akcji nie jest w tej książce najważniejszy. W istocie jest on niezwykle poplątany i łączy świat rzeczywisty ze światem snu, a także różna miejsca, osoby i zdarzenia w przestrzeni i czasie. Najważniejszymi oczkami tej sieci są prawdopodobnie: zaniedbana posesja nieopodal domu państwa Okada (nazywana “Willą Wisielców”) wraz ze znajdującą się na jej terenie studnią, bitwa pod Nomonhan i starcia między Japonią i Rosją w czasie II wojny światowej, tajemniczy pokój 208, który Toru Okada często odwiedza w snach, czy wreszcie tytułowy “ptak nakręcacz”. Wszystkie te elementy zdają się mieć ze sobą niewytłumaczalny związek i wpływać na siebie nawzajem.
Powyższe ‘streszczenie’ wydaje się pewnie dość nieudolne, ponieważ tak naprawdę każda próba skondensowania treści Kroniki ptaka nakręcacza będzie skazana na niepowodzenie. I o ile trudno jest uchwycić główną oś czy przesłanie powieści (jeżeli takowe istnieje), o tyle łatwo jest zanurzyć się w jej skomplikowanym świecie. Pomaga w tym znakomita narracja, która jest jednocześnie oszczędna i refleksyjna; z jednej strony realistyczna, a z drugiej przesiąknięta mnóstwem wątpliwych i fantastycznych motywów. Żeby dopełnić tego paradoksalnego obrazu stwierdzę jeszcze, że mimo pozornej łatwości, z jaką można pochłaniać kolejne strony, powieść stawia czytelnikowi spory opór. A podobno tylko książki, które to robią, są naprawdę wartościowe.
Cóż, ostatecznie mogę powiedzieć, że opinia krążąca o Kronice ptaka nakręcacza na pewno nie jest bezpodstawna. Nie wiem, czy mógłbym polecić ją każdemu, gdyż jej urok w dużej mierze zależy od indywidualnego gustu – choćby tego, czy lubimy takie wybitnie niejasne historie. Ja sam przeczytałem tę powieść z dużą przyjemnością i choć zajęło mi to sporo czasu, na pewno nie był on stracony.
PS. Niniejszą notkę dedykuję Wachowi, który nie wiedzieć czemu uważa mnie ostatnio za autorytet w dziedzinie literatury pięknej ;)
Pisać programy można na wiele sposobów, jako że obecnie mamy całe mnóstwo różnych języków programowania przeznaczonych dla różnych zastosowań i gustów. Wśród wyjątkowo pozycję zajmują jednak, które wcale nie dążą do tego, aby być wygodne, użyteczne, efektywne czy mieć inne podobne zalety. Otóż zwykle jest wręcz przeciwnie.
Chodzi mi o języki ezoteryczne. Ciężko za bardzo powiedzieć, do czego faktycznie one służą, ale jedno jest pewne: są one bardzo dziwne i przez to całkiem interesujące :)
Jednym z takich nietypowych języków jest twór o przeuroczej nazwie Brainfuck. Jego cechą szczególna jest mała liczba dostępnych instrukcji, których jest tylko osiem. Mimo to język jest zupełny w sensie Turinga, co z grubsza znaczy, iż można w nim zakodować dowolny algorytm.
Jak zatem wygląda w nim chociażby tradycyjny Hello World? Ano mniej więcej tak:
Dość nieoczywiste, prawda? :) Na pewno pomaga tu wyjaśnienie, że w tym języku wykonujemy wszystkie operacje przy pomocy wskaźnika skaczącego po wirtualnej pamięci. Wskaźnik ten można inkrementować i dekrementować (instrukcje >
i <
), przechodząc do kolejnych komórek; to samo można też robić z zawartością pamięci (+
i -
). W końcu jest też możliwość realizacji jednego rodzaju pętli (a co za tym idzie także instrukcji warunkowych) oraz wejścia/wyjścia dla pojedynczych znaków. Innymi słowy mamy wszystko, co jest programiście potrzebne do szczęścia :)
Nazwa języka Brainfuck sugeruje aczkolwiek, że programowanie w nim nie jest zbyt proste i chyba powyższy kod dobrze o tym świadczy. Lecz tak naprawdę BF jest jednym z prostszych języków ezoterycznych! Dane są w nim oddzielone od kodu, pamięć jest jednowymiarową tablicą (taką jak w rzeczywistych komputerach), a wszystkie instrukcje są sprecyzowane jednoznacznie i zawsze takie same, Czy może być inaczej?
Odpowiedź jest naturalnie twierdząca, a świadczy o tym przykład języka Malbolge - uważanego powszechnie za najtrudniejszy istniejący obecnie język programowania. Jest on tak trudny, że program Hello World został w nim napisany nie bezpośrednio przez człowieka, lecz przy pomocy... specjalnie spreparowanego algorytmu genetycznego! To wszystko dlatego, że w kodzie napisanym w tym języku występuje nieprawdopodobna liczba zależności między instrukcjami. Wykonują one na przykład inne czynności w zależności od adresu pamięci, pod którym się znajdują (jako że dane i kod są umieszczone oczywiście w tym samym obszarze wirtualnej pamięci). Dodatkowo niemal wszystkie instrukcje po wykonaniu same się modyfikują, i to oczywiście zgodnie z trudną do ogarnięcia permutacją. Prawda, że programowanie w czymś takim to nie byłaby bułka z masłem? ;]
A jednak nawet w takim pokręconym języku ktoś wykonał drugie popularne zadanie programistyczne, czyli napisał program wypisujący słowa piosenki 99 Bottles of Beer. W większości normalnych języków sprawa zajęłaby najwyżej kilka minut, ale tutaj była to kwestia ośmiu... lat :)
Ale to jest właśnie urok języków ezoterycznych oraz ich sens, którym jest całkowity brak sensownego zastosowania/ Albo raczej konieczność włożenia ogromnego wysiłku, jeśli komuś przyszłoby do głowy, by tego typu język rzeczywiście do czegoś sensownego zastosować. I o to tutaj chodzi.
Kontakt z takimi wynalazkami ma jeszcze jedną pozytywną cechę. Otóż widząc przykłady napisanego w nich kodu mamy wielką ochotę podziękować twórcom "normalnych" języków programowania, że napisanie w nich interpretera czy kompilatora języka ezoterycznego jest zwykle przynajmniej kilka razy łatwiejsze niż stworzenie programu Hello World w takim właśnie języku :)
Z dobrodziejstwa metod wirtualnych po prostu nie można nie korzystać. Dzięki nim kod jest bardziej elegancki, krótki, często (wbrew powszechnej opinii) efektywniejszy i naturalnie bardziej obiektowy :) Wszystkie te zalety opierają się oczywiście na tym, że tak naprawdę nie musimy wiedzieć, jaką wersję metody wirtualnej – oryginalną czy nadpisaną w klasie pochodnej – wywołujemy w danym przypadku.
Sama metoda aczkolwiek ‘wie’ to doskonale. Czasami zdarza się jednak, że chcielibyśmy wywołać jej odziedziczoną wersję, pochodzącą z klasy bazowej. Podobnie jak większość języków, C++ nie czyni tego automatycznie (z wyjątkiem konstruktorów i destruktorów), jako że nie zawsze jest to potrzebne. Ale nierzadko się przydaje i jest wygodne.
W wielu językach, jak choćby Delphi czy C#, mamy pomocnicze słowa kluczowe, służące do takich właśnie wywołań. W przeciwieństwie do nich C++ oferuje jednak dziedziczenie wielokrotne, wobec tego czasami klasa bazowa nie jest określona jednoznacznie. Dlatego też chcąc wywołać odziedziczoną wersję metody, musimy jawnie użyć nazwy tej klasy., np.:
Wielodziedziczenia używamy jednak rzadko i w zdecydowanej większości sytuacji klasa bazowa będzie tylko jedna. Na takie okazje Visual C++ przewidział własne słowo kluczowe __super
. Możemy też pokusić się o bardziej przenośne rozwiązanie, definiując taki oto szablon:
a wówczas zyskamy swoje własne “słowo kluczowe” base
o możliwościach podobnych do tych z C#:
Na nieszczęście jest tu mnóstwo różnych “ale”. Największym problemem jest to, że właściwej klasy bazowej (tutaj CFoo
) nie ma jak zainicjalizować w klasie pochodnej, wobec czego musi ona dysponować domyślnym konstruktorem, który na dodatek będzie używany zawsze. To poważny feler, którego nie ma za bardzo jak naprawić. Dlatego jeśli bardzo doskwiera nam brak słowa base
, to chyba jedynym sposobem jest… ręczne dodawanie typedef
a podobnego do tego w szablonie Inherits
.
Dopiero C++0x wprowadzi możliwość dziedziczenia konstruktorów (na zasadzie przekierowywania ich parametrów do klasy bazowej), która pozwoli wyeliminować wspomniane ograniczenie. Wówczas taka wersja szablonu:
powinna zdać egzamin dla dowolnej klasy T
.
Internet powstał dobrych kilka dziesięcioleci temu, lecz jego obecnie najważniejsza część – czyli World Wide Web – liczy sobie “jedynie” około dwudziestu lat. Od momentu jej wynalezienia sukcesywnie jednak zyskuje na znaczeniu i można wręcz powiedzieć, że powoli wypiera niektóre z pozostałych usług sieciowych.
Już kilka lat temu dla wielu osób (szczególnie nowicjuszy) wyrazy ‘Internet’ i ‘WWW’ były właściwie synonimami, a przeglądanie stron główną formą sieciowej aktywności. Od tamtej pory ten trend coraz bardziej się nasila i strony WWW może nie tyle zastępują, co “połykają” inne usługi internetowe.
Weźmy taki webmail, czyli witryny służące dostępowi konta e-mail z poziomu przeglądarki. Bardzo często z powodzeniem zastępują one tradycyjne programy pocztowe, pozwalając na wykonywanie wszystkich typowych czynności związanych z pocztą. Oprócz tego mamy też wszelkiego rodzaju internetowe czaty, których większość jest dostępna w formie apletów Javy osadzonych na stronach WWW. W końcu nie sposób nie wspomnieć o forach dyskusyjnych, które stały się poważną alternatywą dla tradycyjnych grup Usenetowych.
Szczególnie Google przoduje w tym “uprzeglądarkowianiu”. Zaczęło się od GMaila, początkowo dostępnego tylko z poziomu strony WWW, zaś ostatnim wynalazkiem z tej serii jest Google Docs – czyli niemalże cały pakiet biurowy, w całości w formie zdalnej strony internetowej. Wszystko to jest oczywiście oznaczone magicznym słówkiem AJAX, które bynajmniej nie oznacza tutaj nazwy płynu do mycia szyb :)
Z jednej strony mamy więc witryny internetowe, które zachowują się jak programy. Z drugiej strony narzędzia służące do korzystania z nich – czyli przeglądarki – same stają się coraz bardziej rozbudowane. Standardem jest już wbudowany menedżer zarządzający pobieranymi plikami czy czytnik kanałów RSS; niektóre przeglądarki oferują znacznie więcej. Rekordy pod tym względem bije Opera, o której zwykłem mawiać, że chyba tylko prać i prasować jeszcze nie potrafi ;P
Czy taki kierunek ewolucji WWW i Internetu jest korzystny? Z jednej strony możliwość korzystania z wszystkich dobrodziejstw sieci (i nie tylko sieci) bez “ruszania się” z przeglądarki jest wygodna. Osobiście uważam jednak, że przeglądarki stron WWW powinny robić przede wszystkim to, na co wskazuje ich nazwa, zaś ich rozwój powinien polegać na dążeniu do jak najlepszego spełnienia standardów sieciowych, zwiększenia wygody użytkownika czy efektywności. To ostatnie jest szczególne istotne, gdyż nowe aplikacje webowe są coraz bardziej skomplikowane i zużywają coraz więcej zasobów systemowych.
A to jeszcze nie wszystko. Przecież skoro przez WWW mogą działać choćby edytory tekstu, to dlaczego nie… systemy operacyjne? I choć takie wynalazki jak ajaxWindows są póki co tylko ciekawostkami, kto wie, dokąd nas ostatecznie zaprowadzi ta przeglądarkomania…
Zakończyłem ostatnio pewien etap w tworzeniu silnika, czyli prace nad własnym systemem graficznego interfejsu. Taka okazja jest dość dobra dla wykorzystania napisanego już kodu i złożenia z niego tzw. techdema.
Takie demo nie jest naturalnie tym samym co wersja demonstracyjna gry (na przykład dlatego, że chwilowo żadnej gry nie piszę :)) czy demo scenowe. Jego celem jest prezentacja aktualnych możliwości silnika lub jakiegoś fragmentu jego funkcjonalności.
Czy jest sens pisania takich dem? Jak zwykle można podać przynajmniej kilka argumentów przemawiających tak za, jak i przeciwko. In plus możemy zaliczyć techdemom, że:
Tyle zalet. Są też jednak argumenty drugiej strony, iż:
A dodatkowo tworzenie dem, publikowanie screenów i tym podobne działania są przejawem tej silnikologii, do której, szczerze mówiąc, nie jestem jeszcze tak do końca przekonany :) Ale to już temat na inną okazję.
W każdym razie dopóki nie zajmę się na poważnie grafiką 3D i dopóki nie będzie w tej dziedzinie widać jakichś efektów, nie mam chyba za bardzo się czym chwalić ;P
Kto koduje trochę dłużej z użyciem technik programowania obiektowego, ten zapewne zna ideę wzorców projektowych. Są one czymś w rodzaju przepisów na określone klasy i związki między nimi, które rozwiązują konkretne problemy. Przykładowo znany wzorzec Iterator stwarza uniwersalny sposób na dostęp do elementów kolekcji – niezależnie od tego, czy jest to lista, drzewo, zwykła tablica, itp.
Ale oprócz tych pożytecznych wzorców istnieją też… antywzorce. Zdecydowanie nie są one instrukcją, jak pewne fragmenty programów pisać. O nie, wręcz przeciwnie – mówią one, jak ich nie pisać i pokazują typowe oraz mniej typowe błędy popełnione we wszystkich fazach tworzenia oprogramowania.
Więcej o nich można poczytać choćby w Wikipedii. Zamieszczona tam lista jest niezwykle obszerna, ale trudno się dziwić – mówimy przecież o sposobach, jak zrobić coś źle, a w tej dziedzinie ludzkość ma wybitne osiągnięcia :)
Do ciekawszych (i pouczających) przykładów takich antywzorców należą chociażby:
switch
– niezwykle “pomysłowy” sposób na zakodowanie kilku następujących po sobie czynności przy pomocy pętli i instrukcji wyboru, która zależnie od tego czy jest to 1., 2., czy N-ta iteracja wykonuje jakieś z góry ustalone czynności. Przyznam, że gdy to pierwszy raz zobaczyłem, nie mogłem wprost uwierzyć, że ktoś może wpaść na coś tak niedorzecznegoJak widać ten antywzorce dotyczą wielu różnych aspektów programowania i projektowania, i czasami ich niepoprawność wcale nie jest taka oczywista. (Chociaż na pewno ostatnia pozycja z listy powyżej jest aż nazbyt oczywista). Warto zatem zapoznać się z nimi; dzięki temu możemy znaleźć inspirację zarówno do unikania błędów, jak i ich popełniania ;]
Wyobraźmy sobie, że kończymy właśnie dużą partię kodu – kilkadziesiąt albo kilkaset linijek. Zamykamy ostatnią klamerkę (ewentualnie piszemy end
lub coś w tym guście), stawiamy ostatni średnik. Potem jeszcze tylko kilkukrotne przeciąganie suwaka paska przewijania, kilka pobieżnych rzutów oka na powstały listing i… kompilujemy.
Co się wtedy dzieje? Ano zwykle w tym momencie zatrzymuje nas jakiś trywialny błąd składniowy :) W sumie nic wielkiego, jakiś brak średnika albo powtórzony nawias; poprawiamy i próbujemy dalej. Po kilku razach w końcu zaakceptuje nasz program w całości – i zwykle dopiero wtedy zaczyna się prawdziwe debugowanie :]
Nie da się ukryć, ze czas tuż przed i w czasie kompilacji może być dość stresujący. Niby należałoby się już cieszyć, że oto wykonaliśmy jakiś (zwykle niewielki) kroczek na drodze do działającej całości. Zwłaszcza jeśli przez długi czas kod był rozgrzebany i nie dało się kompilować, domknięcie go z powrotem do kompilowalnej całości – uzupełnionej zapewne o nowe funkcje i możliwości – jest bardzo satysfakcjonujące.
Jednocześnie jednak to chwila próby. Pisanie kodu to w pewnym stopniu akt twórczy, ale jego kompilacja to proces najzupełniej prozaiczny, który sprawdza, jak nasze pomysły wypadają pod względem poprawności językowej. I jakby tego było mało, kod skompilowany na początku jest zwykle bardzo odległy od kodu poprawnego. Kompilacja jest więc zapowiedzią kolejnej, jeszcze “przyjemniejszej” fazy programowania – testów.
Jak zatem załagodzić kompilacyjny stres? Myślę, że należy wypracować sobie odpowiadającą nam częstotliwość przeprowadzanych kompilacji. Jeżeli bowiem będziemy przeprowadzali je zbyt rzadko, wówczas każda próba będzie trwała dłużej (więcej kodu = więcej błędów składniowych i tym podobnych), a w wynikowym kodzie ukrytych będzie więcej błędów do usunięcia podczas debugowania. Z drugiej strony trudno jednak kompilować i testować każdą drobnostkę, bo można całkowicie utonąć w szczegółach.
Czyli mówiąc wprost: ciężkie jest życie programisty :) W miarę nabywania doświadczenia okazuje się aczkolwiek, że potrafimy bez większych problemów doprowadzić do kompilacji coraz dłuższe partie kodu, w których w rezultacie nie ukrywa się już tak wiele błędów jak wcześniej. W programowaniu ważne jest bowiem, aby nie zrażać się pierwszymi (ani następnymi) niepowodzeniami.