Posts from 2010

Niestatyczne klasy wewnętrzne w praktyce

2010-08-16 20:35

Postawiłbym tezę, że każdy szerzej używany język programowania ma ok. jedną wyróżniającą cechę, definiującą w znacznym stopniu sposób, w jaki się go używa. Innymi słowy, każdy język z czymś się kojarzy. I tak myśląc o C, przypominamy sobie od razu wskaźniki; w Pythonie przychodzi nam na myśl składnia oparta na wcięciach; Pascal kojarzy nam się natychmiast z begin/end; w LISP-ie mamy miliardy nawiasów; w C++ przeciążanie operatorów; w PHP kod przeplatany wstawkami HTML – i tak dalej. Chyba tylko C# jest – przynajmniej dla mnie – swego rodzaju wyjątkiem w tej kwestii (co niekoniecznie musi być złe, bo oznacza również brak wyraźnie irytujących feature‘ów).

Dzisiaj jednak chciałem napisać o Javie z tego względu, że przyszło mi ostatnio kodować nieco w tym języku. Wrażenie, jakie w związku z tym odnoszę, jest takie, iż Java to obecnie chyba najbardziej “klasycznie obiektowy” język ze wszystkich, które mają w branży jakieś znaczenie. Tu nie ma żadnych udziwnień, które łamałyby paradygmat OOP-u, na który składają się m.in. obiekty dostępne przez referencje, komunikacja za pomocą interfejsów i metod, wyraźny podział na proste typy wbudowane i złożone klasy, jawne tworzenie kopii obiektów, i tak dalej. Z jakichś powodów “wynalazki” w rodzaju typów generycznych czy wyrażeń lambda przebijają się do Javy bardzo, bardzo powoli.
Do tej listy trzeba też dopisać jakąkolwiek formę wskaźników na funkcje, przydatną w implementacji callbacków, czy też delegatów przeznaczonych do obsługi zdarzeń. Zamiast tego javowe API muszą uciekać się do (znów wybitnie OOP-owego) implementowania ustalonych interfejsów i polimorficznych wywołań metod. Do tego jednak język posiada unikalny feature, który – przynajmniej w założeniu – ma ten proces wydatnie ułatwiać. To właśnie tytułowe niestatyczne klasy wewnętrzne (inner classes).

Idea jest prosta. Jeśli klasę B umieścimy wewnątrz klasy A, to zabieg ten w Javie będzie miał skutek nie tylko dla widoczności tej pierwszej. Klasa B będzie wtedy klasą wewnętrzną A także w tym sensie, że każdy jej obiekt będzie zawsze związany z jakimś obiektem klasy A. Tą związanie odbywa się automatycznie i objawia dostępem do składników klasy “otaczającej”:

  1. public class Foo {
  2.     class Bar {
  3.         public void Oyey() { Boo{}; }
  4.     }
  5.     private Bar bar = new Bar();
  6.     public void Boo() { }
  7. }

To w sumie nie jest aż tak imponujące. Wprawnym okiem można łatwo zauważyć, że to w gruncie rzeczy tylko cukierek składniowy dla przekazywania this do konstruktora klasy wewnętrznej i późniejszego odwoływania się do niego. Ciekawiej jest wtedy, gdy mała, wewnętrzna, pomocnicza klasa jest na tyle mała i pomocnicza, że nie warto nawet nadawać jej nazwy, bo potrzeba nam tylko jednego jej obiektu:

  1. public class Foo {
  2.     private ISomeInterface si = new ISomeInterface() {
  3.         @Override
  4.         public void someMethod() { }
  5.     };
  6. }

Wówczas wszystko co o tej klasie wiemy to to, że implementuje ona pewien interfejs i właśnie za jego pośrednictwem możemy się do jej jedynego obiektu odwoływać.

