środa, 21 listopada 2012

Enkapsuluj swoje wyjątki

Tyle o tej enkapsulacji, a mało kto pisze o tym w kontekście wyjątków.
Może najpierw trochę o tym jakie wyjątki rzucać. Zgodnie z arkanami sztuki metoda powinna rzucać wyjątki _tylko_i_wyłącznie_ istotne z punktu widzenia interfejsu jaki reprezentuje. Biorąc za przykład:
interface UserRepository {
    List<User> activeUsers() throws DataAccessException;
}
Metoday activeUsers zwraca listę użytkowników i jest zupełnie sensownym to, że zgłosi wyjątek DataAccessException, gdy pojawi się błąd z dostępem do danych. Zgłaszanie wyjątku typu BufferOverflowException, albo DOMException nie da klientowi żadnej użytecznej informacji na temat tego co się stało. Klienta interesuje to, że repozytorium ma problem z dostępem do danych, a nie to, że w środku jakiś bufor się przepełnił, albo przetwarza błędny dokument DOM.

To prowadzi do myśli o enkapsulacji. Gdybyśmy sprzedawali bułki w sklepie, a nasz piekarz wstrzymałby dostawę na parę dni, ponieważ w jego piekarni pojawił mklik próchniczek - nie informowalibyśmy o tym naszych klientów, bo już więcej by się nie pojawili. Taką informację zachowamy dla siebie, zmienimy piekarza, a klientowi powiemy, że mamy problemy z dostawą na parę dni. I tak też powinniśmy robić w świecie Javy. Metoda activeUsers() nie powinna zwracać wyjątku typu SQLException czy FileNotFoundException. Klienta to nie interesuje skąd repozytorium bierze dane. Klient po prostu chce te dane!
Wyjątki powinny być enkapsulowane przed rzuceniem ich dalej. Przykładowa implementacja metody activeUsers:
public List<User> activeUsers() throws DataAccessException {
    try {
        // wyciąganie danych z bazy
    } catch (SQLException e) {
        throw new DataAccessException(e);
    }    
    return users;
}
DataAccessException ładnie enkapsuluje w sobie co się stało w metodzie activeUsers. Użytkownik repozytorium jest wolny od tej implementacji i wymiana na inne źródło danych nie pociąga za sobą daleko idących zmian w kodzie.
Jeśli to repozytorium byłoby wystawiane np. przez webservice do zdalnych klientów to implementacja może wyglądać tak:
public List<User> activeUsers() throws DataAccessException {
    try {
        // wyciąganie danych z bazy
    } catch (SQLException e) {
        logger.error(e);
        throw new DataAccessException("Error during data fetching");
    }    
    return users;
}
W takim wypadku klient nie ma zupełnie wiedzy o tym czego używamy do przechowywania danych, a my nie tracimy informacji o błędzie.

wtorek, 30 października 2012

Nie zwracaj nulla!

