Suplement do Suppliera

September 18th, 2014

Na ostatnim spotkaniu wrocławskiego JUGa padło pytanie co do Suppliera z poprzedniego wpisu dotyczące tego jak należy zamykać BufferedReader. Trochę się niestety zamotałem i nie byłem wstanie udzielić dobrej odpowiedzi na to pytanie, ale po spokojnym przemyśleniu sprawy rzecz jest bardziej niż prosta.

Błogosławione niech będą rzeczy nowe w Javie 7

Dokładnie zaś konstrukcja try-with-resource, czyli możliwość trochę lepszego zapisu syfu związanego z zamykaniem zasobów. Efektywnie oznacza to, że wystarczy by nasz Supplier implementował interfejs Closeable:

Listing 1. Samo zamykający się Supplier

public class FileReaderViaSupplier implements Supplier<Optional<String>>, Closeable {

	private BufferedReader reader;

	public FileReaderViaSupplier(String filePath) {
		this(new File(checkNotNull(filePath)), UTF_8);
	}

	public FileReaderViaSupplier(File file, Charset charset) {
		checkNotNull(file);
		checkNotNull(charset);
		checkArgument(file.exists(), "File %s does not exist", file.getAbsolutePath());
		try {
			reader = Files.asCharSource(file, charset).openBufferedStream();
		} catch (IOException e) {
			checkState(false, e.getMessage());
		}
	}

	@Override
	public Optional<String> get() {
		try {
			String reference = reader.readLine();
			return Optional.fromNullable(reference);
		} catch (IOException e) {
			return Optional.absent();
		}
	}

	@Override
	public void close() throws IOException {
		reader.close();
	}
}

I to tyle.

Tradycyjnie już na koniec mała prośba jeżeli spodobał ci się ten wpis udostępnij go w mediach społecznościowych korzystając z przycisków poniżej. Jak masz pytania zadaj je w komentarzu. Postaram się zaspokoić twoją ciekawość.

Supplier z Guavy jako narzędzie odczytu plików

September 13th, 2014

Interfejs Supplier opisałem już pewien czas temu. Dziś chciałbym przedstawić pewne specyficzne zastosowanie tego interfejsu. Mianowicie stworzymy klasę, która będzie w oparciu o ten interfejs odczytywać pliki.

Prawie leniwe czytanie plików

Generalnie w Javie da się czytać pliki w sposób leniwy. Bierzemy sobie BufferedReader i wpinamy go w jakiś listener. Następnie uderzamy zdarzeniem “następna linia” i pobieramy linię. I tak aż się plik skończy.
W przypadku Supplier-a mechanika działania jest bardzo zbliżona. Z jedną małą różnicą. Musimy czytać “do przodu” jeżeli chcemy by odczyt był następnie wykorzystywany w ramach iteratorów. Te ostatnie zapewnią nam spójny interfejs z Guavą.
Co więcej o ile w przypadku “zwykłego” czytania plików możemy sobie zaryzykować zwrócenie wartości null lub wymuszenie obsługi weryfikowalnego wyjątku to gdy idziemy w kierunku idiomów funkcyjnych dobrze by było zamknąć jakoś te problemy.

Catched Exceptions i Guava

Dziś na skróty. Generalnie jedną z cech programowania funkcyjnego jest brak efektów ubocznych. Jednym z takich efektów jest rzucenie wyjątku. Wszystko ładnie pięknie, ale uruchommy sobie interpreter Erlanga (który językiem funkcyjnym jest) i napiszmy coś takiego:

Listing 1. “Brak efektów ubocznych”

Liczba = 1.
Wynik = Liczba / 0.

Oczywiście dostaniemy wyjątek. Jako, że Erlang hołduje idei “Let it crash” to jeżeli doprowadzilibyśmy do takiej sytuacji w programie to maszyna wirtualna by ubiła nam proces, a następnie go wznowiła.
My nie będziemy aż tak mili. Jeżeli coś się wywali to przepchniemy to na chwilę obecną do wyjątku nieweryfikowalnego i wyrzucimy… Let it crash :D

Przy czym należy pamiętać, że Guava udostępnia klasę narzędziową Throwables, która pozwala na opakowywanie i konwertowanie wyjątków. Tu będzie ona średnio przydatna ;)

Czytamy plik linia po linii z użyciem Supplier

Zakładam, że czytamy linia po linii, plik np. csv, a nie plik binarny, gdzie chcemy odczytać blok. Ogólne zasady będą takie same komplikuje się tylko zasady określające ile danych chcemy odczytać.

Zastanówmy się przez chwilę co powinno być efektem przeczytania linii z pliku? Zapewne obiekt typu String. Co jeżeli linia jest pusta? Nasz String powinien być pusty. W przypadku końca pliku będzie oczywiście null. Czy to nam odpowiada? Raczej nie. Szczególnie ten ostatni przypadek. Co prawda można by zamienić do na zwracanie pustego stringa, ale wtedy nie wiemy, że odczytujemy ostatnią linię, a nie np. pustą linię w środku pliku.

Pamiętajmy, że odczyt pliku będzie obudowany w dostawcę, a ten nie dostarcza metod iteratora typu hasNext. Oczywiście można napisać taki iterator (kolejny post będzie o tym). Jednak my skupimy się na samym odczycie.

Jak wybrnąć z tego problemu? Użyć Optional. To było proste. Zasady też są proste. Jeżeli dojdziemy do końca pliku to zwracamy Absent inaczej Present. Do odczytu plików tekstowych linia po linii posłuży nam stary dobry BufferedReader. WIemy zatem jak będzie wyglądał “core” naszej aplikacji

Listing 2. Metoda get naszego readera

public class FileReaderViaSupplier implements Supplier<Optional<String>> {

	private BufferedReader reader;

	@Override
	public Optional<String> get() {
		try {
			String reference = reader.readLine(); // 1
			return Optional.fromNullable(reference).or(Optional.<String>absent()); // 2
		} catch (IOException e) {
			return Optional.absent(); // 3
		}
	}
}

Na początku odczytujemy pojedynczą linię (1), a następnie na jej bazie tworzymy obiekt Optional, który będzie istniał o ile tylko odczytaliśmy coś co nie jest null-em. W przeciwnym wypadku zostanie zwrócony Absent. Absent zostanie zwrócone też w przypadku błędu.

Jak to zainicjować

Mamy co prawda reader, ale nadal nie mamy kodu odpowiedzialnego za jego inicjalizację. Przedstawię tylko jeden konstruktor. Można go potraktować jako ostatni w ścieżce delegacji pomiędzy różnymi wersjami (bo jak zwykle operując na plikach mamy od cholery konstruktorów).

Listing 3. Konstruktor naszego readera

