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.

czwartek, 18 października 2007

Ale co to właściwie znaczy, dynamiczny?

W definicji języka Ruby, podanej na jego stronie domowej, już jako pierwsze słowo pojawia się "dynamiczny". Dla kogoś, kto dopiero poznaje świat Ruby, nie do końca może być jasne, co tak naprawdę znaczy jego dynamiczność. Czym się objawia, skoro wymieniana jest jako sztandarowa jego cecha?

Dynamikę można rozumieć jako ruch czy przeobrażenie - i tego wytłumaczenia chciałbym tu użyć. Ruby, podobnie jak Java, jest językiem obiektowym. Inaczej jednak niż w Javie, raz utworzony obiekt może zmieniać nie tylko swój stan, ale i zestaw komunikatów, które przyjmuje. Prościej i dosadniej rzecz ujmując, w Javie jedyne, co może zmienić się w obiekcie to zawartość jego pól. Natomiast w Ruby, oprócz tego możliwe jest dynamiczne (czyli w trakcie wykonywania programu) dokładanie nowych oraz zmiana czy usuwanie istniejących metod i pól.

Taka funkcjonalność języka daje programiście duże możliwości. Bardzo chętnie skorzystali z nich między innymi twórcy frameworku Rails. Przykładowo w warstwie modelu (bazy danych), definiując własną klasę dziedziczącą z ActiveRecord::Base, otrzymujemy za darmo cały zestaw "magicznych" metod. Dlaczego magicznych? Ponieważ można by długo i nieskutecznie szukać np. źródła dla metody find_by_author klasy Book (reprezentującej dane z tabeli book w bazie danych - zakładamy tu że tabela ta ma kolumnę author). A mimo to można z niej od razu skorzystać:
my_favourite = Book.find_by_author "Terry Prattchet"
Jak to możliwe? Oto co dzieje się w momencie wywołania takiej metody:
  1. do obiektu Book wysyłany jest komunikat find_by_author (to po prostu określenie wywołania metody, używane w świecie Ruby) z parametrem "Terry Prattchet"
  2. obiekt Book nie posiada żądanej metody, więc wysyłany jest komunikat method_missing (to standardowa metoda klasy Object, z której dziedziczą wszystkie inne klasy w Ruby; jej domyślna implementacja rzuca wyjątek NoMethodError)
  3. wykonywana jest method_missing przeciążona w klasie ActiveRecord::Base - i tu dynamika Ruby wykorzystana jest w pełni, bo na podstawie informacji, że próbowano wykonać metodę o nazwie zaczynającej się od "find_by" oraz tego, że klasa Book ma pole "author", w locie tworzona jest nowa metoda find_by_author, której implementacja to wywołanie gotowej uniwersalnej metody ActiveRecord::Base.find z odpowiednimi parametrami
Prawda, że pięknie? Wywołujemy nieistniejącą metodę i w efekcie nie tylko nie dostajemy błędu, ale jeszcze metoda sama się tworzy po czym wykonuje dając poprawny wynik! :-)

Co najważniejsze, nie trzeba wcale sięgać po własne konstrukcje z dziedziny metaprogramowania czy Domain-Specific Languages, aby używać i korzystać w pełni z dynamicznej natury Ruby. Oto prosty przykład, klasa Book, tym razem niezwiązana z ActiveRecord::Base:
class Book
attr_accessor :author, :title
end
A oto jej odpowiednik w języku Java:
class Book {
private String author;
private String title;
public String getAuthor() { return author; }
public void setAuthor(String value) { author = value; }
public String getTitle() { return title; }
public void setTitle(String value) { title = value; }
}
Wbrew pozorom, attr_accessor nie jest jakimś udziwnionym słowem kluczowym do definiowania pól razem z metodami dostępowymi. To najzwyklejsza metoda z modułu Module, dostępna dla każdego obiektu w Ruby. Rezultat jej wykonania to utworzenie w takim obiekcie pola o zadanej nazwie oraz metod: pobierającej i ustawiającej wartość tego pola. Dokładnie to samo można uzyskać w sposób zbliżony do Javy:
class Book
def author
@author
end