Po raz kolejny siedzę w czyimś kodzie w którym co chwilą jest if:
public List <User> usersList() {
    List<Object[]> users = db.query("SELECT id, name, email FROM users");
    if (users == null);
        return null;
    //...
    // mapowanie na obiekty i zwrot wyników
I dalej gdzieś w serwisie czy czymś:
List<User> users = usersRepo.usersList();
if (users == null)
    return null; // i zabawa trwa dalej!
Takie IFy mają tendencje do ciągnięcia się przez wszystkie warstwy sięgając nawet samego widoku. Pozbycie się tego nie tylko poprawi wygląd kodu, zmniejszy złożoność, ale też ograniczy możliwość wystąpienia NullPointerException. A rozwiązanie tego jest proste - zwracać tzw. NullObject. Koncepcja prosta jak budowa cepa. W tym przypadku możemy szybko zmienić by obiekt zwracał pustą listę:
public List <User> usersList() {
    List<Object[]> users = db.query("SELECT id, name, email FROM users");
    if (users == null);
        return Collections.<User>emptyList();
    //...
    // mapowanie na obiekty i zwrot wyników
I już nie trzeba się bać o NPE i kod robi to co powinien (czyli zwrócić _listę_). Ciekawszym przykładem będzie, gdy zaczniemy mieć różne implementacje jakieś funkcjonalności. Np. gdy mamy odpowiednią klasę odpowiedzialną za kolorowanie obramowania, ale czasami nie musimy go kolorować. Najpierw wersja 'brzydka':
public interface BorderPainter {
    void paint(Element element);
}

public class BorderPainterFactory {
    public static BorderFactory get(BorderType type) {
        switch (type) {
            case BOLDED:
                return new BoldetBorderPainter();
            case DOTTED:
                return new DottedBorderPainter();
            case NONE:
                return null;
        }
    }
}

// i użycie
BorderPainter painter = BorderFactory.get(type);
// albo gorsze - sprawdzać czy type jest typu NONE przed wywołaniem factorki
if (painter != null) { 
    painter.paint(someElement);
}
A lepiej sobie wprowadzić implementację BorderPainter która najzwyczajniej w świecie nic nie robi :)
public NoBorderPainter implements BorderPainter {
    public void paint(Element element) {}
}
// i wywołanie jest zawsze takie samo:
BorderPainter painter = BorderFactory.get(type);
painter.paint(someElement);
Takie podejście jest dużo bardziej object oriented, zmniejsza złożoność, zwiększa elastyczność, czytelność, stabilność i testowalność kodu. Oczywiście nie zawsze jest to możliwe, by takowy NullObject skontruować i wprowadzić. Ale z doświadczenia widzę, że około 90% takich konstrukcji może zostać zrefaktoryzowanych.

Piękna prezentacja na temat usuwania IFów z kodu autorstwa Misko Heverego (co mocno rozszerza mój wątek o null objectach) można zobaczyć poniżej. POLECAM!


poniedziałek, 26 marca 2012

GeeCON 2012 [Film:]


Czwarta edycja GeeCONa zbliża się wielkimi krokami! Dla tych co jeszcze nie wiedzą odbędzie się w pomiędzy 16 a 18 Maja w Poznaniu.
Dzisiaj na twitterze szumu narobił nowy film promocyjny podsumowujący zeszłoroczną edycję:
The laws of GeeCON
Jak widać warto tam być! W tym roku wśród gwiazd konferencji znajdą się:
  • Bruce Eckel
  • Gavin King
  • Ivar Jacobson
  • Adam Bien
  • Kevlin Henney
  • Pete Muir
a z polskich znanych twarzy:
  • Jacek Laskowski
  • Dawid Weiss
  • Tomek Kaczanowski
  • Piotr Walczyszyn

Schedule jest już znany, więc można powoli planować ścieżkę prezentacji podczas konferencji.

Jak ktoś już się zapisał to można sobie na bloga wrzucić ładnego bannerka :-)

Do zobaczenia!


piątek, 1 lipca 2011

Poznań JUG - Warsztaty z języka Scala

Dzięki uprzejmości Grzegorza Balcerka, który zgłosił pomysł oraz siebie jako ochotnika do poprowadzenia warsztatów, mam przyjemność zaprosić wszystkich na otwarte warsztaty z języka Scala! 
Scala ostatnio robi się dość popularna, więc warto przyjść i "liznąć" trochę z tego języka. Bez przedłużania dane szczegółowe:

Data: 09.07.2011 (sobota)
Godzina: 10:00
Miejsce: Siedziba Cognifide (uwaga zmiana adresu!)  ul. Murawa 12-18
Rejestracja: tutaj (wymagana, liczba miejsc ograniczona!)
Wymagane jest posiadanie własnego laptopa (prosimy zabrać listwy zasilające;) z JDK 6.

Opis warsztatów nadesłany od autora:
Zakres: od podstaw do .... - nie wiem, zależy na ile czas 
pozwoli.Poniżej kolejne tematy które mogłyby być omawiane, ale nie 
wydaje mi się żeby było możliwe omówienie wszystkiego - ze względów 
czasowych: wprowadzenie, podstawy składni, podstawowe typy danych, 
wyrażenia sterujące, klasy, cechy, metody, obiekty, typy ogólne, 
standardowe typy ogólne, funkcje, dopasowywanie wzorców, klasy i 
obiekty przypadków, pakiety, klauzule importu, uprawnienia, wyjątki i 
wyrażenie return, modyfikator implicit, kolekcje, pętla for, XML, 
programowanie interfejsów graficznych, programowanie współbieżne, 
parsowanie, adnotacje, języki dostosowane do dziedziny, kompilator
Przewidziana jest przerwa obiadowa z pizzą i napojami :-)


Zapraszam!