public FileReaderViaSupplier(File file, Charset charset) {
	checkNotNull(file);
	checkNotNull(charset);
	checkArgument(file.exists(), "File %s does not exist", file.getAbsolutePath());
	try {
		reader = Files.asCharSource(file, charset).openBufferedStream();
	} catch (IOException e) {                                                                    
		checkState(false, e.getMessage());
	}
}

W pierwszych trzech liniach sprawdzamy czy w ogóle możemy sobie pozwolić na utworzenie readera. Wykorzystuję tu Preconditions, które opisywałem pewien czas temu. Następnie próbujemy utworzyć BufferedReader. Jeżeli to się nie uda to walimy kolejnym wyjątkiem.

W przeciwieństwie do odczytu gdzie błąd oznaczał tylko zakończenie działania tu przyjąłem trochę inną strategię. Proces odczytu jest leniwy. Zatem możemy zabezpieczyć się przed warunkami zewnętrznymi inaczej niż w przypadku zachłannego procesu tworzenia obiektu. Tu chcemy od razu powiedzieć “nie wiem, nie znam się, zarobiony jestem, przyjdź pan jurto”. Natomiast w przypadku odczytu po prostu mówimy “nic więcej nie mogę odczytać”. Nie ważne jest czy wynika to z problemów z plikiem czy też jest po prostu koniec pliku.

Podsumowanie

Mamy zatem podstawowy element pozwalający na leniwe odczytywanie plików. W dodatku napisaliśmy to w sposób pozwalający na wykorzystanie razem z innymi elementami Guavy.

O tym jak dobrać rozwiązanie do problemu i czy opłaca się pisać rozwiązania uogólnione będę mówił w trakcie warsztatów “Beginning Functional Programming in Java world” na Warsjawie już 26-27 września.

Na koniec mała prośba jeżeli spodobał ci się ten wpis udostępnij go w mediach społecznościowych korzystając z przycisków poniżej. Jak masz pytania zadaj je w komentarzu. Postaram się zaspokoić twoją ciekawość.

Funkcja niezmieniająca i konsumenci w Guavie

September 8th, 2014

W poprzednim wpisie poświęconym Guavie wspomniałem, że przyjrzymy się sprawie wywołania loggera w naszym if-potworku.

Listing 1. If-potworek

for (ReportRunHistory reportRunHistory : reportRunsHistory) {                     
	LOG.info(reportRunHistory.toString());                                        
	if (reportRunHistory.status.equals(CANCELED.getStatusText())                                  
			|| reportRunHistory.status.equals(FAILED.getStatusText())                             
			|| reportRunHistory.status.equals(COMPLETED.getStatusText())){
        // ...
        }                                       
}

Sprawę ifów mamy już rozwiązaną. Czas przyjrzeć się temu co można zrobić z pierwszą linijką. Tak naprawdę to co tu potrzebujemy to monadaW. Taka zwykła, smutna, haskellowa monada. Na monadę składają się dwie operacje (ok to uproszczenie, ale tu wystarczy) wiązanie i powrót… ok nie będzie o monadach, bo takie rozwiązanie choć możliwe do zaimplementowania zapewne zniszczy nam mózgi. My naprawdę żyjemy w świecie obiektowym więc i rozwiązanie będzie obiektowe. Co prawda mamy sobie coś co się nazywa Option i jest to taka dość prymitywna w użyciu monada, ale my chcemy mieć rozwiązanie problemu, a nie kolejny problem zastępczy (w razie jak by pierwszego nie było).
Zapomnijmy więc o monadach.

Konsument konsumuje

Tyle tylko, że w Guavie nie ma interfejsu Consumer<T>. samodzielne dopisanie go nie stanowi problemu

Listing 2. interfejs Consumer<T>

public interface Consumer<T> {

	public void consume(T t);
}

Trochę większym problemem jest jego zastosowanie. Jeżeli na ten przykład zamiast takiej implementacji zastosowalibyśmy coś co rozszerza istniejącą funkcjonalność w rodzaju

Listing 3. interfejs Consumer<T> jako rozszerzenie funkcji

public interface Consumer<T> extends Function<T, Void>{
}

I następnie użyli znanej nam metody compose to byśmy mieli poważny problem. Metoda ta jak pamiętamy przyjmuje dwie funkcje i zwraca trzecią spełniającą zależność

Zależność compose
// offtopic
obrazek wygenerowany za pomocą apki EqGraph. Czyli kolejnego wielkiego niedokończonego projektu ;)
// koniec offtopicu

W naszym przypadku oznaczało by to funkcję zwracającą Void, co było by równoznaczne z operacją kończącą. Nie za dobrze. Dlatego też można to zrealizować w trochę koślawy sposób:

Listing 4. Funkcja przyjmująca interfejs Consumer<T>

public class InConsumerFunction<I> implements Function<I, I> {

	private final Consumer<I> consumer;

	public InConsumerFunction(Consumer<I> consumer) {
		this.consumer = consumer;
	}

	@Override
	public I apply(I input) {
		consumer.consume(input);
		return input;
	}
}

Nie jest to najlepsze możliwe rozwiązanie, ale jak na nasze potrzeby na obecnym etapie wystarczające. Co tu się dzieje jest chyba jasne. Tworząc instancję naszej funkcji przekazujemy jej jakiegoś konsumenta, który robi swoje, a następnie funkcja zwraca oryginalny obiekt.

Dlaczego jest to koślawe?

Po pierwsze dlatego, że konsument może zmienić stan naszego obiektu. Programowanie funkcyjne opiera się o niezmienność poszczególnych bytów. Tu nie mamy możliwości zagwarantowania braku zmiany w konsumencie… bywa…
Po drugie sama funkcja posiada stan. To znacząco ogranicza możliwość jej powtórnego użycia. Z drugiej strony dobrze przemyślany system logowania powinien pozwolić na wywołania silnie typowane. Co to oznacza? Ano oznacza tyle, że treść komunikatów jest sobie zamknięta w jakiejś klasie i nie mamy dostępu do nich z zewnątrz. Jedyne co możemy zrobić to przekazać im listę argumentów.
Da się to nawet napisać i zapewne kolejny post będzie poświęcony tej tematyce, ale to nadal jest konieczność pałowania się z czymś na co nie mamy ochoty.
Po trzecie musieliśmy wprowadzić dodatkowy byt, którego użycie wprost z poziomu Guavy jest niemożliwe. Wymaga on każdorazowego opakowania. Nie za dobrze.

Podsumowanie

Guava daje nam bardzo dużo możliwości. Jednocześnie brakuje w niej “duperelków”, których dopisanie jest uciążliwe i wymaga stosowania haków o ile chcemy napisać coś w możliwie ogólny sposób. Jednocześnie pisanie rozwiązań dla konkretnych problemów może okazać się znacznie prostsze i efektywniejsze.

