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 obliczSilnie(List input) {

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

//...
class FunctionAsStatic {

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

	public Collection obliczSilnie(List 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 obliczSilnie(List input) {

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

//..

class FunctionLambdaStatic {

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

	public Collection obliczSilnie(List input) {

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

//...

class FunctionLambdaRef {

	public Collection obliczSilnie(List 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.

Interfejsy z implementacją w Javie 8

June 16th, 2014

Zastanawialiście się kiedyś po co w Javie 8 wprowadzono interfejsy z (domyślną) implementacją? Oczywiście można powiedzieć, że dzięki temu mamy miksiny/domknięcia/traity czy jak to tam zwał w zależności od punktu odniesienia.
Ale czy taka konstrukcja nie jest przez przypadek rozwiązaniem znacznie poważniejszego problemu projektowego?

Jak zrobić API?

API, czyli po ludzku interfejs aplikacji/biblioteki/narzędzia itp. Java udostępnia dwa w miarę rozsądne rozwiązania pozwalające na tworzenie takich interfejsów.
Pierwsze to oczywiście interfejsy. Zawierają w sobie tylko definicje, sygnatury, metod. Całkowicie abstrakcyjne, niezwiązane z konkretną implementacją ani z określonym podejściem. Fajne są. Generalnie współcześnie w produkcji kodu kładzie się nacisk na programowanie z wykorzystaniem właśnie interfejsów. Dzięki temu zachowujemy elastyczność. O tym za chwilę.
Drugim rozwiązaniem są klasy abstrakcyjne. Sprawdzają się gdy wiemy iż implementacja, jej główny koncept, nie zmieni się w znaczący sposób, a użytkownik powinien zdefiniować szczegóły. To podejście pozwala na tworzenie rozwiązań, które mają mniej punktów swobody, ale jednocześnie już “odwalają” za nas część pracy. Dobry pomysł, gdy wiemy iż wszystkie możliwe, i zdroworozsądkowe, implementacje interfejsu będą powtarzać pewne schematy. Zresztą bardzo często jest tak iż klasa abstrakcyjna implementuje interfejs tak by użytkownik musiał już tylko dopisać niewielki kawałek. Sparametryzować kod.

Elastyczność API

To co napisałem dotychczas jest oczywiste do tego stopnia iż pytanie o różnicę pomiędzy interfejsem i klasą abstrakcyjną zadają już nawet panny z HRu na rozmowie. Na takie pytanie oczywiście pada odpowiedź o tym, że klasa abstrakcyjna to tak naprawdę zwykła klasa, że można tylko dziedziczyć po jednej z nich, a interfejsy to panie kochany implementujemy ile chcemy.
Ok, dobra odpowiedź. Przy czym nie o to chodzi :D

Główna różnica polega na elastyczności w rozumieniu zarówno zmiany implementacji jak zmiany samego interfejsu. Interfejs jest po opublikowaniu praktycznie niezmienny. Jego implementacje mogą być najróżniejsze. Wyobraźmy sobie taki oto interfejs:

Listing 1. Prosty interfejs

interface Sort<T extends Comparable<T>>{

	Collection<T> sort(Collection<T> c);

}

Ilość implementacji jest naprawdę duża. Zresztą, znajomy z forum 4programmers rzeźbi ciekawy projekt na ten temat. Se weźcie poczytajcie. Ad rem, wyobraźmy sobie, że wydaliśmy naszą bibliotekę z tym interfejsem i ludzie z niej korzystają. Względnie wykorzystaliśmy ją w dużym projekcie i wirus naszego interfejsu rozlał się na całą zdrową tkankę kodu. Teraz chcemy dodać kolejną metodę i dupa…
Interfejsy po publikacji są mało elastyczne. Zmiana w interfejsie pociąga za sobą konieczność zmiany we wszystkich implementacjach.
Trochę inaczej ma się sprawa z klasami abstrakcyjnymi.

Listing 2. Prosta klasa abstrakcyjna

abstract class AbstractSort<T extends Comparable<T>>{

	abstract Collection<T> sort(Collection<T> c);

}

Tu dodanie kolejnej metody jest banalnie proste. Jeżeli nie będzie abstrakcyjna to nie wpływa na poszczególne podklasy. Pojawia się i już. Wadą tego rozwiązania jest oczywiście utrata elastyczności w budowaniu hierarchii klas. Klasy muszą dziedziczyć z naszej klasy abstrakcyjnej i tym samym wykluczają się z innych hierarchii.

Rozwiązanie pośrednie

Rozwiązaniem pośrednim tego problemu jest wielopoziomowy interfejs. Po prostu jeżeli zachodzi konieczność dodania nowej metody do interfejsu to tworzymy podinterfejs.

Listing 3. Hierarchia interfejsów

interface FreeSort<E> extends Sort{

	Collection<E> sort(Collection<E> c, Comparator<E> comp);

}

Teraz tam gdzie potrzebujemy zmieniamy implementowany interfejs. Mało zabawne.

Rozwiązanie w oparciu o Javę 8

Po tym dość długim wstępie czas na prezentację tego co dają interfejsy z domyślną implementacją w Javie 8. Otóż domyślna implementacja daje nam naprawdę dużo. Z jednej strony zachowujemy swobodę jaką dają interfejsy. Z drugiej otrzymujemy możliwość bezinwazyjnego dodawania metod tak jak w przypadku klas abstrakcyjnych.
Nasz interfejs po zmianie nie będzie miał już podinterfejsu. Otrzyma dodatkową metodę, a jej dodanie nie wpłynie na istniejące implementacje

Listing 4. Interfejs z dodatkową metodą

interface Sort<T extends Comparable<T>>{

	Collection<T> sort(Collection<T> c);

	default <E> Collection<E> sort(Collection<E> c, Comparator<E> comp ){
		throw new UnsupportedOperationException("Default impl");
	};
}

Podsumowanie

Domyślna implementacja w interfejsach wprowadzona w Javie 8 to nie tylko namiastka wielodziedziczenia czy też “coś na wzór scalowych traitów”. To rozwiązanie poważnego problemu projektowego, który bardzo często wpływa na kształt naszego kodu.
Co więcej dzięki temu rozwiązaniu można zrezygnować ze sztucznej hierarchii interfejs + klasa abstrakcyjna, z której wszystko dziedziczy. Takie uproszczenie pozwala na tworzenie API elastycznego zarówno pod względem dowolności implementacji jak i łatwego w rozszerzaniu o nowe możliwości.

Hello IT, please turn off and turn on computer….

June 13th, 2014

Ok jako arystokrata pozwolę sobie na chwilę plebejskiej rozrywki w postaci popełnienia wpisu na bloga…

Sprawa tekstu Tomasza Molgi o “IT-Arsystokracji” jest powszechnie znana. Tydzień zaczęliśmy z grubej rury i czas na podsumowanie. Pozwolę zatem sobie na popełnienie wpisu. Oczywiście by nie być w całkowitym oderwaniu od rzeczy trzy linki:

Każdy z tych tekstów gdy potraktujemy go jako coś samodzielnego nie ma sensu. Nawet po dodaniu kontekstu całej sprawy nadal nie jest dobrze. Jednak jeżeli przyjrzymy się na chłodno tym trzem tekstom na raz to można zobaczyć całkiem ciekawy obraz nie tylko polskiego IT, ale i tzw. pokolenia przemian ustrojowych.

Po kolei jednak…

Tekst 1

Ból dupy. W sumie to idealnie podsumowuje ten tekst. Tyle tylko, że nie do końca wiadomo skąd się wziął. Proste wytłumaczenie, zawiść o kasę, nie wytrzymuje starcia z rzeczywistością. Moim zdaniem dupa redaktora boli o coś trochę innego.
Otóż zapewne z okazji n-lecia matury albo innej okazji okazało się, że klasowa łamaga, osobnik będący pośmiewiskiem i obiektem różnych wrednych żartów (notabene dziś podpadających pod zwykłe znęcanie się) przytoczył się własnym, wypaśnym samochodem (może było to Camaro ;) ), a przy okazji różnych dyskusji okazało się, że kredyt mieszkaniowy już dawno się spłacił i stu metrowy apartament jest “mój i tylko mój”.
Jakże to, żeby taki jebany kuc, co piłki prosto kopnąć na wf-ie nie potrafił. Kujon pierdolony zarabiał lepiej niż ja – klasowa “elytka”. Cóż worek maści na ból dupy dla pana redaktora.
Ale tego nikt oficjalnie nie powie, bo przecież my, pokolenie urodzone gdzieś tam w okolicach stanu wojennego, zawistni nie jesteśmy. I to niezależnie czy prawaki czy lewaki :)

W tekście zaszyte jest jeszcze coś. Swoista wisienka na gówno-torcie. Otóż wypowiedź Filipiaka. Jak to źle, że mu “chłopaków” podkupywali. Tego samego Filipiaka, co to rzucił iż

Każdego wykwalifikowanego programistę da się zastąpić skończoną liczbą studentów

I w którymś z kolejnych wywiadów dorzucił iż liczba ta to “zazwyczaj jeden”. Jeżeli ktoś traktuje wysoko wykwalifikowanych specjalistów jak roboli od łopaty to niech się nie dziwi iż mu uciekają. Ludzie mają swoją godność i nie będą przed byle prezesiątkiem tańczyć jak im zagra.

Mówiąc prościej i tak by nasza biznesklasa zrozumiała. Jeżeli traktujecie programistów i ogólniej dział IT jak roboli to spierdalajcie na drzewo. Krzyż wam w dupę i chuj w plecy.

Tekst 2

Wielki powrót. Tekst chyba miał na celu pokazanie, że programiści rzeczywiście są zepsuci. Pokazał jednak, że Polska to “dziki kraj”. Kraj w którym nie płacenie faktur jest OK, że olewanie ludzi też jest OK. Zatem w cenę trzeba wliczyć swoiste ubezpieczenie od niezapłaconych faktur.
Mamy tu jednak jeszcze kilka innych fajnych aspektów. Pierwsza to “odkrycie” iż IT to biznes globalny. Dosłownie globalny. Ze względu na swój charakter nie ma znaczenia czy pracownik siedzi w Polsce, Bangalore, Singapurze czy też w Nowym Jorku. Dla klienta liczy się dostarczony produkt. W mniejszym stopniu to czy telekonferencja będzie zrobiona rano czy wieczorem. Oczywiście są pewne granice przyzwoitości jeśli chodzi o odległość liczoną w strefach czasowych. Choć i to można zazwyczaj jakoś ogarnąć.
Z globalizacji wychodzi też kolejna rzecz. Zarobki. Skoro programista w Palo Alto może krzyknąć 100 dolarów za godzinę to czemu ja we Wrocławiu nie mogę? Korzystam z tego i mam bonus w postaci niskich kosztów życia w porównaniu z USA. Zatem moje zarobki są jeszcze wyższe (szczególnie gdy porównamy je na zasadzie jeden do jeden z np. zarobkami pismaka). Oczywiście są też programiści za 8 dolców za godzinę…
… bo programistom płaci się za wiedzę, doświadczenie i umiejętności. Nie tylko za bycie zajebistym.
Ostatnia rzecz o której wspomnę w odniesieniu do tego tekstu, a która już została poruszona to sposób pracy i rozliczeń. Pomimo, że statystyki pokazują iż w IT “zatrudnia się na etat”, to tak naprawdę ogromna ilość programistów rozlicza się w oparciu o umowy cywilnoprawne lub fakturę. Tym samym nasza “arystokracja” należy do grona mikroprzedsiębiorców tak kochanych w naszym państwie i pracowników “na śmieciówkach” (tu kolejny tekst z Filipiaka – on też pracuje na śmieciówce). Etaty w IT są zarezerwowane dla specyficznej grupy pracowników.

Tekst 3

Dobry, ale szkoda iż wyglądał jak pierdololo działu HR. Serio, serio. Lecz i tu mamy kilka smaczków. Smaczek pierwszy to etaty. Występują przede wszystkim tam gdzie klientowi zależy na tworzeniu własnych szytych na miarę rozwiązań, czyli w korpo. Pracowników kontraktowych zatrudnia się na zasadzie ludzi od brudnej roboty. Ekspertów od babrania się w gównie. Względnie do wykonania bardzo konkretnych zadań, które pozwalają polepszyć jakość produktów, a nie wymagają stałej pracy np. w celu przeprowadzenia specyficznych prac optymalizacyjnych.
Trochę ten świat etatowego programisty poznałem i wiem, że to tak musi działać. Wiedza kluczowa dla organizacji musi zostać w jej ramach. UoP jest tu świetnym rozwiązaniem. Pracownik w zamian za stabilność i całkiem niezłą kasę dostaje prikaz by gromadzić i przekazywać wiedzę o produktach kolejnym pokoleniom. Ma też możliwość awansu w strukturach firmy w przeciwieństwie do kontraktora. Tym by osłodzić życie “wiecznego robola” płaci się jeszcze więcej :) Jednocześnie jak wspomniałem dostają do robienia rzeczy, których pracownicy wewnętrzni nie mają ochoty ruszać.
Kolejna sprawa to olewanie działów IT na każdym etapie. Począwszy od rekrutacji, poprzez niesłuchanie opinii IT o możliwościach rozwoju oprogramowania i jego ulepszania, a kończąc na zwalaniu przez biznes na IT wszelkich swoich niepowodzeń.
Jest też w tym tekście mowa o starych technologiach. Tu rzecz ma się trochę inaczej. Mateusz Kurleto nie ma racji pisząc, że biznesowi nie zależy na “nowoczesności”.

Proponuję zastanowić się nad innowacyjnością technologii, które wykorzystywane są lata po tym jak producenci wycofują wsparcie a migracje na nowe wersje (nie wspominając o następcach) są niemożliwe do wywalczenia.

Wiesz z czego to wynika? Ano z tego, że technologie te są stabilne. Nikt, w żadnym banku, domu maklerskim czy instytucji ubezpieczeniowej nie pozwoli sobie na wprowadzenie czegoś co nie jest stabilne. Dlatego też do wyliczania obciążeń inżynierowie nadal używają FORTRANa, dlatego w bankach na zachodzie hula COBOL. Kod się nie zużywa. Po 20 latach można założyć iż naprawiono w nim wszystkie bugi, a te które pominięto są znane i uwzględnione w procesie.
Motywacja do zmiany w przypadku tego typu organizacji jest jedna – specjaliści idą na zasłużone emerytury.

Moje zdanie

IT to bardzo specyficzna branża. Szczególnie współczesne, które wyrwało się z okowów informatyki-nauki. Dziś programistą może zostać każdy. Masz komputer to możesz programować. Nie trzeba kończyć studiów ani zdawać państwowego egzaminu. Po prostu musisz się trochę pouczyć…

… no właśnie nie takie trochę. Programiści to specyficzni ludzie. Ci, który trafiają to naszego grona tylko ze względu na kasę bardzo szybko są eliminowani. W sumie to sami się eliminują, bo być programistą to stan umysłu. Mało kto z osób nie związanych z tym fachem zdaje sobie sprawę ile poświęcenia wymaga ten zawód. Tu nie liczy się przebojowość ani uroda. Tu liczy się ciężka praca. Praca nad sobą. Ta wymaga samodyscypliny, “ogarnięcia”, pokory. Tu trzeba po porostu zapierdalać.
Jak rozmawiam ze znajomymi z IT to przez wiele lat głównym problemem w życiu było to jak magazynować książki. Później pojawiły się czytniki. To ułatwiło nam życie.
Programiści są chyba jedyną poza lekarzami grupą zawodową, która musi stale się uczyć. Nie mówię tu o czytaniu i poszerzaniu wiedzy dla przyjemności. Tylko o zwykłej nauce. Dziennikarz może położyć lachę na książki wraz z odebraniem dyplomu. Inżynier może całe życie projektować silniki bez potrzeby sięgania po literaturę fachową. Programista do końca swojego zawodowego życia musi się uczyć.
Pracowałem z ludźmi, którzy do emerytury mieli bliżej niż ja stażu. Nadal się uczyli. Nadal chodzili z nami na szkolenia i nadal czytali różne książki z zakresu IT. Ciągle poszerzali swoją wiedzę. Wyobraźcie sobie gościa lat sześćdziesiąt kilka, który jak uczniak siedzi w ławie, słucha wykładu i robi notatki. Później jak wróci do domu to grzecznie przerabia jeszcze raz cały materiał. Inaczej się nie da.
Wysokie zarobki są nie tyle co nagrodą co po prostu rynkową wyceną zgromadzonej przez nas wiedzy. Bardzo często nie tylko o komputerach, ale też o różnych innych dziedzinach życia. Programista poza wiedzą fachową zdobywa też wiedzę “biznesową”. Bez tego nie będzie wstanie pisać dobrego kodu. Bez tego też nie spełni wymagań klienta. Programowanie komputerów to naprawdę ciężki fach i jeżeli ktoś mówi, że programistom się w dupach poprzewracało to niech po prostu odwoła się do słów Lecha Wałęsy

Sprzątaczce należy się więcej niż dyrektorowi, bo ona więcej potu daje.

Ale by przytoczyć te słowa trzeba wcześniej przeczytać trochę więcej niż to co polecił przeczytać profesor przed kolokwium.

Najpopularniejszy język świata…

June 5th, 2014

… to COBOL. Zdziwieni? Jak wam powiem, że 80% kodu napisanego na świecie jeszcze kilka lat temu było napisane w COBOLu to będziecie jeszcze bardziej zdziwieni :)

Ok, ale nie o tym chciałem. Otóż łapię się ostatnio, że niektóre rzeczy łatwiej jest mi napisać w COBOLu niż w Javie. Nie jest ich dużo, ale mają pewną swoją specyfikę i idealnie wpasowują się w COBOLa.

By nie być gołosłownym mały przykład.

Jaś został deportowany z USA z powodu pracy na czarno. Przybył nad Wisłę mając w kieszeni 5 dolarów. Jako, że za kilka dni wyrusza na truskawki do Hiszpanii postanowił je wymienić na euro. Po drodze chce jeszcze coś zjeść w kraju więc musi dokonać przeliczenia USD>PLN>EUR. Pomóżmy Jasiowi policzyć ile będzie miał na każdym etapie operacji.
Kursy (dzisiejsze)
1 EUR 4,1215
1 USD 3,0275

Program w COBOLu będzie wyglądał tak:

Listing 1. Przelicznik walut w COBOLu

IDENTIFICATION DIVISION.
PROGRAM-ID. PRZELICZNIK-WALUT.
DATA DIVISION.
WORKING-STORAGE SECTION.
    01 kEUR PIC 9V9999 VALUE 4.1215.
    01 kUSD PIC 9V9999 VALUE 3.0275.
    01 PLN PIC 99V99 VALUE ZERO.
    01 EUR PIC 99V99 VALUE ZERO.
    01 USD PIC 99V99 VALUE 5.
PROCEDURE DIVISION.
    DISPLAY "--- Przeliczam USD na PLN".
    MULTIPLY USD BY kUSD GIVING PLN ROUNDED.
    DISPLAY "Po przeliczeniu " USD " mam " PLN "PLN".

    DISPLAY "--- Przeliczam PLN na EUR".
    DIVIDE PLN BY kEUR GIVING EUR ROUNDED.
    DISPLAY "Po przeliczeniu " PLN "PLN mam " EUR "EUR".
STOP RUN.

A teraz zadanie dla was drogie misie. Napiszcie tak elegancko samo dokumentujący się kod w Javie.

Nie istnieje coś takiego jak “planowe postarzanie”

May 8th, 2014

W wielu dyskusjach w internetach na temat “jebanego szrotu składanego z chińskich części przez jebanych nierobów i partaczy w fabrykach w krajach białych murzynów” przewija się hasło “planowego postarzania części”.

Na czym ma to polegać?

Teoria (spiskowa) mówi, że producent specjalnie postarza części za pomocą różnych mechanicznych sztuczek by przedmioty psuły się zaraz po skończeniu gwarancji. Względnie po skończeniu się gwarancji dodatkowej. Ma to na celu wymuszenie na klientach regularnej wymiany urządzeń na nowe.
Dodatkowo sama konstrukcja urządzenia powoduje, że wymiana uszkodzonej części jest nieopłacalna pomimo, że część jest tania…

Jak to jest w praktyce?

… no właśnie część jest tania. Warto sobie poczytać o pewnym ciekawym przypadku. Dla leniwych. GM postanowił oszczędzić niecałego jednego dolara na każdym samochodzie stosując tańszy, ale i znacznie gorszy mechanizm stacyjki. Co więcej klienci, którzy zgłaszali problem byli wysyłani na drzewo. Koszt naprawy to… pięćdziesiąt kilka centów. W efekcie mamy 13 trupów, sprawę w sądzie (a amerykańskie sądy nie pierdolą się z kwotami odszkodowań), wezwanie serwisowe ponad dwóch i pół miliona aut i generalnie chujnię z grzybnią.
Nie ma czegoś takiego jak planowe postarzanie. Zamiast tego producenci starają się maksymalnie ograniczyć koszty produkcji. Pierwsze ograniczenie już miało miejsce. Przeniesiono fabryki do tańszych państw. W droższych pozostawiono montownie oraz linie produkcyjne dla najbardziej zaawansowanych elementów.
Drugie ograniczenie to modularyzacja. Golf czy Skoda, Nissan, a może Dacia wiele współczesnych samochodów różni się tylko marką i sposobem gięcia blach. Kluczowe elementy – płyta podłogowa, rama, silnik, układ przeniesienia napędu itd. są współdzielone przez wiele modeli w ramach jednej korpo. To samo dotyczy elektroniki, agd czy maszyn. Tak jest po prostu taniej.
Trzecie ograniczenie kosztów to “żyłowanie” elementów. To co wiele osób nazywa postarzaniem jest tak naprawdę montażem części tańszych. Zazwyczaj mniej żywotnych. Sztuka polega na takim obcięciu kosztów by zmianie nie uległy inne parametry konstrukcyjne, ani czas gwarancji.

Po co się to robi?

Ano po to by ograniczyć koszty. To przełoży się na większe zyski i premie zarządu. Co więcej nawet w przypadku wtopy jest ona problemem kolejnego prezesa. Tak jak ma to miejsce w przypadku GM. Skutkiem ubocznym jest też podtrzymanie konsumpcji, bo powiedzmy sobie szczerze wszyscy akcjonariusze chcą by firmy sprzedawały więcej i więcej, ale nikt nie patrzy na realne zapotrzebowanie rynku. Ile można mieć lodówek, pralek, samochodów?

Zatem na koniec coś pozytywnego. W IT występuje proces odwrotny. O ile tylko jest szansa na napisanie softu tak by wymagał mocniejszego sprzętu to się go tak pisze… w imię zysków oczywiście.

“Uleniwienie” parametru jako sposób na długie wywołania

April 30th, 2014

Wyobraźmy sobie kod, metodę która przyjmuje dwa parametry. Pierwszy parametr jest następnie wykorzystywany w instrukcji warunkowej, która znowuż wykonuje jakiś kod związany z drugim parametrem. Drugi parametr jest wykorzystywany tylko po spełnieniu warunku. Znajomo brzmi?
Drugi parametr jest wyliczany w wyniku jakiejś długo trwającej operacji. Nasze zadanie brzmi zrobić coś co spowoduje, że całość zacznie działać szybciej. Przy czym:

  • Nie możemy zmienić zależności obiektów. W tym przekazywać ich jako parametrów.
  • Długo trwająca operacja musi zostać wykonana wielokrotnie tzn. nie możemy sobie jej wykonać raz na starcie i cachować wyniku.

Poniżej strasznie prymitywny kod, który mniej więcej emuluje nam to co chcemy:

Listing 1. Przykładowy kod

public class App {

	private LongOpService los;

	public App() {
		los = new LongOpService();
	}

	public static void main(String[] args) {
		App a = new App();
		Random r = new Random();
		for (int i = 0; i < 10; i++) {
			a.someOp(r.nextInt(10), a.los.longOp());
		}
	}

	public void someOp(int param, int longOpResult) {
		if (param < 2)
			System.out.println(longOpResult);
		else
			System.out.println(param);
	}
}


class LongOpService {

	public int longOp() {
		System.out.println("Long Op!");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return 42;
	}
}

Interesuje nas pętla w metodzie main, która emuluje wielokrotne wywołanie metody someOp, która znowuż wymaga w pewnych przypadkach wyniku longOp. Na kolejnych listingach będziemy obracać się w okolicach tej pętli.

Brute force - NullObject

Załóżmy, bardzo optymistycznie, że metoda longOp ma ograniczony zbiór wartości, które może zwrócić. Względnie zamiast inta zwraca coś mniej prymitywnego... i można wstawić null. Możemy wtedy zastosować wzorzec Null Object.

Listing 2. Null Object

for (int i = 0; i < 10; i++) {       
	int param = r.nextInt(10);       
	int longOpResult = -1;           
	if (param < 2)                   
		longOpResult = a.los.longOp();
	a.someOp(param, longOpResult);   
}                                    

Tu rolę obiektu użytego zamiast null spełnia wartość -1. Strasznie to brzydkie. Jest lepsza metoda. Może by tak udało się przenieść wywołanie długiej operacji do ciała metody someOp. No tylko, co z tymi zależnościami...

Opóźnienie wywołania

Może jednak udało by się to zrobić jeżeli sięgniemy po pewne myki znane ze Scali. Jest tam sobie taka konstrukcja:

Listing 3. Co chcemy wykorzystać ze Scali

def someOp(param: Int, longOpResult: =>Int) = {...}

Zapis ten oznacza "wylicz drugi parametr jak go będziesz potrzebować". Na czym polega myk? W językach imperatywnych by wywołać jakąś metodę komputer wyznacza wszystkie parametry. Co oznacza, że za każdym razem odpala naszą operację i czeka... czeka... czeka... Jako, że nie możemy przekazać bezpośrednio obiektu LongOpService (bo przyjdzie sprawdzanie zależności pomiędzy modułami i nas zje) to zostaje nam tylko jedna sensowna opcja.

Listing 4. Nasze rozwiązanie

for (int i = 0; i < 10; i++) {
	a.someOp(r.nextInt(10), new Callable<Integer>() {
		@Override                                    
		public Integer call() {                      
			return a.los.longOp();                   
		}                                            
	});                                              
}

W tym przypadku wywołanie długo trwającej operacji nastąpi dopiero po spełnieniu warunku. Przy czym Callable nie jest najgenialniejszym rozwiązaniem ponieważ rzuca wyjątkiem.

Dobra. Tyle na dziś. Choć rozwiązanie nie jest niczym odkrywczym dla pr0, to jednak może się komuś przydać.

Kod z Devcrowd 2014

April 28th, 2014

Jako, że już kilka osób pytało

https://github.com/Koziolek/koziolekweb/tree/master/java8

Jako, że git nie ma partial clone to pociągniecie przy okazji cały kod z bloga… Fajnie :)