piątek, 24 czerwca 2011

(Prawdopodobnie ostatnie) Podsumowanie nowości w Java 7

Wpisów na temat rzeczy, które mają się pojawić w Java 7 już kilka u mnie powstało (3). Ale ten chyba będzie ostatnim, bo termin wydania jest już znany i bliski - 28 lipca 2011! Z tej okazji postanowiłem przejrzeć ten brzydki dokument JSR 336.

Żeby nie zanudzać i nie rozwodzić się za bardzo (większość rzeczy już jak wspomniałem opisałem), lista zwycięzców tej długiej bitwy:

Bytecode
Lepsze wsparcie dla języków dynamicznych opartych na JVM - dodanie instrukcji invokedynamic.

Java Beans
Tutaj głównie poprawki w API. Dodano adnotację @Transient i oznaczono nią trochę getterów, poza tym poprawki w API ;-)

Kolekcje
Tutaj podobnie dodatki w API. Głównie chwalą się dodaniem metod Collections.emptyIterator() oraz Collections.emptyEnumeration() oraz zaktualizowaniem dokumentacji.

I/O
Nowe IO! NIO.2 opisane w JSR 203 :-) Wsparcie dla POSIX i ACL, nowy pakiet java.nio.file zawierający stuff do obsługi metadanych, dowiązań, ułatwienia przeglądania katalogów (interface FileVisitor)  i jeszcze trochę tego, ale za bardzo się nie zagłębiałem akurat w ten temat.

java.lang
Tutaj tzw. project coin (JSR 334) z zestawem kilku zmian do samego języka: switch po stringu, blok try dla zasobów wymagających zamykania, multi-catch, rozszerzona składnia dla liczb, dedukcja typów (dla generyków). Poza tym wiadomo dodatki/usprawnienia w API (w tym już chyba sławny java.util.Objects z metodami do porównywania obiektów, obliczania hashcode itp.).

Class loader
Wsparcie dla współbieżnych class loaderów! :-)

Współbieżność
Dodano nowy framework ForkJoin przeznaczony dla aplikacji z intensywnymi obliczeniami. Posiada dobre wsparcie dla maszyn wieloprocesorowych i jest niezależny od platformy. + dodatki w standardowym API.

I18N
Wsparcie dla Unicode 6.0.0


To były główne zmiany, poza tym różnorakie zmiany w: java.util.loggingjava.util.regex, java.net, JMX, java.util.zip, bezpieczeństwie (obsługa TLS 1.2) oraz dodatki do Swinga i AWT.

Jak widać ostatecznie domknięcia nie zmieściły się w wersji finalnej. To samo tyczy się projektu Jigsaw czyli modularyzacji JDK (co mnie bardzo zdziwiło, bo myślałem, że to pewniak). Jestem z tego powodu bardzo zawiedziony, bo to dla mnie były właśnie te killing-features, które wprowadziłyby nowy blask językowi.

No cóż... z nadzieją musimy obserwować rozwój specyfikacji Javy 8... :-/

niedziela, 19 czerwca 2011

Pierwszy Job z Hadoop

Zanim napiszemy pierwszego joba trzeba przygotować sobie środowisko. Obsługa wszystkiego z konsoli może z czasem być nużąca (chociaż ja tam lubię;).

Ja do dewelopmentu używam NetBeansa i polecam wtyczkę Karmaspehere Community Edition. Pozwala ona w łatwy sposób definiować i odpalać sobie zadania Hadoopa.
Aby zainstalować tę wtyczkę należy dodać do listy 'update sites' adres: http://hadoopstudio.org/updates/updates.xml. Ja zainstalowałem wszystkie 4 dostępne pluginy od Karmasphere. Polecam też się zarejestrować i aktywować plugin, bo będą się pojawiać komunikaty o aktywacji (ale chyba można pracować bez niej).

Ok, możemy zacząć pisać :-) Projekt oparłem o maven, pom.xml nie jest zbyt rozdbudowany:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>pl.lstachowiak</groupId>
    <artifactId>HadoopSamples</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>HadoopSamples</name>
    <url>http://blog.lstachowiak.pl</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.apache.mahout.hadoop</groupId>
            <artifactId>hadoop-core</artifactId>
            <version>0.20.1</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
    </dependencies>
</project>