O tym jak dobrać rozwiązanie do problemu i czy opłaca się pisać rozwiązania uogólnione będę mówił w trakcie warsztatów “Beginning Functional Programming in Java world” na Warsjawie już 26-27 września.

Na koniec mała prośba jeżeli spodobał ci się ten wpis udostępnij go w mediach społecznościowych korzystając z przycisków poniżej. Jak masz pytania zadaj je w komentarzu. Postaram się zaspokoić twoją ciekawość.

Blog Day 2014

August 31st, 2014

W tym roku będzie trochę “prywaty”

Paragraf na kozetce – blog poświęcony prawu i psychologii w procesie postępowania sądowego (oraz około sądowego).
Simply Home About – dla tych co remontują i urządzają… bo to kolejny raz w tym roku przerabiałem
Subiektywnie o finansach – chyba najlepszy polski blog dziennikarski o finansach.
Czas Dżentelmenów – trochę męskiej, klasycznej mody, trochę historii, troch ciekawostek. Generalnie warto poczytać ponieważ niektóre informacje są bezcenne.
Ciekawostki historyczne – świetne czytadło historyczne dla tych, co chcą posiąść wiedzę nietypową.

Predykaty z Guavy i enumy, czyli praktyki dwa łyki

August 31st, 2014

Mamy sobie taki sprytny inaczej system uruchamiania raportów w innym systemie, gdzie z jednej strony mamy status raportu jako enum, a z drugiej jako String. Klasyczny przypadek prowadzący do “potworkowania” kodu takimi oto ifami:

Listing 1. If-potworek

for (ReportRunHistory reportRunHistory : reportRunsHistory) {                     
	LOG.info(reportRunHistory.toString());                                        
	if (reportRunHistory.status.equals(CANCELED.getStatusText())                                  
			|| reportRunHistory.status.equals(FAILED.getStatusText())                             
			|| reportRunHistory.status.equals(COMPLETED.getStatusText())){
        // ...
        }                                       
}

Tomek Nurkiewicz na Confiturze w 2012 roku opowiadał o tym jak pozbywać się Ifów z kodu. Ten kod podpada pod “jest prosto nie trzeba upraszczać”. Mnie jednak osobiści drażni takie coś. Można to naprawdę sprowadzić do czegoś co będzie wyglądać dobrze i co najważniejsze będzie łatwe do przeczytania. Jak używamy Guavy rzecz staje się jeszcze prostsza.

Do czasu…

Pierwszy problem każdego korpo

Trochę się po korpo nie informatycznych włóczyłem i zaobserwowałem, że istnieje w nich pewna wspólna cecha dotycząca kodu. Można by ją opisać, jako mieszankę niechęci do zmian z silną separacją zespołów. Wyobraźmy sobie, że nasz system raportowy ma klienta, który jest napisany w Javie i dostarczony nam przez zespół zajmujący się apką raportową. Rzecz w tym, że jak znajdziemy jakiś błąd to procedura wdrożenia poprawki jest drogą przez mękę. W praktyce napisanie własnego lokalnego haka na bibliotekę jest prostsze…
Inaczej mówiąc w korpo jak dostajesz coś zapaczkowane w jara, to choć by zespół odpowiedzialny za ten kod siedział po sąsiedzku to uzyskanie poprawki jest długotrwałe.
A co w przypadku gdy chcesz uzyskać nową funkcjonalność? Zapomnij.

Miej więcej taki problem mamy z naszym systemem raportowym. Choć klient mógłby zwracać enum to zwraca String, a my musimy robić brzydkie haki w naszych enumach w rodzaju:

Listing 2. “Hakowany” enum

public enum RunReportStatusEnum {

	QUEUED("queued"), RUNNING("running"), COMPLETED("completed"), FAILED("failed"), CANCELED("canceled");

	private final String statusText;

	private RunReportStatusEnum(String statusText) {
		this.statusText = statusText;
	}

	public String getStatusText() {
		return statusText;
	}

}

String w konstruktorze enuma, który mapuje nasz kod na zasadzie enum-String. Można to rozwiązać mapą, ale… nadal zostaje ten pieprzony If z Listingu 1 gdzie jakoś musimy w pewnym momencie przefiltrować sobie to co przychodzi na nasze enumy.

No ale mamy Guavę

A Guava ma predykaty. Pisałem już o predykatach i funkcjach, a teraz chcę pokazać jedno z ciekawszych zastosowań, moim zdaniem oczywiście.

Krok 1. Enum – predykat

Pierwszym krokiem refaktoryzacji jest zauważenie, że w IFie kod składa się z trzech identycznych pod względem konstrukcji warunków. Można je zgeneralizować w następujący sposób:

Listing 3. Metoda uogólniająca dla Ifa

boolean is(ReportRunHistory reportRunHistory, RunReportStatusEnum e){
	return reportRunHistory.status.equals(e.getStatusText());
}

Co w samo w sobie jest może i ładne, ale użycie tego w kodzie nadal paskudne. Zatem w zamiast czegoś takiego można napisać własny enum implementujący Predicate:

Listing 4. Enum – predykat

public enum RunReportStatusEnumPredicate implements Predicate<String> {
	IS_QUEUED(QUEUED), 
	IS_RUNNING(RUNNING), 
	IS_COMPLETED(COMPLETED), 
	IS_FAILED(FAILED), 
	IS_CANCELED(CANCELED);

	private final RunReportStatusEnum statusEnum;

	private RunReportStatusEnumPredicate(RunReportStatusEnum statusEnum) {
		this.statusEnum = statusEnum;
	}

	@Override
	public boolean apply(String input) {
		return statusEnum.getStatusText().equals(input);
	}
}

W praktyce nie wychodzi to poza to co na listingu 3. Główna różnica polega na tym, że całość można sprawnie przetestować (banalne) oraz mamy zapewniony interfejs zdatny do użycia z Guavą.

Krok 2. Składamy predykat

Kto czytał tekst o predykatach ten wie, że można składać predykaty wykorzystując metodę Predicates.or. Złóżmy zatem nasz predykat:

Listing 5. Enum – predykat

or(IS_CANCELED, IS_FAILED, IS_COMPLETED)

LOL… jakie to proste… nasz wyjebany w kosmos IF zamienił się w jednolinijkowca… Nope…

Krok 3. Transformata

Jak przyjrzycie się interfejsowi naszego predykatu to przyjmuje on typ String, a w pętli mamy do czynienia z ReportRunHistory. Jak zatem wtłoczyć do naszego kodu inny typ? Tu z pomocą przychodzi kolejna metoda Predicates.compose, która przyjmuje predykat i funkcję, która zwraca typ zgodny z typem wejściowym predykatu.

Listing 6. Funkcja konwertująca

public class ReportRunHistoryStatusAsStringFunction implements Function<ReportRunHistory, String> {
	public static final ReportRunHistoryStatusAsStringFunction REPORT_RUN_HISTORY_STATUS_AS_STRING_FUNCTION
			= new ReportRunHistoryStatusAsStringFunction();
	
