wtorek, 19 lutego 2008

Z archiwum X

Każdy kto na poważnie zajmuje się programowaniem w Javie zetknął się z przełącznikami, które można ustawiać przy wywołaniu wirtualnej maszyny. Naturalnie w większości przypadków wystarczają trzy:
  • -jar do wskazywania archiwum Jar zamiast pojedynczego pliku klasy
  • -cp (lub -classpath) do ustawiania ścieżki klas
  • -Xmx do ustawiania maksymalnej wielkości stosu (przydatne zwłaszcza przy pracy ze środowiskami typu Eclipse czy Netbeans)
Ten ostatni to tak naprawdę tylko jeden skromny przedstawiciel bardzo płodnej rodziny przełączników X. JVM od Suna posiada ich mnóstwo, a implementacje od IBM czy Bea również posiadają swoje zestawy. Jak się w tym połapać? Na szczęście jest strona która zbiera wszystkie przełączniki w jednym miejscu (chociaż tylko z implementacji Sunowskiej) - "A Collection of JVM Options". Podane są przełączników nazwy, wartości, wersje JVM które je obsługują, a spora część z nich jest całkiem dobrze opisana (chociaż nie znalazłem tam np. niekiedy cennej informacji że przełącznik -client jest zawsze domyślnie włączony w JVM dla systemów Windows).

Od teraz będę już wiedzieć jak sprawić żeby JVM zauważył że jest w środowisku 64-bitowym (-d64), włączyć zapisywanie do pliku logu akcji odśmiecania pamięci (-Xloggc), wymusić użycie maksymalnej dostępnej pamięci (-XX:+AggressiveHeap) czy wyświetlić ilość użytej pamięci stosu (-XX:+PrintHeapUsageOverTime). Masa innych trików czeka jednak na swoich odkrywców, a więc do dzieła!

środa, 6 lutego 2008

Unikanie błędów

Tytuł tego posta jest dość chwytliwy. Kto nie chciałby unikać błędów w życiu? Wiedzieć z góry, że kupno tamtego mieszkania będzie oznaczało nieustanną walkę z grzybkami na suficie albo że autobus, do którego ma się zamiar wsiąść, ma zamiar się w połowie drogi zepsuć?

Tutaj chciałbym jednak zwrócić uwagę na jeden specyficzny sposób unikania błędów podczas tworzenia oprogramowania. Sposób jest bardzo prosty - pisać mniej kodu. Po chwili zastanowienia można zauważyć, że pomysł jest wręcz oczywisty, bo nie da się zrobić błędu w linii kodu, która... nie istnieje.

Kolejna chwila zastanowienia nad powyższym sposobem unikania błędów prowadzi do wniosku, że idealnie byłoby nie pisać kodu w ogóle. Zero kodu = zero błędów! Nirwana! Chociaż może nie do końca, bo tworzone oprogramowanie miało mieć jakąś funkcjonalność, a skoro nic nie napisaliśmy, to próżno szukać tej funkcjonalności.

Jak w takim razie pisać mniejsze ilości kodu, a mimo to z powodzeniem dostarczać gotowe oprogramowanie? Jest parę sposobów:
  • używanie generatorów kodu - to rozwiązanie wykorzystujące fakt, że większość programów IDE ma funkcje wstawiania gotowych fragmentów kodu (np. klasyczne getters / setters dla pól obiektów w Javie); ten sposób oznacza tylko, że programista sam nie musi pisać kodu, który jednak i tak powstaje, więc nie tędy droga
  • stosowanie zasady DRY (ang. "don't repeat yourself" - "nie powtarzaj się") - z czystym sumieniem mogę ten sposób polecić; trzymanie się głównej wytycznej DRY będzie powodować zapalenie się ostrzegawczej lampki za każdym razem, gdy spróbujemy po raz kolejny napisać to samo
  • używanie języków wyższego poziomu - to najbardziej kontrowersyjny sposób i naturalnie nie zawsze możliwy do zastosowania; pamiętając o tym, należy jednak mieć świadomość korzyści, które niesie ze sobą język wysokiego poziomu - możliwość wyrażania myśli bardziej bezpośrednio w kodzie; jako ilustracja niech posłuży króciutki kod w języku Haskell obliczający pierwiastek liczby metodą Newtona:
    -- funkcja "sqrt" zwraca nieskończony ciąg kolejnych
    -- przybliżeń wartości pierwiastka, z którego możemy
    -- wybrać np. piąte jako wystarczająco dobre
    sqrt x = sqrtNewton x 1

    sqrtNewton x guess = average : sqrtNewton x average
    where
    average = (y + (x / guess)) / 2