Problem jaki wybrałem dla pierwszego przykładu to analiza statystyk serwera Apache. Bierzemy plik access.log i analizujemy typy żądań do serwera. Na końcu chcemy mieć liczbę żądań każdego typu.
Mój pomysł na Map i Reduce jest następujący:
Czyli:
- Hadoop wczytuje i dzieli plik na tzw 'input splits'
- przekazuje każdy split do funkcji map, gdzie kluczem jest numer linii w pliku a wartością sama linia
- implementowana przez nas funkcja map sprawdza jaki tym żądania HTTP znajduje się w linii i zwraca jako klucz typ żądania a jako wartość liczbę 1 (umownie sobie tak robimy)
- Hadoop wykonuje tzw. shuffle grupując nam wyniki z map w pary [TYP] -> [KOLEKCJA JEDYNEK]
- nasza funkcja reduce jedyne co robi to zlicza liczbę elementów w kolekcji i zwraca jako klucz typ żądania a jako wartość liczbę elementów w kolekcji co jest zarazem wynikiem końcowym
- Hadoop zapisuje dane na dysk

(Bardzo) Możliwe, że nie jest to idealne rozwiązanie, ale nie mam jeszcze wielkiego doświadczenia z Hadoop ;)

Przejdźmy do implementacji. Funkcja map:
HttpMethodMapper.java:
package pl.lstachowiak.hadoop.sample1;

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;


public class HttpMethodMapper extends Mapper<LongWritable, Text, Text, IntWritable> { // 1

    @Override
    public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        HttpMethod method = getMethodFromLine(value); // 2
        context.write(new Text(method.name()), new IntWritable(1)); // 3
    }

    HttpMethod getMethodFromLine(Text value) {

        if (value.find(HttpMethod.CONNECT.name()) != -1) {
            return HttpMethod.CONNECT;
        }

        if (value.find(HttpMethod.DELETE.name()) != -1) {
            return HttpMethod.DELETE;
        }

        if (value.find(HttpMethod.GET.name()) != -1) {
            return HttpMethod.GET;
        }

        if (value.find(HttpMethod.HEAD.name()) != -1) {
            return HttpMethod.HEAD;
        }

        if (value.find(HttpMethod.OPTIONS.name()) != -1) {
            return HttpMethod.OPTIONS;
        }

        if (value.find(HttpMethod.PATH.name()) != -1) {
            return HttpMethod.PATH;
        }

        if (value.find(HttpMethod.POST.name()) != -1) {
            return HttpMethod.POST;
        }

        if (value.find(HttpMethod.PUT.name()) != -1) {
            return HttpMethod.PUT;
        }

        if (value.find(HttpMethod.TRACE.name()) != -1) {
            return HttpMethod.TRACE;
        }

        return HttpMethod.UNKNOWN;
    }
}
Krótki komentarz do tego co się dzieje w kodzie:
1 - klasa mappera musi (od wersji API 0.20) rozszerzać klasę Mapper. Parametrami są kolejno:
- typ klucza (numer linii)
- typ wartości (linia w pliku)
- typ klucza wynikowego (typ żądania http)
- typ wartości wynikowe (liczba 1)
2 - pobranie typu żądania z linii, wiem, że rozwiązanie jest dalekie od idealnego, ale nie o to chodzi w przykładzie ;)
3 - zapis wyniku

Jak widać Hadoop posiada własny zestaw typów danych. W większości są to obudowane typy podstawowe. Stworzone zostały one aby w troszkę lepszy sposób serializować dane i przesyłać przez sieć. Zaleca się ich używanie. Typ HttpRequest to zwykły enum z listą dostępnych typów żądań HTTP. Dostępny jest w źródłach.

Funkcja reduce:
HttpMethodReducer.java:
package pl.lstachowiak.hadoop.sample1;

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Reducer.Context;


public class HttpMethodReducer extends Reducer<Text, IntWritable, Text, IntWritable> { // 1

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { // 2
        int counter = countValues(values); // 3
        context.write(key, new IntWritable(counter)); // 4
    }
    
    <T> int countValues(Iterable<T> values) {
        int counter = 0;
        for (T value : values) {
            counter++;
        }
        
        return counter;
    }
}
Ponownie krótki komentarz:
1 - musimy rozszerzać klasę Reducer, parametry kolejno to:
- typ klucza z Map
- typ wartości z Map
- typ klucza wynikowego
- typ wartości wynikowej
2 - tutaj widać efekt działania operacji shuffle. Zamiast otrzymywać tą jedynkę z funkcji Map otrzymujemy iterator po kolekcji jedynek (w naszym przykładzie).
3 - zliczamy elementy
4 - zapisujemy w wynikach :-)