	private ReportRunHistoryStatusAsStringFunction(){}

	@Override
	public String apply(ReportRunHistory input) {
		return input.status;
	}
}

W efekcie nasz kod można przepisać do:

Listing 7. Funkcja i enum

compose(
		or(IS_CANCELED, IS_FAILED, IS_COMPLETED),
		REPORT_RUN_HISTORY_STATUS_AS_STRING_FUNCTION
)

Co zwraca predykat przyjmujący ReportRunHistory i odpalający warunek z predykatu gdzie potrzeba String.

Pułapka

Kod zaczął się trochę “rozwlekać”. Można mu zarzucić, że stworzyłem wiele małych klas, których powtórne użycie jest wątpliwe, a trzeba je jakoś utrzymywać. Powiem tak, jeżeli boisz się tworzyć nowe klasy “na masę” to idź pisać w pascalu gdzieś indziej. Kluczem do sukcesu w tym przypadku jest duża granulacja kodu. W praktyce ( i w rozwiązaniu z korpo) z pojedynczej konstrukcji for-if-for-if-logika można wygenerować nawet 10-12 klas, a każda z nich będzie odpowiadać, za jakąś pojedynczą funkcjonalność. Choćby była to obsługa ifa.
Ten przykład jest niekompletny, ponieważ if znajduje się w pętli to nie do końca widać siłę tej refaktoryzacji. Ale o tym następnym razem, bo nie o tym tu chciałem teraz napisać.

Duża granulacja kodu to też pewna pułapka. Z jednej strony bardzo łatwo jest taki kod przetestować, ale z drugiej strony kto będzie pisał testy dla getterów (nasza funkcja to opakowanie gettera). tym samym może okazać się, że po większej refaktoryacji, gdzie dodaliśmy dużo nowych małych klas bez testów, nagle nasze pokrycie testami spada i wywala buildy. Bywa… choć testowanie małych klas wydaje się zawracaniem dupy to należy się zmusić i to robić.
Można też ułatwić sobie życie testując trochę większe kawałki kodu. Tu dobrym kandydatem na napisanie testu jest nie funkcja i enum, ale predykat powstały z ich kompozycji. Choć nadal duża część testu to sprawdzenie “czy guava działa” to jednak jest to już test, który już napisaliśmy. Kiedy? Gdy przystępowaliśmy do refaktoryzacji i mieliśmy pokrycie testami dla całego Ifa. Pamiętajcie, że nadal testujemy jakiś większy kawałek kodu – serwis czy coś w ten deseń, gdzie w środku w wyniku refaktoryzacji udało się wyciągnąć ileś tam małych klas.

Podsumowanie

Przedstawiona tu technika radzenia sobie z ifem jest dobrym punktem zaczepienia jeżeli chcecie wprowadzać elementy programowania funkcyjnego do swojego kodu. Nie jest to oczywiście programowanie funkcyjne jako takie, ale ma jego naturę (funkcja i predykat są bezstanowe, nie zmieniają stanu obiektu itp.). To już dużo. Znacznie więcej niż można sobie wyobrazić gdy widzimy przeciętny kod korpo-potworka i chcemy się w nim rozeznać.

Jest to oczywiście fragment większej refaktoryzacji, a kolejnym ciekawym jej elementem jest to jak wywołać logger z pierwszej linii pętli.

O tym między innymi będę mówił w trakcie warsztatów “Beginning Functional Programming in Java world” na Warsjawie już 26-27 września.

Na koniec mała prośba jeżeli spodobał ci się ten wpis udostępnij go w mediach społecznościowych korzystając z przycisków poniżej. Jak masz pytania zadaj je w komentarzu. Postaram się zaspokoić twoją ciekawość.

Clean services, albo mikro architektura…

August 24th, 2014

Jak cholerę zwał tak zwał. Generalnie ostatnie spotkania Wrocławskiego JUGa dały mi trochę do myślenia.

Pierwsze z Wujkiem Bobem było poświęcone Clean Architecture. Wcześniej Andrzej Bednarz opowiadał nam o swoich doświadczeniach z tym rozwiązaniem, ale tu mieliśmy okazję “u żródła”.
Drugie poświęcone architekturze mikroserwisów poprowadzone przez Roberta Firka było swoinstym dopełnieniem.

O co chodzi?

Clean Architecture jest koncepcją takiego tworzenia funkcjonalności by były one jak najbardziej odseparowane od świata zewnętrznego. Interaktor (ciężko mi znaleźć polskojęzyczny odpowiednik) ma swój interfejs komunikacyjny i korzysta z pewnych obiektów – encji domenowych (wspólnych dla wielu aplikacji nie tylko dla tej konkretnej). Gdy potrzebuje dostać się do danych korzysta z interfejsu dostępowego, ale nie DAO lecz czegoś w rodzaju gateway-a. Co jest za nim… who cares. Po prostu dostajemy dane w pewnym określonym formacie i czy będzie pod spodem baza danych, czy jakiś sawant… W efekcie dostajemy aplikację, która ma charakter kontenera pluginów. Jest sobie jakiś core biznesowy reprezentujący use casy, a reszta to tylko łatwo wymienialne pluginy.

Mikroserwisy są znowuż podejściem gdzie stawiamy duży nacisk na separację poszczególnych funkcjonalności i ich niezależność. W tym podejściu liczy się przede wszystkim duża granulacja kodu, tak by zmiana nie trwała tygodniami, a raczej godzinami. Poszczególne fragmenty komunikują się ze sobą i każde wywołanie powinno nieść ze sobą wszelką wiedzę i informacje potrzebną do wykonania danej operacji. Mikroserwisy mogą powstawać w różnych technologiach i nie mogą współdzielić zasobów.

Widzicie na czym polega myk?

Mikro architektura, czyli clean services

Projektowanie zaczynamy zgodnie z Clean Architecture. Naszą bazą mentalną jest konkretny use case. Reprezentuje on jeden niezależny scenariusz w aplikacji. W tym momencie projektujemy kod, spisując kolejne kroki i korzystając z bazy pojęć CA. Mamy więc interaktora, boundaries, entyties, gateways.
Następnie zaczynamy kod pisać i na poziomie pojedynczego use casa nadal trzymamy się CA.
Gdy jednak dokładamy realizację elementów “plugowalnych” chociażby jakiś testowy gateway to staje się on mikroserwisem. Staramy się tak zaprojektować interfejs by wywołanie jakiegoś pluginu realizowane było w oparciu o MiS.

Co to nam daje?