Pozostaje tylko problem, co zrobić z kodem, który już został napisany i przypomina objętością miłośnika Big Mac'ów? Odpowiedź wykracza już poza tego posta, ale w największym skrócie - używając refaktoryzacji stosować się do zasady DRY.

A więc - miłego (nie)pisania kodu!

czwartek, 24 stycznia 2008

Zamienił stryjek sIEkierkę na kIEk

Sporo mojej pracy dotyczy aplikacji WWW (web applications). Z tego powodu należę do dużej grupy ludzi niezadowolonych - to najłagodniejsze określenie - z powodu konieczności użerania się z produktem Internet Explorer firmy MS, a konkretnie z jego silnikiem renderującym strony HTML. Powstało mnóstwo stron opisujących błędy tego silnika oraz sposobów ich obejścia, nie zawsze niestety skutecznych. Wiele napisano również o tym, jak bardzo zacofana jest w IE obsługa standardów (szczątkowa obsługa CSS 2 - pochodzącego z 1998 roku, brak obsługi XHTML 1.1 - to rok 2001 się kłania, brak wsparcia dla SVG 1.1 - zatwierdzonego w 2003 roku). Jednak miesiąc temu na blogu IE pojawiła się pierwsza jaskółka nadziei: kolejna, 8. już wersja IE, ma być zgodna z CSS2! Niedługo potem z piekła zaczęły docierać sygnały o wyraźnym ochłodzeniu.

Minął miesiąc i oto na tym samym blogu pojawia się wpis "Compatibility and IE8". I wszystko wraca do normy. Owszem, IE8 będzie poprawnie(*) obsługiwał CSS2, ale... mało kto się o tym dowie, bo ten tryb "super-zgodności-a-nie-jak-do-tej-pory-takiej-zwykłej-zgodności" będzie się włączał tylko na specjalne życzenie wyświetlanej strony (po dodaniu do niej odpowiedniego tagu <meta>). Czyli po raz kolejny (po sztuczce z odpowiednio spreparowanym DOCTYPE i komentarzami-instrukcjami "if IE") pojawi się hak / przełącznik / obejście dla poprzednio zepsutych implementacji. To już przestało być śmieszne, chociaż co bardziej wytrwali w komentarzach do wpisu na blogu dowcipnie i z sarkazmem sugerują że w IE9 będzie tryb "hiper-zgodności-tym-razem-to-już-naprawdę" włączany np. głośnym klaśnięciem w dłonie...

(*) oczywiście może się okazać, że będzie to "poprawnie", ale tylko w microsoftowym znaczeniu tego słowa; pożyjemy zobaczymy

poniedziałek, 17 grudnia 2007

A mój wizual basic jest szybszy!!11!

Przyznaję, sam lubię poczytać dobrze rozpalone technologiczne wojny dyskusyjne. Najczęściej spotykane są oczywiście wojny pomiędzy zwolennikami systemów operacyjnych, ale w dziedzinie programowania równie chwytliwe są wojny o wyższości jednego języka nad innymi. Ta wyższość może się objawiać w najróżniejszy sposób, przez "lepszą" składnię, "lepszą" funkcjonalność, "lepszą" popularność, "lepszą" historię powstania, czy wreszcie "lepszą" wydajność. I do tematu wydajności ostatnim chciałbym tu parę słów komentarza od siebie dodać.

