Monthly archive for August, 2010

Odpalanie klasy

2010-08-31 12:09

Niskopoziomowe, wewnętrzne klasy logiki mają tę wadę (jeśli można to tak nazwać…), iż są właśnie niskopoziomowe i wewnętrzne – a przez to trudne do testowania w powiązaniu z całą aplikacją. Między nimi a interfejsem może znajdować się wiele warstw, które utrudniają debugowanie.
To sprawia, że przydatne stają się testy jednostkowe (unit tests). Do ich tworzenia i uruchamiania potrzeba jednak odpowiednich frameworków. Na szczęście te są już dostępne dla niemal każdego sensownego języka programowania i nierzadko są wręcz częścią jego lub jego środowiska.
Nie zawsze jednak tak było. Alternatywą dla ręcznego tworzenia specjalnych aplikacji przeznaczonych wyłącznie do testowania klas były (i właściwie nadal są) interesujące mechanizmy “uruchamiania klas” jako takich, które oferują niektóre języki programowania.

Dotyczy to przede wszystkim – jeśli nie wyłącznie – tych spośród nich, w których koncepcja programu czy aplikacji nie jest wyraźnie zakreślona. Coś jakiego jak entry point (“punkt wejścia”), od którego zaczyna się wykonywanie kodu, musi jednak istnieć i trzeba mieć jakiś sposób na jego określenie. Jeżeli zamiast aplikacji mamy tylko mniej lub bardziej luźny zbiór klas, to siłą rzeczy musi się on znajdować w którejś z nich.
A jeśli znajduje się w jednej, to czemu nie dodać go też do innych – także tych, które z założenia nie mają pojęcia o interakcji z użytkownikiem? Tworzymy w ten sposób pewnego rodzaju back-end, a owe dodatkowe punkty wejścia mogą posłużyć do testów. Oto prosty przykład w języku Java:
public class Sumator
{
private int sum = 0;
public void add(int x) { sum += x; }
public int getSum() { return sum; }

// testowy punkt wejścia
public static void main(String[] args) {
int n = args.length > 1 ? Integer.parseInt(args[1]) : 100;

Sumator s = new Sumator();
for (int i = 1; i <= n; ++i) s.add(i); System.out.println ("Spodziewana suma: " + (n * (n + 1) / 2)); System.out.println ("Otrzymana suma: " + s.getSum()); } }[/java] Statyczna metoda main to w Javie sposób na określenie punktu wejścia. Typowym miejscem dla niego jest główna klasa aplikacji, względnie całkiem osobna klasa przeznaczona do zarządzania samym uruchamianiem programu. Tutaj umieszczamy metodę main w klasie logiki (bardzo zaawansowanej zresztą ;-]), przez co możemy “odpalić” samą tę klasę i wykonać jakiś kod testujący jej funkcjonalność.

Z popularnych języków mających taki feature można jeszcze wspomnieć Pythona. W nim obiektowość nie jest obowiązkowa, więc to skrypt (plik .py) jest podstawową jednostką kodu, którą można uruchamiać:

  1. import sys
  2.  
  3. class Sumator:
  4.     def __init__(self): self.sum = 0
  5.     def add(self, x): self.sum += x
  6.  
  7. if __name__ == "__main__":
  8.     n = int(sys.argv[1]) if len(sys.argv) > 1 else 100
  9.     s = Sumator()
  10.     for i in range(1, n+1): s.add(i)
  11.     print "Spodziewana suma: ", (n * (n + 1) / 2)
  12.     print "Otrzymana suma: ", s.sum

Daje się w nim też umieścić “swobodny” kod, co na pierwszy rzut oka wydaje się dobrym miejscem na instrukcje testowe. Trzeba tylko otoczyć je pokazanym wyżej ifem, aby były one uruchamiane wyłącznie przy wywoływaniu skryptu z wiersza poleceń, nie zaś przy imporcie zawartego w nim kodu (instrukcją import).

Tags: , , , ,
Author: Xion, posted under Programming » Comments Off on Odpalanie klasy

ta’i ma la lojban. bangu fi lo na’e satci

2010-08-28 21:58

Jak w lojbanie można wyrażać się nieprecyzyjnie?