Dwa słowa podsumowania. CA i MiS są to dwa podejścia, które kładą nacisk na dwa inne elementy architektury. W CA staramy się modularyzować kod tak by reprezentował on poszczególne zagadnienia biznesowe. Każdy niezależny element w kodzie ma odbicie w use caseie. MiS kładzie znowuż nacisk na to jak poszczególne moduły ze sobą współpracują oraz na to by awaria jednego z nich nie wywaliła nam aplikacji.
Moim zdaniem połączenie tych dwóch podejść pozwala na stworzenie z jednej strony aplikacji, która będzie dość odporna na awarie. Jednocześnie każda pojedyncza jednostka organizacyjna będzie miała jasno określony zakres odpowiedzialności biznesowej.
Ponad to, jako że w każdej aplikacji istnieją pewne byty konieczne z punktu widzenia prawidłowego działania, ale nie niezbędne biznesowo (administracja uprawnieniami, składowanie danych, audyt), będzie można tak zorganizować kod by ich istnienie miało jak najmniejszy wpływ na logikę biznesową.

Typy prymitywne z logiką biznesową wstęp do dobrego kodu

July 13th, 2014

Większość aplikacji obiektowych, które są tworzone jak świat długi i szeroki nie ma za dużo wspólnego z OOP. Przynajmniej na poziomie modelu. Ten został w czasach proceduralno-strukturalnych. Mamy zatem klasy, które w rzeczywistości są strukturami znanymi z C tyle tylko, że wyposażonymi w gettry i settery.
Te klasy są trochę jak aktorki w filmach porno. Patrzysz na nie i niby mają na sobie fatałaszki (metody), ale jak zaczniesz z taką zabawę to dość szybko okaże się, że nie ma ona nic do ukrycia. O ile w porno jest to OK, to w programowaniu obiektowym nie do końca. Klasa, która ujawnia zbyt dużo może szybko stać się źródłem problemów. W dodatku sama w sobie ma tendencję do obrastania dodatkowymi klasami narzędziowymi, bez których nie za bardzo może żyć.

Friendzone

Jest to pewien antywzorzec będący bezpośrednim następstwem anemicznego modelu. Można powiedzieć, że klasa “awansowała” do friendzone jeżeli w stosunku do innej klasy pełni rolę służebną nie otrzymując nic w zamian. Klasa taka musi spełniać pewne wymagania:

  • Musi być częścią publicznego API systemu.
  • Jej usunięcie nie ma wpływu na logikę działania systemu.
  • Jej usunięcie ma wpływ na zachowanie systemu.

Założenia te spełnia większość klas typu “utilsy do czegoś tam”. Szczególnie zaś najprzeróżniejsze parsery informacji z GUIa, serwisów zewnętrznych.

Przypadek specjalnej troski

Specyficznym przypadkiem jest użycie typów podstawowych (prymitywnych i String) jako pełnoprawnych członków modelu. To zły pomysł. Jak dopuścisz prymitywów do dyskusji to zaczną odpierdalać nietoperza, osrają wszystko dookoła, a na koniec dadzą w mordę przypadkowym gościom. Jeszcze gorzej jest gdy typ prymitywny niesie w sobie pewną logikę biznesową. W takim wypadku trzeba z nim jakoś żyć. Niestety wielu programistów idzie po linii najmniejszego oporu i zamiast opakować typ prymitywny “friendzonuje” grupę klas by tylko się nie narobić. A to błąd…

Karmimy model

By jakoś wyrwać się z okowów anemicznego modelu należy przeprowadzić refaktoryzację. Zazwyczaj pierwszy krok takiej refaktoryzacji polega na wydzieleniu klasy opakowującej. Niestety wiele osób na tym kończy. W najlepszym wypadku dostajemy takiego potworka jak poniżej

Listing 1. Ta klasa też jest anemiczna

public class Pesel {

	private final String pesel;

	public Pesel(String pesel){
		this.pesel = pesel;
	}

	@Override
	public String toString() {
		return pesel;
	}
	public void accept(Validator validator){
		validator.validate(this.pesel);	
	}

}

Niby wszystko jest OK. Nawet jakaś walidacja jest… taka to wali wyjątkami… Ale jednak trochę do dupy… trochę bardzo.

Klasa nie niesie ze sobą żadnej wartości. Jak ją przerobić na coś co ma wartość? Wszystko zależy od tego jaką informację biznesową niesie ze sobą określony typ prymitywny. W przypadku numeru PESEL ilość informacji jest stosunkowo duża jak na tak krótki numer. Zatem można wygenerować dość bogatą klasę opakowującą. W dodatku ze względu na specyfikę logiki numeru PESEL można tu naprawdę dużo upchnąć.

Pierwszym krokiem przy dokarmianiu modelu jest decyzja jak “bogaty” ma być. Ja postaram się to zrobić na full wypas. Zatem klasa będzie niosła ze sobą całą logikę zawartą w numerze PESEL oraz dodatkowo będzie kompensować jego niedociągnięcia.

Listing 2. Pesel na bogato pierwsze kroki

public class RitchPesel {

	private final String dateOfBirth;
	private final String serialNumber;
	private final int sexDigit;
	private final int controlDigit;

	private final boolean isCorrect;
	private final boolean isValid;

	private RitchPesel(String dateOfBirth, String serialNumber,
	                   int sexDigit, int controlDigit,
	                   boolean isCorrect, boolean isValid) {
		this.dateOfBirth = dateOfBirth;
		this.serialNumber = serialNumber;
		this.sexDigit = sexDigit;
		this.controlDigit = controlDigit;
		this.isCorrect = isCorrect;
		this.isValid = isValid;
	}

	public Sex sex() {
		return Sex.byDigit(sexDigit);
	}

	public LocalDate birthDate() {
		return PeselBirthDateParser.parse(dateOfBirth);
	}

	public boolean isCorrect() {
		return isCorrect;
	}

	public boolean isValid() {
		return isValid && isCorrect;
	}

	@Override
	public String toString() {
		return dateOfBirth + serialNumber + sexDigit + controlDigit;
	}

}