Na wypadek, gdyby jakimś cudem ktoś nie spotkał się dotąd z podobnymi wypowiedziami, oto parę przykładów:
  • "PHP skaluje się dużo lepiej niż Rails"
  • "Java jest za wolna"
  • "Mogę to samo napisać w C i będzie działać 600 razy szybciej"
...i można by tak wymieniać w nieskończoność. A czy te stwierdzenia są prawdziwe? Oczywiście że... tak. W konkretnym przypadku, środowisku, sprzęcie, problemie. Dla doświadczonego programisty PHP napisanie skalowalnej aplikacji CRUD będzie tylko wykorzystaniem jego wiedzy w praktyce; napisanie podobnej aplikacji w Rails - bez odpowiedniej wiedzy - będzie niemal niewykonalne. Program napisany w Javie z wykorzystaniem biblioteki Swing, naruszający podstawowe zasady modelu jednowątkowego dostępu do zasobów graficznych, będzie działał fatalnie i przywieszał się co chwilę. Zoptymalizowany program napisany w C będzie działał szybciej od większości odpowiedników np. w Ruby.

Czyżby więc rację mieli ci wszyscy którzy z uporem godnym lepszej sprawy wciskają podobne wypowiedzi na forach, blogach itp.? Niestety nie, ponieważ upraszczają sytuację do maksimum, zestawiając ze sobą języki i "porównując" suche fakty lub wręcz stwierdzając tylko swoją opinię. Tymczasem nie istnieje coś takiego jak "szybszy" język (pomijając już tu fakt, że porównywać można co najwyżej konkretne implementacje języka a nie specyfikacje):
  1. można szybciej coś napisać w tym języku niż w innym
  2. gotowy program napisany w tamtym języku może się wykonywać szybciej (na identycznej maszynie) niż napisany w jeszcze innym
  3. nową wersję aplikacji napisanej w języku X można wdrożyć szybciej niż w języku Y.
Ale jeden język programowania nie może być "szybszy" od drugiego!

Jest jeszcze jedna sprawa - dla niektórych stwierdzenie o "szybszym" języku jest jednoznaczne z pkt. 2 wyżej. To błąd. Rozpatrując tylko czasy wykonania analogicznych programów napisanych w różnych językach, nie należy patrzeć tylko na liczby bezwzględne. Nakład pracy poświęconej na stworzenie takiego programu i przewidywany czas pracy z kodem źródłowym w przyszłości mogą być (i często są) ważniejsze od czasu działania gotowej aplikacji. Przykładowo - piszemy prosty analizator logów serwera, generujący raport w pliku XML z gotowym arkuszem XSL. Napisanie takiego programu w C zajmie nam 4 godziny. Napisanie takiego samego programu w Ruby zajmie 2 godziny (zakładam znajomość Ruby i C). Uruchomienie programu napisanego w C dla danego pliku danych wejściowych zajmie np. 10 sekund. Dla programu w Ruby i tych samych danych program będzie działał 30 sekund. Analiza logów będzie przeprowadzana raz na miesiąc. Jakie w tym momencie ma znaczenie fakt, że implementacja w C jest 300% szybsza od tej w Ruby? Żadne, bo ta w Ruby jest wystarczająco szybka w tej konkretnej sytuacji.

środa, 28 listopada 2007

Odpowiednie narzędzia do pracy to połowa sukcesu

Esencją tego bloga jest informowanie o narzędziach programistycznych, jakich można użyć do rozwiązania problemów codziennej pracy. Do tej pory jednak skupiałem się na językach programowania, a przecież kod, który piszemy musi w jakiś sposób powstać. I nie zamierzam tu zaczynać kolejnej wojny o wyższości Vim nad Emacs, Eclipse nad Netbeans, Scite nad Textpad itd. Chciałbym się podzielić swoim (może ciągle niewielkim) doświadczeniem z programowania w Ruby oraz Haskellu.

Bez wielkiego wstępu i rozwodzenia się, uważam że do tworzenia oprogramowania w Ruby najlepszym środowiskiem jest Netbeans 6. Dzięki pracy autorów "Ruby plugin" możemy cieszyć się komfortem pisania z podpowiadaniem składni, dokumentacją API, debuggerem, refaktoryzacją oraz pełną integracją z samym IDE. Na obecną chwilę, Netbeans (w wersji 6.0) nie ma godnej konkurencji.

