Wrażenia po Java Developer' Days 2009

Wpis dodany 20 października 2009 o godz. 18:22:59 w kategorii Java, Konferencje, Techblog.

Parę dni temu w Krakowie dobiegła końca kolejna edycja konferencji skupionej wokół Javy. Moje pierwsze wrażenie po JDD 09 jest takie, że mogłoby być nieco lepiej. Zarówno poprzednia edycja JDD, jak i tegoroczne konferencje javowe, czyli GeeCON oraz Javarsovia, poprzeczkę postawiły wysoko, nawet bardzo. Wydaje mi się więc, że tegoroczna edycja wypadła nieco słabiej, choć oczekiwania były naprawdę spore.

Wykłady miały miejsce na Uniwersytecie Ekonomicznym i wydaje mi się, że nie była to miejscówka marzeń. Parę wykładów było prowadzonych w dwóch ścieżkach, a druga sala nadawała się bardzo średnio na konferencję, była za mała i mimo trzech (ale zbyt małych) ekranów, sporo osób nie mogło śledzić slajdów i kodowania na żywo. Zresztą główne miejsce, czyli przerobiona sala gimnastyczna, też nie zachwycała, podobnie zresztą jak cały catering. Nie to jednak jest esencją konferencji (czyż nie? ;), więc może zostawmy już średnio smaczny obiad, braki w ciasteczkach czy kiepsko pomyślane identyfikatory (najlepsze blipowe tagi dla nich to #usability oraz #fail).

Zaczęło się, co nieco dziwne, od wykładu sponsora i jak na taki charakter wykładu, to było całkiem nieźle. Dwójka prelegentów (T. Baeyens i J. Barrezz) Rad Hata pokazywała możliwości jBPM. Sesja na kolana nie rzuciła, chociaż samo soft wydał się całkiem sensowny, większość pokazu to były nagrane filmiki, więc wszystko działało bez zarzutu.

Dopiero następnym wykładem był "keynote" Marka Richardsa na temat "messagingu" (JMS i te sprawy). Oczywiście merytorycznie bez zarzutu, chociaż prelegent skupił się na podstawach. Była chwila kodowania na żywo, ogólnie wykład niezły, chociaż już po drugim użyciu wstawka o "polish vodka" wydawała się wystarczająco mało śmieszna, a prelegent dopiero się rozkręcał w temacie ;). Było fajnie, ale przecież to keynote, więc inaczej być raczej nie mogło. Zdarzyło mi się jednak uczestniczyć w paru lepszych wykładach.

Dalej był wykład kolejnego sponsora, czyli firmy e-Point na temat sytuacji (nie aż tak) wyjątkowych. Zwykle wykłady prelegentów z tej firmy skupiają się na dość częstych problemach czy zagadnieniach, jakie spotykamy programując w szeroko rozumianym JEE. Bywa różnie, ale tym razem akurat było całkiem konkretnie, a paru wymienionych na wykładzie rzeczy programiści nie nauczą w szkole, chociaż koniecznie powinni. Oczywiście część przedstawionych idei była też dyskusyjna. Sporo miejsca poświęconego zostało "checked exceptions", w skrócie temu, że są złe. Nie jest to oczywiście odosobniona opinia; o tym, że "checked exceptions" muszą odejść, pisał ostatnio też Miško Hevery. I tak naprawdę trudno się tu z czymkolwiek nie zgodzić, osobiście dla mnie na topie bezsensu są UnsupportedEncodingException oraz wyjątki w czasie próby stworzenia nowego dokumentu DOM, no ale to żale na osobny wpis. Dobrym wzorcem (nawet bardzo) jest pokazywanie użytkownikowi wraz ze stroną błędu losowego kodu powiązanego z owym błędem (np. w Stripes takie zachowanie bardzo łatwo jest osiągnąć), w ten sposób kiedy dostajemy od użytkownika informację o problemie, potrafimy łatwo powiązać z nim konkretny wyjątek. Ogólnie wykład niezły, takie "must know" dobrego programisty.