Tak w skrócie wygląda teoria. Z praktycznego punktu widzenia mogę natomiast powiedzieć, że dwojga złego lepiej już te wątpliwej świeżości cukierki składniowe mieć, skoro innego wyjścia nie ma… Mam tu na myśli oczywiście fakt, że wobec braku delegatów/zdarzeń/domknięć/itp. jedynym sposobem na komunikację zwrotną w aplikacjach javowych jest wywoływanie metod z ustalonych interfejsów, implementowanych przez obiekty, które następnie podaje się jako “słuchacze” (listeners). Niestatyczne klasy wewnętrzne zapewniają przynajmniej łatwe połączenie między tymi sztucznie wprowadzonymi obiektami i resztą programu.
Trzeba jednak uważać, by nie zamienić swojego kodu w spaghetti, co może się łatwo zdarzyć, jeśli radośnie wpleciemy definicje klas w treść funkcji. Jest to szczególnie niepożądane w kodzie inicjalizującym np. elementy UI, w którym należy po kolei poustawiać wszystkie listenery dla wszystkich kontrolek. Zdefiniujmy je wszystkie w locie i będziemy mieli całą obsługę zdarzeń w jednej metodzie. Fuj!

Dlatego lepiej już definiować takie pomocnicze obiekty w podobny sposób, jak wyżej – tj. jako pola, którym przypisujemy klasy stworzone ad hoc, zawierające metody z reakcjami na zdarzenia. To oczywiście tylko nędzna imitacja składni prawdziwych procedur zdarzeniowych, ale przynajmniej jest to podróbka akceptowalna.

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

Kod otwarty na zdarzenia

2010-08-13 18:35

Wydaje mi się, że przynajmniej pod kilkoma względami programowanie przypomina prowadzenie samochodu. Obu tych umiejętności względnie łatwo się nauczyć i niemal niemożliwe jest zapomnieć. Po nabyciu pewnej wprawy mamy też wystarczającą biegłość, by nie musieć koncentrować całej uwagi na którejś z tych czynności. Wyjątkiem są jedynie te miejsca, w których zalecane jest zachowanie szczególnej ostrożności.
Dla mnie (i pewnie nie tylko dla mnie) takimi miejscami w kodzie są styki programu z otoczeniem, przez które musi przebiegać jakaś komunikacja polegająca na wymianie danych i/lub informacji o zdarzeniach. Fragmenty te są ważne i nierzadko problematyczne, gdyż często pociągają za sobą konieczność dopasowania sposobu wykonywania programu do zewnętrznych wymagań. Jak na skrzyżowaniu, trzeba czasem chwilę poczekać i przynajmniej parę razy obejrzeć się dookoła.

W tym kontekście używa się słowa ‘synchronizacja’, jednak kojarzy mi się ono nieodparcie z programowaniem współbieżnym. To trochę złe skojarzenie, bowiem nie trzeba wcale tworzyć kilku wątków czy procesów, by kwestia zaczynała mieć znaczenie. Wystarczą operacje wejścia-wyjścia (zwłaszcza względem mediów wolniejszych niż lokalny system plików) lub obsługa jakiegoś rodzaju zdarzeń zewnętrznych, albo chociażby RPC (Remote Procedure Call – zdalne wywoływanie procedur) – ale oczywiście ta lista nie wyczerpuje wszystkich możliwości. Ogólnie chodzi o wszelkie odstępstwa od możliwości wykonywania programu krok po kroku – a właściwie kroczek za kroczkiem, gdzie każda sekwencja stałej liczby instrukcji zajmuje umowną, niezauważalną chwilę.