Opisując Netbeans nieprzypadkowo użyłem określenia "najlepsze środowisko", ponieważ do szybkiego edytowania kodu w Ruby (oraz Haskell) używam jEdit.

Jest to bardzo mocno rozszerzalny i konfigurowalny edytor z podświetlaniem składni, zaawansowanym formatowaniem tekstu i obsługą wielu plików na raz. Jednak dopiero po uzupełnieniu podstawowej funkcjonalność o wtyczki (np. wspaniałą SuperAbbrevs) zamienia się w prawdziwy kombajn. Polecam - wypróbujcie jEdit! A - czy wspomniałem już że jest całkowicie darmowy? :-)

wtorek, 20 listopada 2007

Różne podejścia do wielowątkowości

Pisałem ostatnio o języku Haskell. Uważam go za jeden z lepszych w dziedzinie programowania wielowątkowego w modelu "tradycyjnym", czyli z współdzieloną pamięcią (dzięki połączeniu czysto języka funkcyjnego i technologii STM - bardzo przystępnie przedstawionej w "Beautiful concurrency"). Nie jest jednak jedyne możliwe podejście do problemu. Oto krótkie zestawienie stosowanych rozwiązań wraz z komentarzem:
  • brak jakiejkolwiek obsługi wielowątkowości (Javascript, PHP) - wbrew pozorom to też jest jakieś rozwiązanie, w końcu nie zawsze potrzeba takiej funkcjonalności
  • pseudo-wątki obsługiwane przez runtime języka (Python, Ruby) - w językach z pseudo-wątkami (określanymi często w j. angielskim jako "green threads") to środowisko uruchomieniowe języka samo zajmuje się obsługą współbieżności w najprostszej formie, czyli przełączania kontekstu pomiędzy wątkami. Stosując takie rozwiązanie nie da się w pełni wykorzystać potencjału współczesnych procesorów wielordzeniowych czy maszyn wieloprocesorowych
  • wątki natywne operujące na współdzielonej pamięci (C, Java, Haskell) - języki te udostępniają interfejs umożliwiający operacje na wątkach sterowanych przez system operacyjny (ang. "native threads"). Rozwiązanie to cechuje z jednej strony możliwość tworzenia bardzo wydajnych aplikacji, ale z drugiej duży stopień trudności związany z zapewnieniem poprawności działania. To tu pojawiają się zagadnienia synchronizacji dostępu, blokad, semaforów itp.
  • niezależne, natywne lub kontrolowane przez runtime wątki z systemem komunikatów (Erlang) - według wielu znawców tematu tylko to rozwiązanie ma przyszłość w świecie, w którym -dziesiąt czy -set rdzeni lub procesorów przestaje być fantazją. Atutem jest tu niemal nieograniczona skalowalność takiego podejścia, wadą - konieczność zaadoptowania diametralnie innej koncepcji budowy oprogramowania.
Jeszcze słowo komentarza o współdzielonej pamięci. Moim skromnym zdaniem model ten nie jest skazany na wymarcie (a jeśli nawet, to nie w najbliższej przyszłości). Zbyt wiele jest aplikacji, które o ile skorzystają na wprowadzeniu współbieżnego działania, to nie są narażone na wielowątkowość w skali większej niż kilka równoległych wątków. Tak czy inaczej, zawsze znajdzie się odpowiednie narzędzie do rozwiązania problemu.

piątek, 9 listopada 2007

Procesory wielordzeniowe a programowanie