Zatem mamy coś takiego. Klasa zawiera pewną logikę. Na podstawie numeru PESEL możemy określić datę urodzenia oraz płeć. Na uwagę zasługuje tu rozdzielenie pól isValid i isCorrect. Wynika ono z uwarunkowania historycznego numeru PESEL. Nie każdy prawidłowy numer jest poprawny. Pierwsze pole przechowuje informację o poprawności numeru w sensie wyliczenia sumy kontrolnej. Drugie przechowuje informację o prawidłowości numeru w odniesieniu do rejestru. Oznacza to tyle, że może istnieć numer nie spełniający zasad wyliczania sumy kontrolnej, z nieprawidłową datą bądź nieprawidłowym oznaczeniem płci i będzie on prawidłowy.
O takich sytuacjach zapominają ludzie implementujący obsługę numeru i później jest jazda gdy przychodzi klient, podaje dowód osobisty, a system się burzy.
Kolejna ważna rzecz w tym konkretnym przypadku to prywatny konstruktor. Po co on nam? W końcu można by było stworzyć konstruktor, który przyjmował by numer PESEL jako String i robił odpowiednią magię. Rzecz w tym, że tu znowu daje znać o sobie sprawa poprawności i prawidłowości numeru. Można co prawda dodać kolejny parametr w konstruktorze, który będzie opisywał jak ścisła ma być walidacja, ale to nadal nie rozwiązuje problemu. Taki kod choć krótki nie niesie ze sobą informacji co tak naprawdę tam się w środku dzieje.
Kolejna sprawa, i kolejny parametr w konstruktorze, to walidacji płci. Po samym numerze nie rozpoznamy czy zawarta w nim płeć jest poprawna. Trzeba dorzucić parametr informujący czy mamy do czynienia z mężczyzną czy z kobietą. Podobnie ma się sprawa z datą urodzenia. Tu mamy dwa przypadki. Pierwszy jak w przy płci weryfikacja w źródle zewnętrznym, a drugi to weryfikacja dat w rodzaju 31 listopada. Zatem kolejny parametr, który jest opcjonalny (bo przy wyłączonej ścisłej walidacji można go olać). I tak oto otrzymujemy całkiem sporą grupę konstruktorów, które będą dość zagmatwane w swojej naturze.
Rozwiązaniem problemu jest oczywiście stworzenie budowniczego dla naszej klasy. Na tym etapie możemy zagrać sobie na typach i stworzyć kod w oparciu o dziedziczenie. UWAGA! Co co chcę tu przekazać to dobry przykład poprawnego użycia dziedziczenia. Jeżeli ktoś mówi wam, że dziedziczenie w stylu Figura > Prostokąt > Kwadrat jest ok, to mu nie wierzcie. To jest dobry przykład na implementację interfejsu, ale nie dziedziczenie!
Na początek dodajmy do naszej klasy dwie metody statyczne zwracające nam odpowiednich budowniczych

Listing 3. Dwóch różnych budowniczych

public static Builder getStricBuilder(String pesel){
	return new StrictBuilder(pesel);
}

public static Builder getTolerantBuilder(String pesel){
	return new TolerantBuilder(pesel);
}

To już dużo wyjaśnia. Metody te pomimo swojej prostoty są bardzo ekspresyjne. Zarówno osoba czytająca kod w jak i to używająca nie będzie miała problemu ze zrozumieniem co tu się dzieje. Sama klasa Builder jest klasą wewnętrzną i abstrakcyjną. Nie jest interfejsem ponieważ chcemy w niej przechowywać numer w postaci ciągu znaków. Poza tym będzie ona zawierać wspólną logikę dla obu typów budowniczych.
Kolejnym krokiem jest implementacja budowniczych w oparciu o przekazywane parametry i ustawiane flagi.
Ostatni krok to posprzątanie kodu wedle własnych potrzeb.

Oczywiście należy pamiętać o testach.

Podsumowanie

Samo obudowanie typu prymitywnego w mającą biznesowe uzasadnienie klasę jest stosunkowo proste. Jednak dopiero wzbogacenie takiej encji o logikę biznesową, która jest z nią bezpośrednio powiązana pozwala na tworzenie dobrego kodu. Kod taki będzie pozbawiony publicznych klas, które “wpadły w friendzone”. Zostaną one zamknięte w ramach klasy, którą wspierają.
Kod taki pozwala też na napisanie lepszych testów jednostkowych. Testy takie będą rzeczywiście sprawdzać zachowania, a nie powtarzać logikę biznesową. Warto tu jednak zwrócić uwagę na ilość kodu zamkniętego w encji. Jeżeli będzie go za dużo to testowanie może okazać się mordęgą. Szczególnie jeżeli cały kod będzie w ramach klas wewnętrznych BEZ możliwości dobrania się do nich.
W takim wypadku należy pójść na pewien kompromis. Na pewno część kodu (zazwyczaj pewne klasy narzędziowe) będzie miała dużą wartość też poza klasą, w której została zdefiniowana. Warto zatem upublicznić ich API, a w skrajnym przypadku przenieść poza encje. Świetnym przykładem tego typu klas są walidatory. Jeżeli tylko napiszemy je w miarę ogólnie, na przykład przyjmując, że na wejściu otrzymują dane w formie typów prymitywnych, to nic nie stoi na przeszkodzie by wydzielić je do osobnego pakietu. W takim wypadku, walidator może pracować z różnymi encjami biznesowymi reprezentującymi ten sam byt (bo chyba nie wierzycie w bajki o spójnych encjach biznesowych w projektach integracyjnych).

I już po Confiturze

July 9th, 2014

Jak od ośmiu lat początek wakacji stanął pod znakiem Confitury. Po raz trzeci spotkaliśmy się na terenie kampusu głównego UW. I było wesoło…

Java performance tricks and traps – Michał Warecki

Generalnie nastawiłem się na coś co można nazwać “performance track” i się w sumie nie zawiodłem. Prezentacja Michała dotyczyła wszystkich poziomów tego co nazywamy wydajnością. Począwszy od wysoko poziomowej zabawy z konfiguracją po niskopoziomowe zagadnienia bliskie krzemu. Bardzo fajne. Było też trochę o tym jak badać wydajność swojego kodu.

Archeologia kodu źródłowego – Paweł Ślusarz

Poszedłem na tą prezentację z dwóch powodów. Trochę interesuję się “starym kodem” i tym jak się w nim powinno grzebać. To raz, a dwa zaciekawił mnie opis ponieważ wynikało z niego, że będzie to nie tylko teoria.
Sama prezentacja była świetna. Paweł opowiedział, choć pobieżnie, o pewnych prostych lecz nieoczywistych metrykach pozwalających na wyszukanie elementów kodu, które mogą sprawić prawdziwe problemy. Zaprezentował to na żywym projekcie, który był rzeczywiście duży.

Twórz i Rządź, czyli jak developer może pobawić się hardwarem – iBeacony, RaspberryPi, druk 3D itd. – Tomasz Szymanski, Jarosław Kijanowski

Fajna “hakierska” prezentacja. Jednak moim zdaniem chłopaki chcieli pokazać dużo różnych rzeczy i wyszło trochę to takie nieforemne (jak z drukarki 3d). Była zatem mowa i o RaspberryPi i o druku 3D i o iBeaconach. Myślą przewodnią spinającą całość był system oceniania prezentacji na Confiturze oraz aplikacja wspomagająca imprezę. Tyle tylko, że jakoś mnie to nie do końca przekonało. Fajnie na luzie i rzeczowo, ale trochę z kwiatka na kwiatek i bez systematycznego planu.

JVM and Application Bottlenecks Troubleshooting with Simple Tools – Daniel Witkowski