Jeśli by się nad tym zastanowić przez moment, to takich sytuacji jest sporo. Ba, właściwie to wspomniane scenariusze sekwencyjne są raczej wyjątkiem, a nie regułą. Dzisiejsze aplikacje działają w wielozadaniowych środowiskach, na ograniczonych pulach zasobów, wymieniają dane przez różne (niekoniecznie szybkie) kanały informacji, i jeszcze dodatkowo są pod ciągłą presją wymagań co do cechy określanej angielskim słówkiem responsiveness – czyli “komunikatywności z użytkownikiem”. Nic dziwnego, że wspomniane przeze mnie wcześniej ‘punkty szczególnej ostrożności’ stają się na tyle istotne, że zazwyczaj to wokół nich buduje się całą architekturę programu.
W jaki sposób są one realizowane? Zależy to od wielu czynników, w tym od rodzaju aplikacji oraz możliwości i struktury systemowego API, które ona wykorzystuje. Tym niemniej można wyróżnić kilka schematów, aplikowalnych w wielu sytuacjach – chociaż nie zawsze z równie dobrym skutkiem. Są nimi:

  • Aktywne czekanie (busy waiting, active polling), polegające na ciągłym dopytywaniu się o rezultat operacji czy też informację o tym, czy oczekiwane zdarzenie zaszło. Stosowane w praktyce sprowadza się do pętli podobnej do poniższej:
    1. while (!WaitFinished()) { }

    i jest przestępstwem ściganym z urzędu w każdym rozsądnym zespole projektowym :) Istnieją aczkolwiek okoliczności łagodzące, zezwalające na jego popełnienie pod ściśle określonymi warunkami. Musimy jedynie być pewni, że zużywanie do 100% czasu procesora przez nasz program jest akceptowalne i że między kolejnymi zapytaniami możemy też zrobić coś produktywnego. Tak się składa, że istnieje typ aplikacji, w którym oba te warunki mogą być spełnione: gry. Ich pętla główna to nic innego jak aktywne czekanie na informacje o zdarzeniach z renderowaniem kolejnych klatek w tak zwanym międzyczasie.

  • Na wpół aktywne czekanie (soft busy waiting) to złagodzona wersja powyższego. Nie zasypujemy już systemu żądaniami z prędkością światła, a jedynie co jakiś czas. Pomiędzy nimi program może zająć się swoimi sprawami albo po prostu… spać:
    1. while (!WaitFinished()) { Sleep(50); }

    Takie krótkie drzemki zdecydowanie zmniejszają zużycie procesora przez aplikację, ale nie dają jej dużego pola manewru. Ta metoda jest więc stosowalna główne dla usług działających w tle. A raczej byłaby, gdyby nie istniały znacznie lepsze :)

  • Przerywalne oczekiwanie (interruptible waiting) to stan zawieszenia w oczekiwaniu na zdarzenie (np. koniec długiej operacji), podczas którego możliwa jest jednak reakcja na pewne szczególne okoliczności. Koncepcja takiego oczekiwania jest zapewne dobrze znana programistom linuksowym, jako że tam niemal każda operacja może zostać przedwcześnie przerwana przyjściem sygnału do procesu. Ów sygnał może być złapany i w konsekwencji obsłużony, lecz nie powinno to pociągać za sobą żadnych skomplikowanych operacji.
    Przerywalne oczekiwanie jest akceptowalnym sposobem działania programów funkcjonujących w tle, na przykład jako usługi systemowe. Niespecjalnie za to nadaje się dla aplikacji z UI – chyba że co jakiś krótki czas pozwolimy na obsłużenie wejścia od użytkownika. Wtedy jednak jest to już raczej wariant soft busy waiting.
  • Asynchroniczne wywołanie zwrotne (asynchronous callback) znaczy mniej więcej tyle, co “zrób to i powiedz mi, kiedy skończysz” albo “powiadom mnie, gdy to się zdarzy”. W obu przypadkach musimy dostarczyć mechanizm wywołania zwrotnego (callback), zintegrowany z naszą aplikacją. To dosyć wygodne, jeśli i bez tego działa ona na zasadzie sterowania zdarzeniami (event-driven) i rzeczone wywołanie możemy potraktować jako jeszcze jedną sytuację do obsłużenia. (Dlatego komponent BackgroundWorker z Windows Forms jest tak prosty w użyciu). Gorzej jest wtedy, gdy na potrzeby asynchronicznego callbacku musimy rozbić na kilka części (i stanów) program, który bez tego działałby niemal sekwencyjnie.
    Osobną kwestią jest też sposób realizacji callbacka. W dużej mierze zależy on od możliwości języka. W jednych jest to bardzo wygodne (C#, Python, większość języków skryptowych), w innych nieco mniej (Java), a w jeszcze innych ledwo da się to zrobić (C++, a jakże).

Między powyższymi sposobami możliwe są “konwersje”, oczywiście do pewnego stopnia. Wymagać to może uruchomienia dodatkowego wątku, w którym wykonujemy polling operację asynchroniczną lub wręcz blokującą, i którego stan możemy odpytywać lub otrzymać jako sygnał na obiekcie synchronizacyjnym po wejściu w stan przerywalnego czekania.
Nieczęsto jednak taka zabawa ma uzasadnienie. W najlepszym razie otrzymamy rozwiązanie równoważne, a najgorszym stracimy na wydajności operacji dostosowanej pod konkretny typ powiadamiania. Lepiej jest jednak trzymać się tego, co dana platforma i API nam proponuje.

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

Jak się uczyć nowego API

2010-08-10 20:26

Poznawanie nowych narzędzi programistycznych, bibliotek czy nawet platform to częsta konieczność w pracy kodera. Najczęściej jest to środek do osiągnięcia jakiegoś konkretnego celu projektowego, ale nierzadko możemy też zechcieć zagłębić się w nowe API dla samej przyjemności jego poznania. Jak się do tego zabrać? Są różne sposoby.

Możemy na przykład próbować jak najwięcej się o danej bibliotece dowiedzieć. W dzisiejszych czasach nie jest to trudne, bo standardem jest posiadanie dobrej (albo chociaż jakiejkolwiek ;]) dokumentacji. Ponadto wokół niemal każdej choć śladowo popularnej technologii natychmiast wyrastają internetowe społeczności, których dyskusje na forach (rzadziej już na grupach dyskusyjnych) mogą być również cennym źródłem informacji – zwłaszcza w przypadku wystąpienia problemów.
Postępując w ten sposób możemy zyskać bardzo szeroki i głęboki (jak morze :)) wgląd w funkcjonowanie danej biblioteki i jej strukturę, co może nam pomóc w jej użytkowaniu. Istnieje jednak szansa, że poprzez nadmierne skoncentrowanie się na opisach teoretycznych umkną nam ważne kwestie praktyczne.

