Zdjęcia z wyjazdu do Zurychu
Zamieściłem galerię zdjęć, które wykonałem podczas wspomnianego parę dni temu wyjazdu do Zurychu. Niestety najbardziej interesującej części – czyli wnętrza siedziby Google – nie da się fotografować :( Reszta jednak też jest chyba całkiem interesująca… no, przynajmniej mam taką nadzieję :)
Niejako przy okazji zdecydowałem się na przeniesienie galerii do serwisu Picasa. Zrobiłem tak głównie dlatego, iż wtyczka do WordPressa, której używałem do tej pory, zaczęła mnie nieco irytować pod względem łatwości użytkowania (a raczej jej braku).
Uploadowanie zdjęć na Picasę i ich organizowanie jest z drugiej całkiem łatwe i przyjemne. Jak na razie jedyną wadą, którą tam znalazłem, jest brak możliwości hierarchicznego grupowania albumów w kategorie. Wydaje mi się jednak, że przy tej mizernej ilości obrazków i zdjęć, które zamieszczam, nie będzie ona żadnym problemem :)
Jeśli dziś wtorek, to jesteśmy w SzwajcariiTak się akurat złożyło, że po zakończeniu konferencji IGK w Siedlcach nie było mi dane wrócić prosto do domu. To znaczy formalnie wróciłem na jakąś godzinę, ażeby potem jednak udać się w kolejną podróż. Tym razem trochę dalszą. Dokładniej mówiąc: do Zurychu.
Wyprawa ta charakter częściowo turystyczny, do czego – jak dość szybko zdążyłem zauważyć – miejsce to nadaje się wręcz doskonale. Miasteczko (bo miastem raczej bym tego mimo wszystko nie nazwał ;P) położone jest w urokliwej alpejskiej dolince. Ma na wyposażeniu długie jezioro przecinane trasami promów i otoczone wyrastającymi ze zboczy domkami. Pora roku nie jest wprawdzie odpowiednia ani na sporty zimowe ani wodne (panuje tu obecnie wczesno-środkowa wiosna), ale i tak jest tu bardzo przyjemnie. Okolica wybitnie nadaje się na długie spacery, z czego pewnie postaram się w wolnych chwilach korzystać :)
Tych wolnych chwil będzie w sumie sporo, bo zasadniczy biznes będący przyczyną mojej wizyty w Szwajcarii mam już właściwie za sobą. Wizyta w siedzibie Google to jedno z ciekawszych wydarzeń, jakie miałem okazję ostatnio doświadczyć i pewnie zapamiętam ja na długo… Chyba że jakimś dziwnym zrządzeniem układów gwiazd uda mi się tam powrócić, tym razem na dłużej ;-) Tak czy siak polecam wybranie się tam każdemu, kto tylko ma ku temu okazję. Samo pobieżne rozejrzenie się po różnych zakamarkach budynku (a właściwie trzech) było warte spędzenia półtorej godziny w ciasnym fotelu w samolocie ;)
Powtórka z rozrywkiKolejna konferencja IGK zakończyła się wczoraj tradycyjnym konkursem zespołowego programowania gier Compo. Podobnie jak w zeszłym roku startowało wiele zespołów, w tym także i mój, czyli Rzeźnicy Inc. ;) I podobnie jak zawsze, wszyscy starali się zakodować jak najlepiej wyglądające, najbardziej grywalne, najoryginalniejsze i w ogóle najlepsze gry. Naturalnie nie wszystkim się to udało, ale akurat w przypadku mojej drużyny rezultat był chyba całkiem niezły. W każdym razie zasłużył na drugie miejsce, czyli analogicznie jak w zeszłym roku. Krótko mówiąc: trzymamy poziom :)
W najbliższych dniach postaram się zamieścić na stronie jakąś grywalną wersję naszej pracy.
IGK nr 1<<3
Po raz kolejny uczestniczę w konferencji Inżynierii Gier Komputerowych w Siedlcach. Tradycyjnie trwa ona trzy dni (no, dwa i pół) i składa się z dwóch dni wykładów oraz konkursu drużynowego programowania gier. W zeszłym roku moja drużyna sprawiła się całkiem dobrze, więc mam nadzieję, że w tym będzie przynajmniej podobnie :)
Oprócz tego istnieje też szansa na kilka ciekawych wykładów, nie wspominając już o LAN-owym afterparty na koniec ;-)
Moda na krytykowanie OOP-uNauczyłem się już lubić fakt, że w przypadku informatyki powiedzenie o “ciekawych czasach” jest truizmem, bo ciekawie jest po prostu zawsze – głównie ze względu na tempo zmian w wielu dziedzinach. Nawet w tych, wydawałoby się, zastygłych na lata. Niecałe trzy lata temu zżymałem się na przykład na zbytnią ufność w doskonałość obiektowych metod programowania. Dzisiaj zaś przychodzi mi robić coś zdecydowanie przeciwnego.
Programowanie obiektowe jest obecnie sztandarowym kozłem ofiarnym i chłopcem do bicia, otrzymującym ciosy z wielu stron. Już nie tylko programiści gier twierdzą, że nie mogą sobie na nie pozwolić ze względu na wydajność i zamiast niego forsują Data Oriented Design. Pokazywałem niedawno, że sprzeczność między tymi dwoma podejściami jest raczej pozorna niż rzeczywista. Teraz natknąłem się na interesującą opinię, która podważa sens OOP-u jako metodologii, wychodząc z nieco innego punktu widzenia niż wydajność dla celów grafiki real-time:
Object-oriented programming (…) is both anti-modular and anti-parallel by its very nature, and hence unsuitable for a modern CS curriculum. [pogrubienie moje]
Anty-modularne i anty-współbieżne? Oczywiście; da się napisać kod obiektowy, który te dwa warunki będzie spełniał doskonale. Ale to nie oznacza, że każdy kod obiektowy je spełnia, a to właśnie jest implikowane powyżej. Nie da się tego określić inaczej niż jako stereotyp – i to w modelowej wersji, czyli negatywnego uogólnienia z pojedynczych przypadków.
Jako antidotum na te rzekome bolączki OOP-u często wymieniane jest programowanie funkcyjne. Nie ujmując mu niczego ze swojej elegancji, nie mogę jednak nie zauważyć, że zamiata ono wiele problemów pod dywan. Określanie wykonania programu jako serii transformacji danych nie rozwiązuje jednak problemu: gdzie i jak te dane mają być zapisywane i chronione przed równoczesnym dostępem z wielu ścieżek wykonania. Sytuacje, w których programowanie funkcyjne lub quasi-funkcyjne sprawdza się dobrze to takie, gdzie problemy te dały się w miarę łatwo rozwiązać. Tak jest chociażby w przypadku vertex i pixel shaderów, gdzie podział danych wejściowych i wyjściowych na rozłączne bloki jest wręcz naturalny. Fakt ten nie jest jednak zasługą programowania funkcyjnego, tylko natury zagadnienia – w tym przypadku renderowania grafiki opartej na wielokątach.
I właśnie o tym powinniśmy pamiętać, gdy wyzłośliwiamy się nie tylko na OOP, ale dowolny inny paradygmat programowania. Otóż porzucenie go nie sprawi od razu, że magicznie zaczniemy pisać kod doskonale modularny. A już nie pewno nie spowoduje, że niezwykle trudne zagadnienia współbieżności staną się nagle banalnie proste. To niestety tak nie działa.
Nie znaczy to oczywiście, że nie powinniśmy poszukiwać nowych, lepszych metodologii do konkretnych zastosowań. Dlatego przecież wiele języków (np. C++, C#, Python) ewoluuje w kierunku wieloparadygmatowości, aby możliwe było dobranie właściwych narzędzi dla danej sytuacji. Nie wydaje mi się jednak, aby uleganie trendy nurtom krytykowania jakichkolwiek rozwiązań poprzez odwoływanie się do stereotypów i nieuzasadnionych wyobrażeń o nich było w tym procesie specjalnie produktywne. Zdaję sobie jednak sprawę, że “funkcje wirtualne to zuo!” brzmi lepiej niż “wywoływanie funkcji wirtualnych skutkuje narzutem wydajnościowym związanym z dodatkowym adresowaniem pamięci (które nie jest cache-friendly) i może powodować niepożądane skutki uboczne, jeśli ich wersje w klasach pochodnych nie są thread-safe“. Mam jednak nadzieję, iż nikt nie ma wątpliwości, które z tych dwóch stwierdzeń jest bardziej racjonalne.
Podziękowania dla Rega za podesłanie linków, które zainspirowały mnie do podjęcia tego tematu.
Importy niezupełnie z zagranicyPrzeglądając plik źródłowy programu w dowolnym niemal języku, gdzieś bardzo blisko początku znajdziemy zawsze region z importami. Niekoniecznie będą one oznaczone słowem kluczowym import – czasem to będzie using, być może do spółki z #include – ale zawsze będą robiły zasadniczo to samo. Chodzi o poinformowanie kompilatora lub interpretera, że w tym pliku z kodem używamy takich-a-takich funkcji/klas/itp. z takich-a-takich modułów/pakietów. Dzięki temu “obce” nazwy użyte w dalszej części będą mogły być połączone z symbolami zdefiniowanymi gdzie indziej.
Każdy import w jakiś sposób rozszerza więc przestrzeń nazw danego modułu i zazwyczaj wszystko jest w porządku, dopóki dokładnie wiemy, jak to robi. Dlatego też powszechnie niezalecane są “dzikie” importy (wild imports), które nie wyliczają jawnie wszystkich dodawanych nazw, zwykle ukrywając je za gwiazdką (*). Ale nawet jeśli ich nie używamy, to nie oznacza to, że żadne problemy z importowanymi nazwami nas nie spotkają. Oto kilka innych potencjalnych źródeł kłopotów:
import foo.bar.baz; wprowadza do przestrzeni modułu nazwę baz (czyli niekwalifikowaną) w przypadku Javy. W przypadku Pythona ten sam efekt wymaga z kolei instrukcji from foo.bar import baz, a zwykła instrukcja import da nam jedynie kwalifikowaną nazwę foo.bar.baz – która z kolei w Javie i C# jest dostępna bez żadnych importów, a w C++ po dodaniu dyrektywy #include… Całkiem intuicyjne, czyż nie? ;-) Skoro tak, to dodajmy do tego jeszcze fakt, iż…Podsumowując, importy – chociaż często zarządzane prawie całkowicie przez IDE – to w sumie dość poważna sprawa i warto zwrócić na nie uwagę przynajmniej od czasu do czasu.
Najboleśniejszy strzał w stopęZnanych jest mnóstwo kruczków w języku C++, o których trzeba pamiętać, jeśli chcemy efektywnie w nim programować i nie tracić zbyt dużo czasu na odczytywanie niespecjalnie zrozumiałych komunikatów kompilatora czy - gorzej - drapanie się po głowie podczas debugowania naszego programu. O wielu z nich miałem okazję pisać, ale oczywiście ten temat-rzeka daleki jest od wyczerpania :) W jego nurcie wyróżnia się jednak jeden wybitnie złośliwy przypadek, który przez długi czas uważałem aczkolwiek za ciekawostkę, na którą w praktyce raczej nikt się nie natknie...
Rzecz jasna, myliłem się. Okazuje się, że okaz ten jak najbardziej występuje w rzeczywistym świecie. Nie objawia się wprawdzie zbyt często, ale dzięki temu jest jeszcze bardziej podstępny, posiadając tym większą siłę rażenia, gdy w końcu na kogoś trafi. Dobrze więc wiedzieć, co robić, aby się przed nim bronić :] I tym właśnie chciałbym się dzisiaj zająć.
Pewną pomocą jest tutaj fakt, iż opisywany błąd zdaje się zazwyczaj pojawiać w pewnym konkretnym scenariuszu. Jego wystąpienie w owym kontekście jest przy tym tak zaskakujące, że zetknięcie się z nim skutecznie "uodparnia" na wszelkie inne, podobne okoliczności w których problem może wystąpić. Rzeczony scenariusz jest przy tym bardzo typowy: chodzi o wczytanie zawartości pliku (otwartego jako strumień std::ifstream) do kolekcji albo zmiennej, na przykład łańcucha znaków typu std::string.
Są naturalnie tacy, co bawiliby się tutaj w bufor pomocniczy, pętlę i funkcję getline lub coś w tym guście. Programiści lubiący operować na nieco wyższym poziomie wiedzą jednak, że std::string możemy inicjalizować zakresem znaków określonym - jak każdy zakres w C++ - parą iteratorów. Trochę mniej znanym faktem jest z kolei to, że iterować można również po strumieniach. Mamy na przykład coś takiego jak std::istream_iterator, który potrafi automatycznie dekodować ze strumienia egzemplarze ustalonego typu danych:
Jeśli nam to nie odpowiada i wolimy dostęp do "surowych" bajtów, wtedy z pomocą przychodzi bardziej wewnętrzny std::istreambuf_iterator. To właśnie przy jego pomocy możemy szybko przejść po zawartości strumienia plikowego i umieścić ją w stringu:
Jak pewnie można się domyślić, zakres zdefiniowany przez te iteratory w obu przypadkach zaczyna się na początku strumienia. Kończy się zaś w miejscu, gdzie odczyt następnej porcji danych nie jest już możliwy. W naszym "rozwiązaniu" problemu wczytywania całego pliku będzie to więc koniec tego pliku, czyli to co nam chodzi.
Nieprzypadkowo jednak słowo 'rozwiązanie' ująłem w cudzysłów. Powyższe dwie instrukcje zawierają bowiem ów wyjątkowo perfidny błąd, o którym wspominałem na początku. Dość powiedzieć, że jeśli spowoduje on wygenerowanie przez kompilator bardzo tajemniczego komunikatu, będzie to lepszy z jego dwóch możliwych rezultatów. Drugim jest kompilacja zakończona powodzeniem i... kod, który robi dokładnie nic. Nie tylko nic nie wczytuje, ale nawet nie tworzy zmiennej typu string! To raczej zaskakujące, nieprawdaż? ;)