Następnie wybrałem wykład Waldka Kota o bardzo skomplikowanym temacie. Generalnie uważam, że był to chyba najlepszy wykład JDD 09, tylko gdyby zatytułować go "współbieżność w JEE może być sexy", zrobiłby większą furorę ;). Pokazanych zostało parę pomysłów na współbieżność, kiedy działamy w środowisku JEE. Prezentacja Work Manager API na WebLogicu pokazała, że z wątkami można żyć, i to całkiem bezboleśnie. No i Waldek jest zawsze dowodem na to, że Polak jednak może poprowadzić ciekawą prezentację techniczną, i to bez czerstwych żartów.

Wojtek Szeliga opowiedział o "code review". Pokazał parę narzędzi, aczkolwiek prezentacja była bardzo zorientowana na produkty firm JetBrains i Atlassian. Nie twierdzę, że to źle, bo zasługują one na swoją bardzo dobrą opinię, chociaż na IntelliJ z Eclipse jeszcze nie migruję. Były też slajdy o dobrych praktykach przy przeglądach kodu, parę recept jak w ogóle zacząć, no i slajd o "code review" vs "pair programming" też się znalazł (swoją drogą w temacie polecam podcasty AgileTuning). Było całkiem zwinnie, czyli ogólnie w porządku.

Dalej każdy miał na pewno dylemat, który wykład zagranicznej gwiazdy JDD 09 wybrać. Jako że parę testów w Groovym już napisałem, postanowiłem znowu posłuchać Marka Richardsa, który opowiedział o paru anty-wzorcach. Oczywiście było sympatycznie, chociaż do większości sytuacji pasuje cytat "Z czego się śmiejecie? Z siebie samych się śmiejecie!". Przedstawione złe praktyki można by odnieść nie tylko do Javy, ba, nie tylko do samego programowania. Konkretnie pokazane zostały m.in. zasada gwoździa (mając tylko młotek wszędzie widzi się gwoździe) i kult cargo (jakoś działa, ale nie wiem co, jak i gdzie).

Przedostatni wykład dla mnie to Domain Driven Design + Seam, o których opowiadał nieco chyba spięty Sławek Sobótka. Od strony merytorycznej było za to całkiem dobrze, zarówno jeśli chodzi o zainteresowanie DDD, jak i samym JBoss Seamem. Plus za przedstawienie konkretów, gdyż o ile o DDD się słyszy całkiem sporo, to do informacji o tym, jak to zagadnienie ugryźć w praktyce, wcale tak łatwo dotrzeć nie można.

Na deser został wykład Scotta Davisa o REST. Trzeba jedno przyznać amerykańskim prelegentom - potrafią robić show. I Scott je zrobił. Co do samej treści - było trochę przykładów, kodowanie na żywo w Groovym, porównanie złożoności SOAP z prostotą REST. Ogólnie prelegent pozostawił po sobie bardzo pozytywne wrażenie.

Napisałem na początku, że mogłoby być lepiej. Nie chcę jednak napisać, że konferencja się nie udała, bo to nie byłaby prawda. Było dobrze, miejscami bardzo dobrze i nieobecni mają czego żałować, bo jeśli nawet nie było mega hitów wśród wykładów i zupa była za słona ;), to zawsze zostaje tzw. społeczny aspekt konferencji. Miło było znowu zobaczyć (nie aż takich) starych znajomych, rozsianych obecnie po najróżniejszych firmach, nie tylko tych z Krakowa.

Odkrywanie wiedzy z danych

Wpis dodany 06 października 2009 o godz. 20:39:14 w kategorii Data mining, Java, Techblog.

Taki tytuł może świadczyć tylko o tym, że na tapetę wchodzi data mining. Ludzie od marketingu wolą pewnie ogólniejsze określenie "business intelligence", no ale chodzi mniej więcej o to samo, czyli o użycie paru fajnych algorytmów, aby przekopać tony danych i wyciągnąć jakieś nietrywialne wnioski.

Prawdę mówiąc, jeśli chodzi o Javę, to jakichkolwiek doświadczeń z BI nie mam żadnych poza researchem, który zrobiłem ostatnio z czystej ciekawości. Muszę natomiast stwierdzić, że Microsoft podszedł do sprawy całkiem profesjonalnie i w MS SQL Serverze od paru już wersji jest sporo przydatnych rzeczy do zabawy w drążenie danych, polecam zresztą stronę SqlServerDataMining.com. Firma Oracle też się chwali swoim pakietem o wiele mówiącej nazwie Oracle Data Mining. Nie jesteśmy jednak skazani tylko i wyłącznie na dostawców baz danych, chociaż wydaje się, że jak na Javę możliwości wyboru są dość ograniczone.