No to jeszcze program do uruchomienia zadania:
HttpMethodJob.java:
package pl.lstachowiak.hadoop.sample1;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;


public class HttpMethodJob {
    
    public static void main(String[] args) throws Exception {
        
        if (args.length != 2) {
            System.err.println("Usage: HttpMethodJob <input path> <output path>");
            System.exit(-1);
        }
        
        Job job = new Job();
        job.setJarByClass(HttpMethodJob.class); // 1
        
        FileInputFormat.addInputPath(job, new Path(args[0])); // 2
        FileOutputFormat.setOutputPath(job, new Path(args[1])); // 3
        
        job.setMapperClass(HttpMethodMapper.class); // 4
        job.setReducerClass(HttpMethodReducer.class); // 5
        
        job.setOutputKeyClass(Text.class); // 6
        job.setOutputValueClass(IntWritable.class); // 7
        
        System.exit(job.waitForCompletion(true) ? 0 : 1); // gogogo!
        
    }
}

1 - tworzymy zadanie i podajemy klasę główna
2,3 - podajemy źródło danych oraz miejsce zapisu danych wynikowych (wczytywane z argumentów)
4,5 - podajemy klasę mappera i reducera
6,7 - konfiguracja typów danych wynikowych
I uruchomienie zadania :-)

Zanim jednak zaczniemy potrzebne są jeszcze jakieś dane do analizy. Ja wygooglałem coś tutaj (4,3MB).

Uruchomienie

Tutaj obrazkowo:) Przechodzimy do zakładki Services (NetBeans) -> Servers -> Hadoop Jobs -> New Job. Wybieramy nazwę dla Joba i typ 'Hadoop Job from pre-existing JAR file'.
Następnie wpisujemy ścieżkę do JARa (ja podałem z targeta) oraz klasę z funkcją main.
W następnym oknie wybieramy klaster dla wersji 0.20 oraz wpisujemy argumenty dla zadania. Kolejno plik access.log oraz katalog w którym mają być składowane wyniki końcowe.
I klikamy 'finish' :-)

Teraz możemy odpalać!
Prawy guzik na nowego Joba -> Run Job -> Run.

Wszystko powinno pójść dobrze, stan konsoli na końcu (statystyki):
11/06/19 11:53:26 INFO mapred.JobClient: Job complete: job_local_0001
11/06/19 11:53:26 INFO mapred.JobClient: Counters: 12
11/06/19 11:53:26 INFO mapred.JobClient:   FileSystemCounters
11/06/19 11:53:26 INFO mapred.JobClient:     FILE_BYTES_READ=18153346
11/06/19 11:53:26 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=9462349
11/06/19 11:53:26 INFO mapred.JobClient:   Map-Reduce Framework
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce input groups=4
11/06/19 11:53:26 INFO mapred.JobClient:     Combine output records=0
11/06/19 11:53:26 INFO mapred.JobClient:     Map input records=17484
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce shuffle bytes=0
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce output records=4
11/06/19 11:53:26 INFO mapred.JobClient:     Spilled Records=34968
11/06/19 11:53:26 INFO mapred.JobClient:     Map output bytes=142014
11/06/19 11:53:26 INFO mapred.JobClient:     Combine input records=0
11/06/19 11:53:26 INFO mapred.JobClient:     Map output records=17484
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce input records=17484
W katalogu wynikowym powinien znajdować się także plik part-r-00000 z zawartością:
GET 15341
HEAD 1744
POST 398
PUT 1

I mamy gotowe statystyki :-)
Pełne źródła dostępne są tutaj.

sobota, 18 czerwca 2011

Wideo z Poznańskiego Code Retreat

Trochę czasu minęło od pierwszego Poznańskiego Code Retreat, ale parę dni temu pojawił się w sieci krótki filmik z tej imprezy. Wg mnie jest świetny :) Dynamiczny i nawet gdzieś tam mnie widać! Brawa dla autora, którym jest Zbigniew Wantuch!

Zapraszam do obejrzenia:


Źródło: Poznań JUG