Niejako przeciwną metodą jest jak najszybsze zajęcie się ową praktyką. W tym celu wystarcza zwykle przejrzenie różnorakich tutoriali (jeśli takowe są dostępne), a zwłaszcza dokładne przestudiowanie przykładowych fragmentów kodu. Zresztą eksperymentalne poznawanie nowej technologii może być wręcz kopiowaniem sampli, ich uruchamianiem, a następnie modyfikacją w celu zmiany sposobu działania, uelastycznienia lub poszerzenia o nową funkcjonalność.
Gdy w taki sposób zabieramy się do rzeczy, możemy szybko zobaczyć rezultaty. Jeśli jednak będziemy mieli pecha i pojawią się niespodziewane problemy, zaledwie pobieżna znajomość technologii może nam wydatnie utrudnić ich rozwiązanie. Poza tym takie kodowanie “na gorąco” wymaga nieporównywalnie częstszych wypadów na łono dokumentacji :)

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

Styl dla kodu w Wordzie

2010-08-06 21:15

Dokumenty zawierające wstawione fragmenty kodu to zazwyczaj artykuły w sieci formatowane przy pomocy znaczników HTML. Rzadziej pojawia się potrzeba wstawienia listingu programu do tekstu edytowanego w pakiecie biurowym, ale jeśli już takowa zajdzie, to warto wiedzieć, jak to zrobić. A właściwie: jak to zrobić (w miarę) dobrze :)