Warto zacząć od tego, że mamy dwa JSR poświęcone API dla data miningu, są to JSR-73 (Data Mining API) oraz JSR-247 (Data Mining 2.0). Pierwszy zgrzyt to informacja, że prace nad wersją 2.0 APi zostały wstrzymane. Drugim zgrzytem jest to, że raczej nie istnieje implementacja nawet pierwszej wersji standardu, której źródła można obejrzeć i z której można za darmo skorzystać. Liderem obu specyfikacji jest Mark Hornick, pracujący dla Oracle i, co za niespodzianka, w produktach tejże firmy należy szukać jakiejkolwiek implementacji opisywanego właśnie standardu. Można przejrzeć nawet przykłady użycia.

Ok, w takim razie co robić, jak żyć. Zasadniczo do wyboru mamy narzędzie Weka (sponsorowane przez Pentaho) oraz Rapid Miner (kiedyś znane jako YALE). Obydwa rozwiązania możemy używać "standalone", ale możemy je też wykorzystać w naszym kodzie. Postaram się przedstawić tutaj najprostszy przypadek użycia, czyli klasyfikację na klasycznym zbiorze danych (ang. "data set") Iris, opisującym parametry irysów (to takie kwiatki, gdyby ktoś pytał). Zbiór ten zawiera długości i szerokości dwóch różnych rodzajów płatków (ang. "petal" oraz "sepal") irysa, w sumie opisuje 150 roślinek z trzech różnych gatunków. Chodzi oczywiście o rozpoznanie gatunku na podstawie zadanych parametrów.

irys
Kwiatek z własnego ogródka, jakby nie patrzeć - irys.
        // załadowanie zbioru danych
        DataSource source = new DataSource("/tmp/iris.arff");
        Instances data = source.getDataSet();
        data.setClassIndex(data.numAttributes() - 1);

        // wybór algorytmu
        Classifier classifier = new NaiveBayes();
        classifier.buildClassifier(data);

        // uczenie modelu
        Evaluation evaluation = new Evaluation(data);
        evaluation.evaluateModel(classifier, data);

        // classifier.classifyInstance(inst) aby użyć modelu
        String summary = evaluation.toSummaryString();
        out.println(summary);

Powyższy kawałek kodu przedstawia pracę z Weką. Oczywiście tak na dobrą sprawę powinniśmy podzielić posiadane dane na dwa zbiory, jeden do uczenia, drugi do testowania, no ale powiedzmy, że dla celów prostej prezentacji tak też jest OK. Więcej informacji na temat używania Weka w kodzie można znaleźć na wiki projektu. Rozszerzenie *.arff to własny format danych Weki, ale równie dobrze można podpiąć jakikolwiek inny. Nieskalibrowany w żaden sposób algorytm miał 96% skuteczności w rozpoznawaniu gatunków w zbiorze, na którym się uczył.

        // inicjalizacja
        RapidMiner.init();
        
        // załadowanie zbioru danych
        Operator xrffSource = OperatorService.createOperator(XrffExampleSource.class);
        xrffSource.setParameter(XrffExampleSource.PARAMETER_DATA_FILE, "/temp/iris.xrff");

        // przetwarzanie danych
        IOContainer container = xrffSource.apply(new IOContainer()); 
        ExampleSet exampleSet = container.get(ExampleSet.class); 
     
        // uczenie modelu
        Learner learner = OperatorService.createOperator(NaiveBayes.class); 
        Model model = learner.learn(exampleSet);

        // informacje o modelu
        out.println(model.toResultString());

        // stosujemy wytrenowany model na znanych już danych
        Operator modelApplier = OperatorService.createOperator(ModelApplier.class); 
        container = modelApplier.apply(container.append(model));

        // wyświetlamy dane
        ExampleSet resultSet = container.get(ExampleSet.class);
        int i = 0;
        Iterator<Example> examples = resultSet.iterator();
        while (examples.hasNext()) {
            out.print( ++i + ": ");
            Example reader = examples.next();
            Attributes attributes = reader.getAttributes();
            out.println("predicted: " + reader.getValueAsString(attributes.getPredictedLabel()));
        }