Każdy, kto zetknie się z lojbanem, najczęściej szybko dowiaduje się, skąd pochodzi jego nazwa. Źródłem jest określenie logji bangu, czyli język logiczny. Logika sugeruje zaś dokładność, jednoznaczność i precyzję. Sugeruje to, że w lojbanie nie można wyrażać się niejasno, mgliście czy metaforycznie. Gdyby tak było rzeczywiście, to trudno byłoby to uznać za jego siłę, skoro aspiruje on do miana języka, którym mogą się posługiwać (także) ludzie.
Można więc podejrzewać, że to nieprawda – i tak jest istotnie. Dzisiaj chciałbym pokazać kilka przykładów na to, że mimo swojej “logiczności” lojban jak najbardziej dopuszcza przenośnie, niedopowiedzenia, skróty myślowe i inne tego rodzaju niuanse.

Więcej: nie tylko je dopuszcza, ale wręcz do nich zachęca – między innymi za pomocą pojęcia kontekstu, które samo w sobie nie jest precyzyjnie określone. Nietrudno jednak zrozumieć je intuicyjnie. Kontekst to po prostu… no cóż, kontekst :) Przyjmuje się, że gdy coś z niego wynika, to jest to mniej lub bardziej oczywiste zarówno dla nadawcy, jak i odbiorcy wypowiedzi.
A z kontekstu może w lojbanie wynikać bardzo wiele. Za każdym razem, gdy opuścimy jakieś miejsce w zdaniu, to właśnie kontekst je uzupełnia – lub pozostawia nieokreślone:

mi jinvi lodu’u ba carvi ca le cabdei
Sądzę, że dzisiaj będzie padać deszcz.

Tutaj nie musimy na przykład troszczyć się o wypełnienie drugiego miejsca carvi (powierzchni, na którą pada) lub trzeciego (źródła deszczu), bo kontekst mówienia o pogodzie aż nadto wystarcza, żeby wywnioskować, co mamy na myśli. Na tej samej podstawie możemy często pomijać zaimki osobowe, nawet mimo tego, że lojban nie ma odmiany czasowników (że o samych czasownikach nie wspomnę ;P):

ju’i do’u klama le zarci .i xu kansa
Hej, idziemy do sklepu. Idziesz z nami?

Pomijanie czasów (przeszły/teraźniejszy/itd.) jest z kolei tak powszechne, że wyjątkiem są raczej te sytuacje, gdy potrzebne jest ich użycie. Wówczas też chodzi bardziej o określenie punktu odniesienia (ca le cabdei – dzisiaj) niż o wymagania samej gramatyki.

Powyższe przykłady pokazują, jak pomijanie niektórych części wypowiedzi nie musi wpływać negatywnie na jej zrozumiałość. Innym mechanizmem wprowadzania “niejasności” jest tanru, czyli metafora. Polega on na połączeniu dwóch predykatów tak, że pierwszy modyfikuje znaczenie drugiego. Szczegóły tej modyfikacji nie są dokładnie określone i jest tak by design. Użycie tanru wprowadza więc pewną nieprecyzyjność jako cenę za skrócenie wypowiedzi:

mi xabju lo barda zdani — Mieszkam w dużym domu.
mutce xamgu fa lonu cilre fi la lojban. — Uczenie się lojbanu jest bardzo pożyteczne. (;P)
ko sutra bajra klama fa’a mi — Biegnij szybko w moją stronę.

Jak widać odpowiednikami tanru w innych językach są m.in. połączenia rzeczowników z przymiotnikami, a także stopniowanie tych drugich. Da się więc tutaj zauważyć pewne schematy użycia, ale w gruncie rzeczy możliwości tworzenia tanru są nieograniczone – podobnie jak możliwości ich interpretacji.

Takie stwierdzenie może nieco dziwić wobec często podkreślanej jednoznaczności lojbanu. Trzeba jednak pamiętać, że dotyczy ona wyłącznie gramatyki i objawia się tym, że poprawny tekst w lojbanie jest parsowalny zawsze w dokładnie jeden sposób. To zupełnie inaczej niż w typowych językach naturalnych, czego często przytaczanym przykładem jest “angielskie tanru“:

pretty little girls school

W zależności od “nawiasowania” może ono znaczyć: szkołę dla całkiem małych dziewczynek, całkiem małą szkołę dla dziewczynek, małą ładną szkołę dla dziewczynek – i jeszcze kilkanaście innych kombinacji. Lojbański odpowiednik (milxe cmalu nixla ckule) jest z kolei zawsze odczytywany od lewej do prawej i jednoznacznie wyraża pierwszą interpretację.

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