Należy mianowicie pamiętać o tym, że poważne edytory tekstu pozwalają na zgrupowanie ustawień formatowania tekstu w postaci styli. Analogia do klas CSS jest tu dość znaczna, chociaż występują pewne drobne różnice. Jednak i tu, i tu mamy do czynienia z narzędziem, które powinno wykonywać większość pracy związanej z nadawaniem tekstowi wyglądu. Oczywiście miejscowe pogrubienia czy podkreślenia są więcej niż dopuszczalne, ale dla większych odstępstw w formatowaniu – które zwykle mają jeszcze uzasadnienie merytoryczne – należy korzystać z istniejących stylów lub definiować własne. Pod to kryterium podpada na pewno wstawianie do tekstu kawałków kodu.
Niestety, twórcy Worda z pakietu Office uznali najwyraźniej, że niepotrzebny jest predefiniowany styl dla kodu, więc trzeba go sobie stworzyć we własnym zakresie. Należy wówczas pamiętać o:

  • odpowiedniej czcionce. Kod zapisuje się czcionką o stałej szerokości znaków w większości IDE, więc prawie wszyscy programiści są do tego przyzwyczajeni. Ze standardowo dostępnych czcionek osobiście polecam Consolas :)
  • odstępach między akapitami (a raczej ich braku). ‘Akapit’ jest czymś innym niż ‘wiersz’ w edytorach tekstu, co jest podobne do różnicy między tagami p i br w HTML. Wciśnięcie klawisza Enter dodaje nam jednak nowy akapit, co domyślnie oznacza również wstawienie sporego odstępu pionowego. Nie chcemy takiego odstępu w linijkach kodu. Dlatego powinniśmy uaktywnić opcję Nie dodawaj odstępu między akapitami o tym samym stylu w ustawieniach akapitu.
    Gdy tak zrobimy w grę zacznie wchodzić tzw. interlinia, określająca odstęp między wierszami. Ponieważ listingi powinny być pisane dość gęstymi linijkami (choć oczywiście bez przesady), optymalna zdaje się interlinia pojedyncza.
  • wcięciach. Pod tym pojęciem kryją się dwie rzeczy. Pierwszą z nich są tabulatory, czyli miejsca, do których przeskakuje kursor po kolejnych naciśnięciach Tab. Domyślnie są one usytuowane w równej odległości od siebie, co w przypadku kodu ma sens, więc możemy je tak zostawić.
    Drugą sprawą jest wcięcie całych fragmentów kodu, co wyróżniałoby je bardziej spośród reszty tekstu. Wydaje mi się, że dodatkowy prawy margines o szerokości 30 do 50 punktów jest w takim wypadku wystarczający. Takie wcięcie możemy ustawić w opcjach akapitu.
  • sprawdzaniu pisowni. Tak naprawdę chodzi o wyłączenie tego sprawdzania, dzięki czemu nie będziemy mieli w kodzie prawie każdego wyrazu w podkreślonego na czerwono. Czynimy to, przechodząc do opcji języka i zaznaczając Nie sprawdzaj pisowni ani gramatyki. Niestety nic mi nie wiadomo o tym, żeby w przyszłych wersjach Worda miała się pojawić opcja Automatycznie sprawdzaj błędy kompilacji :)
Tags: ,
Author: Xion, posted under Applications, Programming » 6 comments

Starcraft 2 ukończony

2010-08-04 16:45

Trochę to trwało, ale w końcu udało się :) Ukończyłem wreszcie kampanię w Starcraft 2. Zajęło mi to nieco więcej czasu niż przewidywałem głównie ze względu na wybitnie niezbalansowany poziom trudności ostatniej misji. Poprzeczka jest tam podniesiona tak wysoko i tak gwałtownie, że nawet na normalnym poziomie trudności przez prawie cały czas nie za bardzo wiadomo, w co ręce włożyć ;] Po kilku próbach rzecz na szczęście okazała się wykonalna… Nie chcę jednak nawet myśleć, jak to wygląda na najwyższym poziomie trudności ;P

Mimo tej drobnej niedoróbki mogę jednak z czystym sumieniem polecić grę każdemu, komu odpowiada ten rodzaj rozgrywki. Starcraft 2: Wings of Liberty jest zdecydowanie godnym następcą “jedynki” i nie zdziwię się, jeśli osiągnie tę samą albo nawet większą popularność.
Tak więc warto było czekać, chociaż nie jest to nawet definitywny koniec – na wydanie czekają jeszcze kampanie Zergów i Protossów. W międzyczasie można się już jednak zająć bardziej produktywnymi rzeczami, co też niniejszym obiecuję uczynić ;-)