Co w takim razie można powiedzieć o Rapid Miner? Jak widać, jest o wiele bardziej skomplikowany. Właściwie to trudno użyć go jako biblioteki, wpierw i tak musimy go zainicjalizować, co skutkuje potężną dawką logów. Może on korzystać z algorytmów Weki. Samo jego API bardzo mi się nie podoba, tworzenie obiektów poprzez refleksję spowodowane jest tutaj "wstrzykiwaniem" kontekstu do każdego nowo tworzonego obiektu. Tak samo "brzydka" jest metoda setParameter zamiast standardowych setterów, a to przecież jeszcze chyba nadal Java. Wedle dokumentacji w bardziej skomplikowanych przypadkach warto używać klasy Process, aczkolwiek sama dokumentacja nie wygląda na zbyt aktualną, najprostsze przykłady się nie kompilują. Abstrahując od brzydoty API, narzędzia bardzo przyjemnie używa się poprzez klikalne GUI. Warto także obejrzeć filmiki (oczywiście oparte na jakiejś o wiele starszej wersji).

Rapid Miner
Proste drzewko decyzyjne w Rapid Minerze.

Podsumowując, jest czym się bawić, aczkolwiek wypada też mieć nieco żyłki odkrywcy ;). W każdym razie zachęcam, bo data mining to całkiem fajny temat do eksploracji, zwłaszcza jeśli chodzi o samo działanie algorytmów.

ClassLoadery

Wpis dodany 04 października 2009 o godz. 14:34:14 w kategorii Java, OSGI, Techblog.

ClassLoader to obiekt, który jest odpowiedzialny za ładowanie klas. Tak twierdzi javadoc w opisie do abstrakcyjnej klasy ClassLoader i z pewnością ma rację. W pisaniu prostych programów jakakolwiek wiedza na temat ładowania klas nie jest nam specjalnie potrzebna, nie ma jednak co ukrywać, że Java nie została stworzona do prostych programów, ale do szeroko rozumianego "enterpri(s|c)e". A tam tego typu wiedza jest nie tylko przydatna, ale wręcz konieczna.

Zróbmy więc małą powtórkę z tego, jak ładowanie klas działa i dlaczego, jeśli w ogóle, jest fajne. Otóż powodem powstania koncepcji classloaderów w Javie były aplety, które w swoim czasie miały podbić świat. Wyszło jak wyszło, ale classloadery zostały. Liczba mnoga jest jak najbardziej uzasadniona, gdyż do czynienia mamy co najmniej z trzema, są to bootstrap, extension i system.

Typ "bootrstrap" ładuje klasy najważniejsze, systemowe, zazwyczaj te z rt.jar, czyli choćby java.lang.String. Wywołując getClassLoader() dla String.class dostaniemy null. Typ "extension" szuka klas w jre/lib/ext, a dopiero typ "system" ładuje klasy naszej aplikacji. Oczywiście w najprostszym przypadku, bo mamy przecież serwery aplikacyjne i OSGI, ale o tym następnym razem (i o samym OSGI, i o ładowaniu klas w takim wypadku).

public class ClassloaderFun {
    public static void main(String[] args) {
        // dostajemy null
        out.println(String.class.getClassLoader());
        // dostajemy sun.misc.Launcher$AppClassLoader@7d772e
        out.println(ClassloaderFun.class.getClassLoader());
        out.println(Thread.currentThread().getContextClassLoader());
    }
}