Tu było zabawnie. Daniel zademonstrował na przykładzie naprawdę paskudnego kodu jak nasza niefrasobliwość może wpływać na wydajność dostarczanych rozwiązań. Choć osobiście uważam, że refaktoryzowany fragment by mocno “przedobrzony” w swej chujowatości, to jednak dobrze zbierał różne częste potknięcia.
Mam nadzieję, że kod wraz z video będzie szybko dostępny, bo moim zdaniem była to najbardziej wartościowa prezentacja w tej edycji.

Java in low latency world – Andrzej Michałowski

Temat ciekawy i choć można z niego dużo wycisnąć, to nie lubię gdy prezentujący odwołuje się do innych prezentacji z założeniem, że nie będzie o czymś opowiadać, bo już było. Bi nie było. Nie ważne jednak. Prezentacja o trochę innej tematyce niż to co mówił Daniel i wcześniej Michał. Niby to samo, GC, tuning i strojenie maszyny, ale czegoś mi tu zabrakło. Serio, serio.

Nie koduj, pisz prozę – lingwistyczne techniki wychodzące daleko poza Clean Code – Sławomir Sobótka

Może nie tyle sam temat, bo Sławek nie powiedział nic nowego czy odkrywczego, ale sposób przedstawienia problemu zrobił na mnie wrażenie. Trochę śmieszno, trochę straszno, ale bardzo merytorycznie i rzeczowo. Lubię słuchać Sławka, bo to jeden z tych ludzi, którzy mają dar do prowadzenia prezentacji i szkoleń.

Podsumowanie

Później była Spoina i co działo się na Spoinie niech pozostanie na Spoinie. Generalnie w tym roku było na granicy możliwości organizatorów. Przynajmniej mam takie wrażenie. Z jednej strony błyskawiczne zamknięcie rejestracji. Z drugiej około 1400 uczestników, którzy rzeczywiście się zjawili! To spowodowało lekki zator przy rejestracji. W tym roku impreza rozproszyła się też na dwa budynki aulimaks i stary BUW. Na całe szczęście nie było aż tak upalnie jak rok temu.

Dzięki wszystkim za przybycie, fajne rozmowy i dobrą zabawę.

UPC Free – sposób ataku wymyślony w tramwaju

June 27th, 2014

Nowa usługa UPC jest cholernie niebezpieczna. IMO, niebezpieczniejsza niż zwykłe otwarte hotspoty w knajpach. Te ostatnie jak by nie patrzeć mają jakieś sensowne nazwy i zazwyczaj dają się przypisać do konkretnej firmy/lokalu. Hotspoty UPC emitują tylko nic nieznaczące nazwy.

Sposób ataku w przypadku korzystających z tego typu sieci jest banalnie prosty.

  • Przygotowujemy sobie router z nazwą sieci pasującą do modelu nazw UPC
  • Preparujemy lokalnego DNSa tak by strona logowania kierowała do naszego skryptu z adresem wyglądającym na poprawny. Zbieramy PESELe i numery klientów
  • Najtrudniejsza część – udawany SSL. Czyli tworzymy konfigurację, w której nawiązanie połączenia po SSLu z np. pocztą oznacza, że użytkownik łączy się z naszym komputerem po “naszym” SSLu, my rozpakowujemy jego dane i przepakowujemy dalej do zwykłego SSLa z serwerem docelowym łącząc się niejako “W imieniu klienta”.
  • Rozbijamy obóz w jakiś ruchliwym miejscu i czekamy

Może nie uda się wysniffować danych o kontach bankowych, ale już hasło do googla już tak. W efekcie dostaniemy dostęp do danych wrażliwych i może nawet do ksera dowodu… a to już bardzo dużo.

Dlaczego klasy anonimowe to zło – wydajność

June 19th, 2014

Przy okazji jednego z ostatnich code review natrafiłem na kawałek kodu w stylu:

Listing 1. Przykładowy kod z cr

     Collections2.transform(bussinessObjects, new Function<BussinessObject, SomeProperty>(){
        
          public SomeProperty apply(BussinessObject bo){
              return bo.getSomeProperty();
          }
        }
     );

Bardzo częsty obrazek w kodzie. Ogólna logika jest tu taka, że z listy obiektów pewnej klasy chcemy uzyskać listę obiektów z jednego z ich pól i dalej bawić się z tylko z tymi polami. Ma to sens. Kod tego typu jest czytelny i intuicyjny. W dodatku bardzo ładnie mapuje się na specyfikację “…ze zbioru BO wyciągamy wartości pola X dla każdego BO i następnie…”.

Pytanie brzmi czy powyższe rozwiązanie jest ok?

Klasa anonimowa to zło

Szczególnie w Javie, w której przez lata klasa anonimowa była protezą dla brakujących domknięć/miksinów. Jest to też pokłosie szybkiego kodowania prostych rzeczy tak jak przedstawiona powyżej. Wiemy, że można by to zrobić w pętli. Wiemy, że będzie też ok. Dlatego wybieramy prostszy i szybszy zapis z wykorzystaniem transformat z Guavy. W dodatku kod funkcji jest mocno jednorazowy zatem nie wyciągamy go nigdzie na zewnątrz do np. zmiennej.
Oczywiście pozostaje sprawa powtórzeń w kodzie, ale przy ogarniętym procesie budowania w razie czego otrzymamy informację z narzędzi śledzących takie rzeczy. Dochodzą tu jeszcze rzeczy związane z “drabinkowaniem” kodu i jego czytelnością. To jednak insza inszość.

Jako, że jest to proteza to i nie ma w Javie dobrych mechanizmów pozwalających na optymalizację takiego kodu przez JIT. Zauważyć należy, że funkcja taka jest bezstanowa lub jej stan jest silnie powiązany ze stanem obiektu, w którym została utworzona. Zatem można by przenieść ją do pola na poziomie obiektu. Odpowiednio statycznego dla funkcji bezstanowych i niestatycznego dla funkcji związanych z obiektem. Pytanie brzmi o ile szybciej będzie wykonywał się kod po takim przeniesieniu. Rzeczą oczywistą jest to, że usunięcie z kodu inicjalizacji obiektu przyspiesza go. Oczywiste jest też to, że im więcej wywołań metody w której oryginalnie inicjujemy obiekt funkcji tym różnica jest większa.

Sprawdźmy to!

Zadanie

By stworzyć w teście warunki w miarę zbliżone do takich jakie mogą panować na serwerze test będzie miał następującą konstrukcję:

  • Będziemy pracować na stosunkowo dużym zbiorze danych tak by zapchać pamięć.
  • Operacja wykonywana w funkcji powinna być “JITowalna” i łatwa w optymalizacji na poziomie kompilatora.
  • Operacja powinna być “na zdrowy chłopski rozum” dość długa.