Porady odnośnie ubarwiania kodu

2010-08-24 20:01

Wszyscy wiemy, że Notatnik to bardzo dobry edytor kodu. Wystarcza w zupełności, o ile tylko jesteśmy w stanie zorientować się w gąszczu liter, cyfr i symboli bez żadnych wizualnych wskazówek w postaci ich kolorowania. Jakoś dziwnym trafem miażdżąca większość programistów preferuje jednak edytować kod odpowiednio “odmalowany” przez IDE. Widać nie potrafią docenić piękna kodowania na monochromatycznym monitorze ;-)
A nieco poważniej mówiąc: kolorowanie kodu to istotna sprawa, gdyż jest to kwestia komfortu naszych drogocennych narządów wzroku. Ponieważ miałem do czynienia z wieloma edytorami kodu i środowiskami programistycznymi, wydaje mi się, że mogę dość sensownie wypowiedzieć się na temat tego, jak należy ustawić kolory dla poszczególnych składników kodu, by były one komfortowe.


Kolory idealne dla Perla ;>

Najważniejszy jest tu prawdopodobnie odpowiedni kontrast, który nie wyraża się wyłącznie różnicą jasności między tłem a tekstem – zwłaszcza w systemie RGB. Powiedziałbym zresztą, że dla każdego jest to kwestia raczej subiektywna. Znam osoby, które świetnie czują się w kolorystyce rodem niemal z Matriksa: czarne tło i biały tekst, który miejscami przechodzi w jaskrawe kolory, wyraźnie odcinające się na ciemnym ekranie (jak zielony lub purpurowy). Pozwolę sobie jednak zaryzykować stwierdzenie, że takich osób nie ma zbyt wiele :) Powszechniejsza zdaje się konfiguracja z jasnym tłem; ma ona też tę zaletę, że po przełączeniu się do innego okna raczej nie doznamy żadnego “szoku świetlnego”, jako że dokumentacje i większość stron internetowych trzymają się raczej jasnych barw.
Zaznaczam jednak, że ‘jasne’ tło nie musi wcale znaczyć ‘białe’. Dopasowując kontrast, dobrze jest poeksperymentować z tłem lekko żółtym, zielonym czy niebieskim. Nie należy aczkolwiek przesadzać i ustawiać mniej niż ~80-procentową intensywność w którymś z kanałów RGB – w efekcie tło będzie najprawdopodobniej zbyt ciemne.
Analogicznie warto też przyjrzeć się domyślnemu kolorowi kodu (zazwyczaj jest to ten stosowany do identyfikatorów w rodzaju zmiennych lokalnych). Nie musi on wcale być absolutnie czarny; zamiast tego można wypróbować coś w rodzaju RGB(30, 30, 30).


Coraz bliżej święta ;)

Oczywiście ów domyślny kolor kodu to zazwyczaj tylko jedna z wielu barw używanych przez IDE. Obecnie środowiska wyróżniają mnóstwo osobnych elementów kodu, pozwalając każdemu z nich mieć przypisany inny kolor. Nie zalecałbym jednak przesady w korzystaniu z tej funkcjonalności, bo przy odrobinie źle pojętej kreatywności możemy otrzymać schemat kolorów świecący niczym choinka, gdzie dodatkowo najdłuższy ciąg o tej samej barwie nie liczy więcej niż dwa wyrazy. Pamiętajmy, że tutaj ważna jest użyteczność. To, jak bardzo dany element jest wyróżniony i wygląda inaczej niż inne, powinno odpowiadać jego ważności. Typowa kolejność, począwszy od najbardziej istotnych tokenów, wygląda mniej więcej tak:

  1. słowa kluczowe
  2. nazwy własnych typów
  3. wartości stałe (liczbowe, znakowe i tekstowe)
  4. nazwy funkcji i metod
  5. pozostałe identyfikatory
  6. komentarze

Ta lista może rzecz jasna podlegać dostosowaniu pod własne upodobania, a także pod możliwości używanego IDE (część nie obsługuje niestety kolorowania semantycznego, które rozróżnia np. typy od innych nazw). Każdy język dodałby tu też specyficzne dla siebie elementy, sytuujące się zazwyczaj gdzieś w drugiej połowie stawki. W końcu, wewnątrz każdej z tych grup możliwe są też dodatkowe rozróżnienia – lecz, jak już wspomniałem, należy korzystać z nich ostrożnie.