Idźmy dalej, bowiem ładowanie klas nierozłącznie powiązane jest z hierarchią. Każdy classloader (poza typem "bootstrap") posiada jakiegoś przodka. Zazwyczaj zanim nasz classloader spróbuje załadować jakąś klasę, odpytuje najpierw właśnie owego przodka (wedle tzw. podejścia "parent first"). Każdy wątek posiada dostęp do kontekstowego classloadera, czyli tego, który aktualnie jest w użyciu. Zawsze możemy go podmienić za pomocą metody setContextClassLoader. Taka możliwość przydaje się choćby wtedy, kiedy chcemy, aby nowo tworzony wątek miał dostęp do klas ładowanych dynamicznie np. poprzez URLClassLoader. Warto jeszcze wspomnieć, że nie ma problemu z załadowaniem kilku różnych klas o tej samej nazwie zarówno samej klasy, jak i pakietu. Funkcję przestrzeni nazw pełnią tutaj właśnie classloadery.

        URL url = new URL("file:///tmp/plugin.jar");
        URLClassLoader pluginLoader = new URLClassLoader(new URL[] { url });
        Class clazz = pluginLoader.loadClass("net.miracki.classloader.plugin.PluginImpl");
        out.println("Loaded class: " + clazz + ", classloader " + clazz.getClassLoader());
        ((Plugin) clazz.newInstance()).doSomething();

Z tematem ładowania klas powiązany jest wybór implementacji dla różnych API. Istnieje mechanizm SPI, czyli Service Provider Interface, który dzięki odpowiednim znacznikom w strukturze pliku *.jar pozwala poinformować maszynę wirtualną, że implementacją chociażby javax.xml.parsers.SAXParserFactory jest w przypadku Xercesa org.apache.xerces.jaxp.SAXParserFactoryImpl. Dokładnie odbywa się to dzięki umieszczeniu w katalogu /META-INF/services/ pliku o nazwie interfejsu, a którego treścią jest nazwa implementacji.

Z SPI można korzystać albo i nie, na przykład bardzo popularna fasada do logowania, czyli commons-logging używa tego mechanizmu, ale robi też o wiele więcej, co niesie ze sobą różne skutki. Otóż domyślna fabryka (do wyboru implementacji której używa się właśnie SPI) do tworzenia loggerów, czyli klasa org.apache.commons.logging.impl.LogFactoryImpl dynamicznie (tak, to słowo klucz, a kawałki kodu poniżej) szuka odpowiedniego loggera. W szczegóły wgłębiać się nie będę, zwłaszcza, że zrobił to już świetnie w swoim artykule o problemach związanych z ładowaniem klas w commons-logging Ceki Gülcü, twórca m.in. Log4j. Problemy te zaowocowały stworzeniem nowej fasady do logowania, czyli SLF4J oraz następcy Log4j, czyli Logbacka. Poniżej wycinki z kodu commons-logging:

private static final String[] classesToDiscover = {
    LOGGING_IMPL_LOG4J_LOGGER,
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog"
};
for(int i=0; (i<classesToDiscover.length) && (result == null); ++i) {
    result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
                Class c = null;
                try {
                    c = Class.forName(logAdapterClassName, true, currentCL);
                } catch (ClassNotFoundException originalClassNotFoundException) {
                    // tu trochę kodu obsługującego wyjątek
                }

                constructor = c.getConstructor(logConstructorSignature);
                Object o = constructor.newInstance(params);

                if (o instanceof Log) {
                    logAdapterClass = c;
                    logAdapter = (Log) o;
                    break;
                }

To, na co trzeba zwrócić uwagę, to właśnie konsekwencje dynamicznego ładowania klas, które opisał Ceki Gülcü. Właściwie chociaż jego artykuł dotyczy commons-logging i wyboru implementacji loggerów, to jest świetnym źródłem wiedzy na temat ładowania klas. Dobitnie pokazuje, dlaczego nie warto kombinować tam, gdzie to jest zupełnie niepotrzebne. Zresztą nowa fasada Cekiego do logowania, czyli wspomniany już SLF4J, nie wyczynia cudów z classloaderami, wystarczy przejrzeć manual.

A jeśli już jesteśmy przy różnych issues w ładowaniu klas, to niedawno chwalony przeze mnie Stripes Framework też miał problem z classloaderami, gdy próbowano go użyć w środowisku OSGI. Wniosek jest więc taki, że Java i jej wirtualna maszyna pozwalają na wiele, tylko czasem warto przemyśleć konsekwencje swoich decyzji, na pierwszy rzut oka dynamiczne odnajdywanie loggerów też wydawało się być super.