Pierwsze dwa punkty wydają się oczywiste. Serwery są zazwyczaj obciążone zarówno jeśli chodzi o pamięć jak i procesor. Duża ilość danych pozwoli nam na zapchanie pamięci. W dodatku sama operacja “biznesowa” powinna być tak skonstruowana by JIT był ją wstanie zoptymalizować. Ostatni punkt pozwoli nam na sprawdzenie czy takie przeniesienie ma sens jeżeli operacja wewnątrz funkcji jest przynajmniej w teorii dość powolna. Innymi słowy czy czas inicjalizacji obiektu będzie mały w stosunku do czasu operacji.

Nasze zadanie zatem brzmi. Policzy N razy n! dla wszystkich liczb n ze zbioru [0, N-1]. Czyli jeżeli N=5 to odpalamy pięć razy pętle w której liczymy silnie dla liczb od 0 do 4. Całość ma sens jeżeli używamy BigInteger.

Listing 2. Kod “biznesowy”

public class Silnia {

	public BigInteger oblicz(BigInteger n) {
		return silnia(BigInteger.ONE, n);
	}


	private BigInteger tailRec(BigInteger acc, BigInteger n) {
		if (n.equals(BigInteger.ZERO))
			return acc;
		return tailRec(acc.multiply(n), n.subtract(BigInteger.ONE));
	}

}

Tu uwaga Java nie wspiera rekurencji ogonkowej. Po prostu. Wynika to z modelu bezpieczeństwa przyjętego przy projektowaniu języka i tego jak będzie on współpracował ze stosem. Do poczytania tu. Zatem powyższy kod to tylko sztuka dla sztuki i podanie wystarczająco dużej liczby na wejściu spowoduje StackOverflowException wynikający ze zbyt dużej liczby wywołań.

Nasze testowe funkcje będą wołać metodę oblicz. Teraz czas na naszych bohaterów:

Listing 3. Klasy inicjujące funkcje na różne sposoby

class FunctionAlwaysNew {

	public Collection<BigInteger> obliczSilnie(List<BigInteger> input) {

		return Collections2.transform(input, new Function<BigInteger, BigInteger>() {
			@Override
			public BigInteger apply(BigInteger n) {
				return App.silnia.oblicz(n);
			}
		});
	}
}

//...
class FunctionAsStatic {

	private static final Function<BigInteger, BigInteger> FUNCTION = new Function<BigInteger, BigInteger>() {
		@Override
		public BigInteger apply(BigInteger n) {
			return App.silnia.oblicz(n);
		}
	};

	public Collection<BigInteger> obliczSilnie(List<BigInteger> input) {
		return Collections2.transform(input, FUNCTION);
	}
}

Ilość danych na wejściu 17,5mln liczb. Parametr -Xmx2g:

Wprowadź dużą liczbę całkowitą.
17500000
Tworzę listę z danymi
Dane gotowe. Naciśnij enter by zacząć test
Rozpoczynam test dla przypadku z użyciem pola statycznego.
Test zakończony. Zajął 21ms

Rozpoczynam test dla przypadku z użyciem new.
Test zakończony. Zajął 1512ms

i to samo dla parametrów -Xmx2g -server:

Wprowadź dużą liczbę całkowitą.
17500000
Tworzę listę z danymi
Dane gotowe. Naciśnij enter by zacząć test
Rozpoczynam test dla przypadku z użyciem pola statycznego.
Test zakończony. Zajął 622ms

Rozpoczynam test dla przypadku z użyciem new.
Test zakończony. Zajął 6878ms

Zatem widać, że inicjalizacja statyczna jest jakieś dwa rzędy wielkości szybsza, a w przypadku użycia trybu serwera różnica jest rzędu wielkości.

No cześć maleńka… chcesz zobaczyć moją lambdę

Ok teraz kod z pomocą lambd i pytanie na ile będzie szybszy. Przy czym nie używam tu Stream API ponieważ wersja wielowątkowa to insza inszość i temat na inny tekst. Chcąc użyć lambd mamy do wyboru trzy opcje. Pierwsza to “inline”, druga to lambda przypisana do pola, a trzecia to method ref. Kod interesujących nas klas poniżej:

Listing 4. Wykorzystanie lambd

class FunctionLambdaInline {

	public Collection<BigInteger> obliczSilnie(List<BigInteger> input) {

		return Collections2.transform(input, n -> App.silnia.oblicz(n));
	}
}

//..

class FunctionLambdaStatic {

	public static final Function<BigInteger, BigInteger> FUNCTION = n -> App.silnia.oblicz(n);

	public Collection<BigInteger> obliczSilnie(List<BigInteger> input) {

		return Collections2.transform(input, FUNCTION);
	}
}

//...

class FunctionLambdaRef {

	public Collection<BigInteger> obliczSilnie(List<BigInteger> input) {

		return Collections2.transform(input, App.silnia::oblicz);
	}
}

I wyniki dla wersji z -Xmx2g:

Wprowadź dużą liczbę całkowitą.
17500000
Tworzę listę z danymi
Dane gotowe. Naciśnij enter by zacząć test
Rozpoczynam test dla przypadku z użyciem pola statycznego.
Test zakończony. Zajął 30ms

Rozpoczynam test dla przypadku z użyciem new.
Test zakończony. Zajął 2162ms

Rozpoczynam test dla przypadku z użyciem lambdy inline.
Test zakończony. Zajął 576ms

Rozpoczynam test dla przypadku z użyciem lambdy method ref.
Test zakończony. Zajął 6358ms

Rozpoczynam test dla przypadku z użyciem lambdy statycznej.
Test zakończony. Zajął 19ms

i z -Xmx2g -server:

Wprowadź dużą liczbę całkowitą.
17500000
Tworzę listę z danymi
Dane gotowe. Naciśnij enter by zacząć test
Rozpoczynam test dla przypadku z użyciem pola statycznego.
Test zakończony. Zajął 29ms

Rozpoczynam test dla przypadku z użyciem new.
Test zakończony. Zajął 3137ms

Rozpoczynam test dla przypadku z użyciem lambdy inline.
Test zakończony. Zajął 37ms

Rozpoczynam test dla przypadku z użyciem lambdy method ref.
Test zakończony. Zajął 6540ms

Rozpoczynam test dla przypadku z użyciem lambdy statycznej.
Test zakończony. Zajął 24ms

Podsumowanie

Oczywiście metodyka testów jest mocno chałupnicza i wyniki są tylko poglądowe. Dotyczą też javy “od suna” czyli maszyny hotspot.
Wyniki pozwalają jednak wnioskować, że inicjalizacja statyczna będzie dużo szybsza niż każdorazowe wołanie new. Ponad to jeżeli mamy okazję użyć javy 8 to należy wystrzegać się method ref i w zamian używać lambd. Przy czym w wersji serwerowej nie ma większego znaczenia jak będzie ona tworzona.