Tags:
Author: Xion, posted under Games » 11 comments

Pylonem w zerglingi

2010-07-27 18:54

Doczekaliśmy się. Po kilkunastu (!) latach od wydania pierwszej części, Starcraft 2 ujrzał dzisiaj światło dzienne (w Europie). Sam oczekiwałem na tę premierę bardzo długo, mimo że nie jestem jakimś wielkim fanem oryginału, a moje umiejętności kierowania którąkolwiek z trzech ras są raczej skromne :) Od dawna tęskniłem jednak za grą niewymagającą dużą czasu, a jednocześnie nie będą jakimś – tfu, tfu – casualem. RTS-y świetnie w tej roli spisują.

Oczywiście nie znaczy to, że rzucę SC2 w kąt po rozegraniu trzech misji ;P O nie, nie ma mowy. Już teraz mogę bowiem stwierdzić, że mimo podążania za kilkoma nieco denerwującymi trendami w obecnym przemyśle gier (czyli wszechobecnymi achievementami, koniecznością rejestracji online i całymi tymi niby-facebookowymi bajerami), Starcaft 2: Wings of Liberty jest bardzo udaną kontynuacją jedynki.

Tak więc narodowy sport Korei Południowej doczekał się sequela, natomiast blog przynajmniej w tym miesiącu nowej notki na pewno się nie doczeka ;P

Tags:
Author: Xion, posted under Games, Life » 8 comments

Informacje o błędach w rezultatach funkcji

2010-07-25 11:34

Wczoraj naciąłem się na nieprzyjemny rodzaj błędu w kodzie. Opierając się na powiedzeniu “Najciemniej pod latarnią”, można by go określić jako nieprzenikniona ciemność tuż pod najjaśniejszą latarnią w całym mieście. Normalnie nikomu nie przyjdzie do głowy, by jej tam szukać. Chodzi bowiem o jakąś “oczywistość”, w którą wątpienie jest nie tyle nierozsądnie, co wręcz nie przychodzi nawet na myśl.
A wszystko dlatego, że lubię prostotę – przynajmniej w takich kwestiach, jak wyniki funkcji informujące o ewentualnych błędach. Prawie zawsze są to u mnie zwykłe boole, informujące jedynie o tym, czy dana operacja się powiodła lub nie. To wystarcza, bo do bardziej zaawansowanego debugowania są logi, wyjątki, asercje i inne dobrodziejstwo inwentarza. A same wywołania funkcji możemy sobie bez przeszkód umieszczać w ifach czy warunkach pętli.

Są jednak biblioteki, które “twierdzą”, że taka konwencja jest niedobra i proponują inne. Pół biedy, gdy chodzi tu o coś w rodzaju typu HRESULT, do którego w pakiecie dostarczane są makra SUCCEEDED i FAILED. Zupełnie nie rozumiem jednak, co jest pociągającego w pisaniu np.:

  1. int sock;
  2. if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
  3.     { /* błąd */ }

Usprawiedliwiać się mogę jednak tym, że… no cóż, to przecież Linux ;-) Ale kiedy dostaję do ręki porządną bibliotekę z klasami, wsparciem dla STL-a i CamelCase w nazwach funkcji, to przecież mogę się spodziewać czegoś rozsądniejszego, prawda? Skąd może mi przyjść do głowy, że rezultat zero ma oznaczać powodzenie wykonania funkcji?!

Najwyraźniej jednak powinienem taką możliwość dopuszczać. Okazuje się bowiem, że nawet dobrze zaprojektowane, estetyczne API może mieć takie kwiatki. Przekonałem się o tym, próbując pobrać wartość atrybutu elementu XML, używając biblioteki TinyXML i jej metody o nazwie QueryStringAttribute. W przypadku powodzenia zwraca ona stałą TIXML_SUCCESS; szkoda tylko, że jej wartością jest… zero ;P
Więc zaprawdę powiadam wam: nie ufajcie żadnej funkcji, której typem zwracanym nie jest bool!

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


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