Tags:
Author: Xion, posted under Applications, Programming » 6 comments

Triki z PowerShellem #16 – Parametry

2010-08-22 14:44

Pisząc mniej lub bardziej regularnie o PowerShellu, zdołałem zaprezentować całkiem pokaźną kolekcję różnych skryptów. Część z nich do działania wymagała podania dodatkowych danych. Jeśli są one w miarę niezmienne, można je zapisać w samym kodzie jako pseudo-stałe. Jeżeli jednak każde użycie skryptu może potencjalnie potrzebować innych wartości, wtedy najlepiej pobawić się z jego parametrami.

W PowerShellu do specyfikowania argumentów służy słowo kluczowe param. Można je stosować zarówno do samych skryptów, ale także do zawartych w nich funkcji. Tak jest – może nie wspominałem o tym wcześniej, ale w PSh jak najbardziej można definiować własne funkcje na użytek lokalny skryptu:

  1. function Fib
  2. {
  3.     param ([int]$x)
  4.     if ($x -eq 0 -or $x -eq 1)  { return 1 }
  5.     return (Fib ($x - 1)) + (Fib ($x - 2))
  6. }

W obu przypadkach (skryptów i funkcji) instrukcja param powinna znaleźć się na samym początku i wystąpić co najwyżej raz.

Składnia deklaracji parametrów jest raczej prosta i wygląda następująco:

  1. param (..., [typ]$nazwa = wartośćDomyślna, ...)

Kolejne elementy oddzielamy w niej przecinkami. Dla każdego z nich wartośćDomyślna nie jest obowiązkowa, lecz niepodanie jej nie czyni wcale argumentu obowiązkowym. Zamiast tego otrzyma on wartość “neutralną” (np. $null dla obiektów), jeśli nie zostanie przypisany w wywołaniu funkcji/skryptu. Żeby uczynić argument rzeczywiście niezbędnym, należy po prostu… rzucić wyjątek, jeśli nie został on użyty:

  1. param ([string]$name = $(throw "Name required"))

Może to się wydawać dziwne, ale nie zapominajmy, że języki powłok takich jak PowerShell są interpretowane Dlatego błąd w postaci braku wymaganego parametru (który normalnie wykryty by został podczas kompilacji) może dać o sobie znać dopiero w czasie wykonania.

Każdemu parametrowi możemy też przypisać typ, którym może być klasa z .NET-owego frameworka lub któryś z typów wbudowanych. Oczywiście nie musimy tego robić. Wtedy jednak będziemy mieli do czynienia z nieokreślonym obiektem (klasy System.Object), którego rodzaj możemy ewentualnie sprawdzić później (np. metodą GetType).
Specjalnym typem parametru jest switch, czyli przełącznik. Tworzy on parametr, któremu nie podajemy wartości, a jedynie uwzględniamy w wywołaniu (albo i nie). Jest to więc flaga podobna do Recurse czy -Verbose ze standardowych komend PowerShella lub tysięcy podobnych flag w programach i poleceniach z innych shelli. Semantycznie taki przełącznik jest potem zmienną logiczną typu bool:

  1. param ([switch]$Debug)
  2. # ...
  3. if ($Debug) { "Debug mode enabled." | Out-Host }

To mniej więcej tyle, jeśli chodzi o definiowanie parametrów dla funkcji i skryptów. Jak teraz wygląda ich przekazywanie w trakcie wywołania?… No cóż, odpowiedź jest prosta: dokładnie tak samo, jak w przypadku wbudowanych komend PowerShella. Przede wszystkim możemy podawać ich nazwę poprzedzoną myślnikiem oraz wartość:

  1. Fib -x 12 | Out-Host

Nie jest to jednak zawsze konieczne. Zamiast tego możemy zdać się na ich kolejność w deklaracji param, co pozwala ominąć podawanie ich nazw w wywołaniu:

  1. Fib 12

Pamiętajmy tylko, że skrypty (rzadziej funkcje) mogą mieć nierzadko i kilkanaście parametrów. Pomijanie ich nazw na pewno nie wpłynie wtedy pozytywnie na czytelność.

Tags: ,
Author: Xion, posted under Applications, Programming » 2 comments

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
 


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