Miesiąc temu obiecałem napisać o rewolucji w sposobie myślenia o pisaniu programów. Zdradziłem wtedy, że chodzi o język Haskell. Czym więc jest Haskell? Jest językiem programowania (to raczej nie jest niespodzianka) o następujących cechach:
  1. czysto funkcyjny - o ile programowanie funkcyjne jest samo w sobie diametralnie innym podejściem do tworzenia oprogramowania, to dopiero "czysta" funkcyjność Haskella jest prawdziwym unikatem, o którym za chwilę
  2. funkcje jako wartości podstawowe - funkcje mogą być parametrami lub wartościami wynikowymi innych funkcji
  3. currying funkcji - w największym skrócie chodzi o to, że podając np. dwuparametrowej funkcji "dodaj" jeden parametr (np. 3), otrzymujemy w rezultacie nową funkcję jednoparametrową (którą można by nazwać "dodaj3do")
  4. z silną i statyczną kontrolą typów - nie zamierzam bynajmniej rozpoczynać tu dyskusji o wyższości świąt Bożego narodzenia na świętami Wielkiejnocy (w wydaniach "silna czy słaba" / "statyczna czy dynamiczna" kontrola typów), zawsze sugeruję użycie najbardziej odpowiedniego narzędzia w określonej sytuacji
  5. z odgadywaniem typów - interpreter lub kompilator potrafi (w zdecydowanej większości przypadków) poprawnie "odgadnąć" typ wyrażenia, więc programista nie musi go jawnie deklarować; tak przy okazji, to połączenie statycznej kontroli typów z ich odgadywaniem typów jest bardzo dobrym rozwiązaniem, bo pozwala ograniczyć charakterystyczną dla statycznie typowanych języków "rozwlekłość" kodu
Wyjaśnienia wymaga przede wszystkim "czystość" podejścia funkcyjnego zastosowana w Haskellu. Autorzy języka trzymają się ściśle definicji funkcji wyniesionej z dziedziny, która dała nam funkcje - matematyki. Jednym z założeń funkcji jest to, że nie może wpływać ona na otoczenie oraz to, że dla danego parametru wejściowego zawsze daje ten sam wynik. Takiego założenia nie spełnia np. najprostsza "funkcja" (można by ją dla uniknięcia niejednoznaczności określić jako "procedura") pobierania znaku z klawiatury - getChar. Wiele innych języków funkcyjnych przymyka na ten fakt oko i nie robi z tego powodu żadnego problemu. Haskell idzie krok dalej i, korzystając z bardzo abstrakcyjnego działu matematyki zwanego teorią kategorii, wprowadza wyraźny podział na właściwe funkcje (spełniające wymagania definicji funkcji) oraz monady (nie wiem niestety czy to poprawne polskie określenie, równie złe ale dające pewien punkt odniesienia to "akcje"). O tym czym są monady w kontekście języka Haskell pewnie jeszcze napiszę przy okazji (to ulubiony temat blogów o Haskellu), na razie powinna wystarczyć informacja, że Haskell rozróżnia funkcje i akcje.

Całe to zamieszanie z czystością podejścia funkcyjnego raczej odstrasza niż zachęca. Dlaczego więc twórcy Haskella tak się przy tym upierają? Jakie korzyści może to przynieść? I co to wszystko ma wspólnego z tematem posta - procesorami wielordzeniowymi?

Własności funkcji matematycznej - funkcja zawsze da ten sam wynik dla określonego parametru i nigdy nie wpłynie na wywołanie innej funkcji - sprawiają, że jej zastosowanie w programie komputerowym daje potężne możliwości optymalizacji dla kompilatora lub interpretera. Nie ma też żadnego ryzyka związanego z uruchomieniem np. 32 współbieżnych wątków obliczających dowolną funkcję (czego nie można powiedzieć o uruchomieniu chociażby 2 współbieżnych wątków dokonujących modyfikacji w systemie plików). A nie ma lepszej metody na wykorzystanie coraz bardziej popularnych procesorów wielordzeniowych niż aplikacje wielowątkowe.

Chwila - wielu czytelników zapewne teraz zwróci uwagę - aplikacje wielowątkowe to przecież żadna nowość. Zaletą podejścia w Haskellu jest jednak to, że jawnie oddzielone są od siebie bezpieczne bloki kodu (gdzie bezpieczeństwo oznacza "możliwość wpływania na swoje otoczenie") od potencjalnie niebezpiecznych.