def author= value
@author = value
end

def title
@title
end

def title= value
@title = value
end
end
Osobiście nie mam wątpliwości, który sposób jest przyjemniejszy w użyciu. Ruby pozwala na pisanie szybciej i wygodniej, więc dlaczego z tego nie skorzystać?

poniedziałek, 8 października 2007

A teraz coś z zupełnie innej beczki

Już od niepamiętnych dla mnie czasów nie zdarzyło się coś podobnego. Mój mózg niemal wywrócił się na lewą stronę pod wpływem nowo przyswojonej wiedzy. Niemal czułem, jak nieużywane do tej pory synapsy nagle budzą się i zaczynają pracować nad tworzeniem nowych połączeń (skrzypiąc przy tym niemiłosiernie). To było uczucie z gatunku tych najbardziej odświeżających i wymagających zmiany dotychczasowych przyzwyczajeń. Jeszcze ciągle , mimo że upłynęły już 2 tygodnie, nie otrząsnąłem się z szoku intelektualnego.

Co wywołało ten wstrząs? Coś tak przyziemnego i nie kojarzącego się z odczuciami, które właśnie opisałem, jak... poznanie nowego języka programowania. Znacząco odmiennego od tych, które znałem i używałem do tej pory. Niszowego, ale i odkrywczego. Po prostu - czysto funkcyjnego. Tak, wiem, to żadna nowość, języki funkcyjne żyją od dawna i mają się dobrze, jednak jakimś dziwnym trafem do tej pory nie miałem z nimi styczności. Aż do teraz. I podejrzewam, że już na dobre zagoszczą w moim małym świecie.

Ten post to tylko zajawka. Szczegóły oczywiście też się pojawią. Na razie musi wystarczyć jeden - chodzi o język Haskell.

poniedziałek, 24 września 2007

Ach, te polskie krzaczki...

Nieprzyjemna sprawa - nie udało mi się za pomocą skryptu Ruby wrzucić do bazy Oracle danych do kolumny typu CLOB. Nieprzyjemna dlatego, że nie spodziewałem się takich kłopotów. A "nie udało mi się" uzyskać poprawnych "polskich znaczków" korzystając z Ruby-OCI8.

Wszystko było OK, dopóki operacje były przeprowadzane na kolumnach VARCHAR. Było dobrze również wtedy, gdy wrzuciłem kawałek tekstu do kolumny CLOB. W końcu jednak doszedłem do momentu, gdy do kolumny CLOB trzeba było wrzucić Naprawdę Duży Blok Tekstu. I Oracle stwierdził wtedy, że OCIError: ORA-01704. Inaczej mówiąc, Oracle wymaga dla siebie obsługi CLOB tak samo jak BLOB, czyli w Tak-Pokręcony-Jak-Tylko-Oracle-Potrafi-Sposób, z użyciem specjalnych poleceń zapisu i odczytu (najpierw "select ... for update" a potem "... . write"). Taki sposób jest wspierany przez Ruby-OCI, jednak z niewiadomych dla mnie powodów masakrowane są wtedy wszystkie nasze swojskie ogonki i kreski.

Problem w końcu obszedłem, pisząc programik w Javie. Tam cała obsługa CLOB w Oracle jest zupełnie przezroczysta, a martwi się o nią sterownik JDBC. Szkoda jednak, że odpowiadający mu sterownik dla Ruby ma jeszcze takie braki. Ciekawi mnie też, jak sprawa wyglądała by w PHP czy Pythonie. Może ktoś z czytelników tego bloga może podzielić się doświadczeniami?

piątek, 24 sierpnia 2007

Producent-Konsument w Javie 5

Całkiem niedawno, przeglądając dokumentację API dla Java 1.5, odkryłem że pojawił się tam całkiem ciekawy mechanizm upraszczający rozwiązanie odwiecznego problemu Producenta - Konsumenta.

Interfejs BlockingQueue, bo o nim mowa, reprezentuje typ synchronizowanej kolejki o zadanej pojemności. Udostępnia bardzo wygodne w użyciu metody wstawiania (add / offer / put) i pobierania obiektów (poll / take). Ich wygoda polega na ich różnorodności:
  • put wstawia obiekt, czekając jeśli trzeba na zwolnienie miejsca w kolejce
  • offer próbuje wstawić obiekt i zwraca true / false jako odpowiedź czy operacja się udała
  • add wstawia obiekt do kolejki "na siłę" i ewentualnie rzuca IllegalStateException jeśli kolejka jest już pełna
  • take pobiera obiekt z kolejki, czekając jeśli trzeba na pojawienie się obiektu w kolejce
  • poll pobiera obiekt z kolejki lub null, jeśli upłynie określona ilość czasu a kolejka jest pusta
Korzystając z powyższych metod, przykładowa implementacja klas producenta i konsumenta może wyglądać tak (wg oficjalnej dokumentacją Javy):

class Producer implements Runnable {
private final BlockingQueue queue;

Producer(BlockingQueue q) { queue = q; }

public void run() {
try {
while(true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... }
}

Object produce() { ... }
}

class Consumer implements Runnable {
private final BlockingQueue queue;

Consumer(BlockingQueue q) { queue = q; }

public void run() {
try {
while(true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... }
}

void consume(Object x) { ... }
}

piątek, 10 sierpnia 2007

Przygoda w świecie kamieni szlachetnych

Być może udało mi się niektórych spośród czytelników tego bloga przekonać do zapoznania z Ruby. Ale jak zacząć? Cóż, ponieważ chodzi o Ruby, to musi być trywialne! I rzeczywiście jest.

Zdecydowanie najprostszym sposobem na wypróbowanie możliwości Ruby jest Ruby w przeglądarce. Doskonałe rozwiązanie na początek, nie wymagające żadnej ingerencji we własny system. Dodatkowym atutem tej strony jest krótki, interaktywny kurs języka.

Jeśli już zdecydujemy się na instalację Ruby, to również nie będzie z tym problemu. Gotowe do instalacji pakiety są dla większości dystrybucji Linuxa, a instalatory dla MacOS X (One-Click Installer, Locomotive czy przez MacPorts) oraz Windows (oficjalna binarna wersja Ruby lub One-Click Installer z wieloma dodatkowymi bibliotekami).

Na tym jednak nie kończą się możliwości. Można również zacząć używać Ruby, nie przesiadając się ze środowiska Javy czy .NET, dzięki projektom JRuby i IronRuby. Gwarantują one pełny dostęp do VM tych środowisk i płynne współdziałanie kodu pisanego w innych językach razem z Ruby.

Podsumowując, nie ma powodu żeby przynajmniej nie spróbować przygody z Ruby!

piątek, 3 sierpnia 2007

Społeczność i nastawienie

Zapewne większość tych, którzy stykają się ze społecznością Ruby jest wręcz onieśmielona entuzjazmem, z jakim ludzie odpowiadają na ich pytania (czy to na Ruby Forum, czy na IRC, czy na grupie comp.lang.ruby).

Ponieważ jednak nie każdy ma ochotę / czas / odwagę spróbować swoją przygodę z Ruby (do czego oczywiście zachęcam!), to dla przykładu kilka cytatów z zaledwie paru dni (od 1. do 3. sierpnia) na Ruby-Talk:
  • "many thanks for the quick response and the valuable hint" - Ralph Grothe
  • "WOW! What a reply! This has got to be the single best thing for Ruby - the peeps on this mailing list are awesome!" - Gabriel Dragffy
  • "quick reply (didn't last a minute)" - Shai Rosenfeld
  • "Thanks a million!" - John Joyce
  • "That is exactly what I have been looking for" - Henrik Kristiansen
  • "Holy Cow! You guys are geniuses! Y'all are using these methods like I've never seen or even thought of using them before." - Todd Burch
Zwykle pierwsza odpowiedź pojawia się w ciągu godziny, a nawet trywialne pytania potrafią rozwinąć się w dyskusje o alternatywnych rozwiązaniach. Dodam też, że sam Yukihiro Matsumoto (twórca języka Ruby) pojawia się tu i ówdzie udzielając bardzo cennych rad.

Czego można chcieć więcej?

czwartek, 2 sierpnia 2007

Ruby potęgą jest i basta!

Pisanie w języku Ruby może być miłą odmianą po codziennej używaniu języków o statycznej kontroli typów. To oczywiście subiektywna opinia, ale może uda mi się, szanowny Czytelniku, przekonać chociaż do zapoznania się z możliwościami, jakie daje Ruby.

Oto konkretny przykład. Potrzebny nam był w pracy niewielki skrypt, który miał zaktualizować adresy e-mail użytkowników wg określonego klucza. Razem z kolegą usiadłem więc do JEdit i po chwili 64-liniowy skrypcik Ruby był gotowy. Otwierał połączenie z Oracle, odczytywał dane z blisko 60 tys. kont, przerabiał e-mail dla tych, które spełniały określone kryteria, po czym zapisywał z powrotem do bazy danych. Po drodze tworzył plik z opisem zmian i wypluwał na ekran kilka komunikatów.

Nic nadzwyczajnego, prawda? Dlatego byliśmy mocno zdziwieni, kiedy skrypt po uruchomieniu zamarł. Zjadł 90% czasu procesora i... to tyle. Po przerwaniu jego męczarni, dopisaniu jeszcze kilku tekstów o postępie działania i ponownym uruchomieniu okazało się, że wbrew pozorom coś robi, tylko strasznie wolno. Kilka minut później skrypt zakończył działanie, pozostawiając bazę we właściwym stanie, mnie mocno zaniepokojonego, a kolegę zniesmaczonego. Miałem co prawda podejrzanego:

if emails.include? row[1]
...
else
emails << row[1]
end

gdzie "emails" to tablica (Array). Czyżby metody "include?" oraz "<<" na tablicy 60 tys. elementów się nie sprawdzały? Odpowiedź przyszła dzień później. Na grupie comp.lang.ruby ktoś pytał czy istnieje metoda "not-include?". Jak to zwykle na c.l.r dostał dużo odpowiedzi ("unless" zamiast "if", "if not", "if !"). Między innymi, Brett Simmers zasugerował użycie Set zamiast Array, jeśli istotne jest tylko czy dany element znajduje się w zbiorze. I to było to!

W moim skrypcie niezbędne były zaledwie te zmiany:
  1. require 'set' (Set jest w standardowych bibliotekach Ruby)
  2. emails = Set.new (zamiast emails = Array.new)
Po ich wprowadzeniu i ponownym uruchomieniu, skrypt wykonał się w kilkanaście sekund.

Jaki z tego morał? Imponująca jest elastyczność kodu pisanego w Ruby, w którym wprowadzanie zmian nie musi powodować konieczności pisania od nowa całych jego fragmentów. A wartość dodana, jaką stanowi społeczność Ruby, jest przysłowiową wisienką na torcie.

Tak, dobrze widzisz. To kolejny blog w Internecie

Na każdym zebraniu jest taka sytuacja, że ktoś musi zacząć pierwszy
- Rejs

...i właśnie dlatego pojawia się ten post. Każdy blog jakoś się zaczyna i ten nie będzie wyjątkiem.

Tematyka tego bloga jest jasno określona - sztuka tworzenia oprogramowania. Postaram się pisać tylko o tym, co uznam za warte uwagi.

Skąd wziął się tytuł "Nie tylko młotek"? Jest krótką wersją motto tego bloga: "Kiedy masz pod ręką tylko młotek, wszystko dookoła wygląda jak gwóźdź". A co to niby ma znaczyć? Tylko i aż tyle, że ograniczanie sobie dostępnych narzędzi może znacząco utrudnić rozwiązywanie problemów.

Jeśli "młotkowo-narzędziowe" przedstawienie tematyki bloga Tobie nie odpowiada, to może spróbuj odpowiedzieć sobie na pytanie dlaczego systemów operacyjnych nie pisze się w Javie? Albo dlaczego serwerów WWW nie pisze się w assemblerze? Kiedy już odpowiesz sobie na takie pytania, zrozumiesz o czym jest ten blog :-)