poniedziałek, 24 marca 2014

Effective java programming in practice part 2

Guava została stworzona byś pisał mniej kodu.

Jednocześnie kod ten ma takie zalety jak zwiększona czytelność, czystość oraz wydaje się bardziej prosty.

Pozwala skupić się na problemach biznesowych zamiast tworzyć powtarzalne już struktury kodu jak : utils czy helper


Dla mnie Guava jest rozszerzeniem tez zawartych w książce Joshua Blocha pt
Effective java programming.

Dlaczego guava ?
 - static import
 - eliminacja boilderplate code
 - minimalizacja kodu
 - testy, jakość, pewność.
 - wydajność
 - niezmienność
 - kodowanie defensywne
 - wprowadzenie do kodowania funkcyjnego (Functional idioms)
   ( w java 8 -> java.util.function)

Recepty :

Unikaj jak tylko możesz tworzenia obiektu.

Zamiast używać konstruktora w celu tworzenia obiektu użyj statyczną metodę wytwórczą, która zwykle tego nie robi.

Tip : W przypadku Springa możesz użyć : FactoryBean


nigdy nie rób tak jak poniżej !:

Używaj typów prostych zamiast opakowanych typów prostych - wydajność !!

Tip : Rozważ użycie wzorca Flyweight

Tworzenie i zwracanie małych obiektów nie jest kosztowne.

Stosuj pule wątków : Executors (ściśle związane z kolejką zadań) - Wątek pobiera z kolejki zadanie wykonuje je i wracają z powrotem by poprać następne zadanie
Ponowne wykorzystanie tych samych wątków bez tworzenia nowych ,rozkłada koszty kreacji i niszczenia na wiele zadań. Zmniejsza opóźnienia wykonania zadania ponieważ wątek już jest w puli. Odpowiedni rozmiar puli zapewnia efektywne wykorzystanie zasób procesora przy jednoczesnym uniknięciu zajęcia całej dostępnej pamięci przez tworzone nieustanie wątki. (saturation)

Stosuj pule połączeń dla połączeń z bazą
Każde wytworzenie i nawiązanie połączenia jest drogie. Ale idąc w drugą stronę utrzymywanie puli połączeń również. Zawsze należy szukać równowagi.

Przypisuj wartość null do niepotrzebnych już referencji. (wycieki pamięci) Jeśli referencja do obiektu będzie nieświadomie przetrzymywana, nie tylko ten obiekt nie będzie podlegał odśmiecaniu ale również cały graf obiektów mu podległych.

Tip : Najlepszym sposobem na eliminowanie niepotrzebnych referencji jest powtórne ich wykorzystanie lub wyjście poza z ich zasięgu.
Programując pseudo-obiektowo jesteś narażony na powyższą sytuację.

Tip : Stosuj możliwe najmniejsze zakresy zmiennych oraz ich widoczność.

Tip : Buforowanie może powodować wycieki pamięci. Stosuj WeakHashMap , lub stosuj zmienną timeIdle do określenia świeżości danych w buforach.
 
Tip : Listenery i event'y mogą być źródłem wycieków pamięci. Należy pamiętać o wyrejestrowaniu usług.

Unikaj finalizatorów , bo :
 - są nieprzewidywalne
 - niebezpieczne
 - użycie ich zwykle jest błędem programisty
 - powodują spadek wydajności

Zamiast finalizatorów używaj metodę kończącą, zwalniającą dane zasoby.


Używaj konstrukcji try-finally a jeszcze lepiej try-with-resources

Medoty wspólne : 

toString() - kiedy tylko możesz dobrą praktyką jest przedefiniowanie metody toString tak aby nie zwracała funkcji skrótu która to de facto nic nam nie mówi.
Tip : Użyj guavy Objects.toStringHelper lub Lomboka @ToString

equals()

Kiedy nie musimy przesłonić metody equals : 
- kiedy obiekt ze swojej natury jest unikalny
- kiedy nie trzeba sprawdzać logicznej równoważności
- kiedy nadklasa posiada już przesłoniętą metodę equals i to jej działanie jest wystarczające w stosunku do naszych zamierzeń
- kiedy jest to klasa prywatna wewnątrz pakietu i metoda equals nie zostanie nigdy wywołana

Kiedy musimy koniecznie przesłonić metodę equals :
-  logiczna równoważność może różnić się od zwykłej identyczności w ramach obiektu javy

Przedefiniowanie metody equals musi zapewnić następujące warunki: 
 - zwrotność  if (x != null)  x.equals(x) == true
 - symetria  x.equals(y) == y.equals(x)
 - przechodność x.equals(y) == y.equals(z) == z.equals(x)
 - spójność  -  idempotentność  - wielokrotne wywołanie x.equals(y) zawsze zwraca ten sam wynik

Zasada tworzenia : 
 - sprawdź przez == czy argument jest referencją do tego samego obiektu
 - użyj instanceOf w celu sprawdzenia czy argument jest odpowiedniego typu
 - rzutuj argument na właściwy typ
 - dla kluczowych pól porównaj wartości z obiektem przekazanym jako argument
 - zawsze przedefiniuj metodę hashCode() , w przypadku przedefiniowania metody equals()

Tip : Użyj Lomboka lub guavy w celu stworzenia equals() lub hashCode() i zapomnij o powyższych zasadach.

Tip : od javy 1.7  użyj klasy narzędziowej Objects.


hashCode()

Bez prawidłowego działania hashCode zapomnij o prawidłowym działaniu kolekcji opartych o kod mieszający.

Warunki :
 - idempoteność
 - jeśli metoda equals() wykażę , że obiekty są jednakowe metoda hashCode() powinna zadziałać podobnie - identyczna funkcja skrótu.
 - nie jest wymagane aby dwa różne obiekty , według porównania equals zwracały różne wartości. (Jednak zwracanie różnych wartości znacznie poprawia wydajność operacji na tablicach mieszających)


Uwaga ! : zła implementacja metod hashCode i equals może nas bardzo dużo kosztować. Powstałe w ten sposób anomalie i błędy działania programu nie są trywialne i ciężkie go wyłapania. Zawsze poświęć trochę czasu nad zastanowieniem się czy dobrze zaprojektowałeś metodę equals w stosunku do jej kontraktu. Podobnie postępuj z metodą hashCode.

Programowanie defensywne :

Preconditions - check conditions you expect variables
W poprzednim poście zastosowaliśmy buildera aby wyeliminować problem licznych parametrów w konstruktorze. Dziś użyjemy mechanizmu guavy  Preconditions w celu walidacji parametrów podczas stosowania wzorca builder co jest dobrą praktyką.

Co zyskujemy ?
Ważne jest aby możliwie jak najszybciej wyrzucać błędy z funkcji czy funkcjonalności biznesowej tak aby wyrządzić możliwie mało szkód i a potem łatwo namierzyć problem.

kod znajdziesz tutaj

Wyjątki. 
Powstały do obsługi sytuacji wyjątkowych. Tworzenie, zgłaszanie i przechwytywanie wyjątków to operacje stosunkowo kosztowne. Słabo optymalizowane przez JVM
Umieszczenie kodu w bloku try-catch uniemożliwia niektóre optymalizacje.

Możemy przerwać (termination) jeśli błąd jest krytyczny, nie istnieje możliwość powrotu do miejsca w którym pojawił się wyjątek lub wznowić czyli próbować wywołać wadliwą metodę jeszcze raz (np OptimisticLockException)

Są w pełni funkcjonalnymi obiektami w których można definiować pola oraz metody. Ma to na celu pomóc programiście opisać lepiej kod przechwytujący wyjątek oraz zdarzenie które je spowodowało.

Tip : Stosuj centralizację obsługi wyjątków : AOP
AOP : ThrowsAdvice lub @AfterThrowing
(An after throwing advice is executed only when an exception is thrown by a join point. )

Jeśli je stosujemy dobrze to :
 - zwiększamy czytelność
 - ułatwiamy konserwacje oraz solidność
 - bulletproof programming





źródło : www.javamex.com 

Dzielimy na :
- weryfikowalne  (Checked exceptions) - muszą być jawnie obsłużone w metodach
- nieweryfikowalne (Runtime exceptions (unchecked exceptions)) - są instancją klasy Error i RuntimeException
Nie potrzebują jawnej obsługi. Rollbackują transakacje!! 
Wyjątki nieweryfikowalne nie powinny być przechwytywane.
 

- błędy (Errors)

Wyjątki nie mogą służyć do sterowania przepływem programu !!

Dobrze zaprojektowane API nie powinno wymuszać na klientach wykorzystywania wyjątków do sterowania przypływem.

Przychwytywanie wyjątków stosuj w przypadkach kiedy możemy przywrócić normalny stan programu.

Wszystkie zdefiniowane przez Ciebie wyjątki nieprzechwytywalne powinny dziedziczyć po RuntimeException

Wyjątki przechwytywalne stosuj wtedy kiedy można naprawić błąd, a nieprzechwytywalnych do obsługi błędów programowych.

Tip : Patrz rozdział wyżej programowanie defensywne a wykorzystanie wyjątków. 

Nigdy nie ignoruj wyjątku !!  - blok catch nigdy nie może być pusty.
Zaloguj zawsze wyjątek do odpowiedniego loggera (ERROR, FATAL , WARN)



Zawsze zawężaj wyjątki jak to tylko możliwe: Zamiast używać Throwable lub Exception używaj podklas odpowiednich do zaistniałej sytuacji


Nigdy nie łap Throwable. Ponieważ java Erros jest podklasą Throwable. Jeśli program wyrzuci Error to znaczy, że jest martwy.

Zawsze odpowiednio 'wrap' (opakowuj) wyjątek tak aby nie stracić stack trace.

Nigdy nie wyrzucaj wyjątku z sekcji finally !

Jeśli stosujesz metodę która może wyrzucić jakiś wyjątek, lepiej zastosować sekcje finally zamiast catch ponieważ w sekcji finally wykona się jeszcze kod np czyszczący zasoby.

Zawsze wyrzucaj wyjątki najszybciej jak to możliwe.
Tip : patrz wyżej guava Preconditions

Pamiętaj o czyszczeniu zasobów nawet jak zostanie wyrzucony jakiś wyjątek - zastosuj finally block.

Owijaj wyjątki w stosunku do potrzeby. Np NPE zwykle nie mówi nic. Czasem lepiej jest użyć np IllegalArgumentException , IllegalStateException etc


Tip : Zawsze staraj się dopasowywać wyjątki do sytuacji

Tip : Używaj walidatorów JSR 303

<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>


Staraj się nie rozprzestrzeniać takich samych bloków try-catch - Użyj klasy szablony klas lub metody templatowe.



Warte uwagi są również rozwiązania zaproponowane przez frameworki integracji w kwestii obsługi błędów
np definicja ponawiania wygląda w skrócie tak : (źródło camel in action)
 

Ale co w tym dobrego? To, że wszystko jest pięknie zamknięte w abstrakcjach i szablonach przez co nigdy nie ponawiamy kodu. A obsługę sytuacji wyjątkowych i związanych z tym ponowień coś na kształt kolejek JMS mamy out-of-the-box.





Kiedyś używałem swoich wyjątków biznesowych, które dziedziczyły po Exception. To nie było dobre. Po pierwsze takie podejście łamie zasadę Open-Close po drugie narusza hermetyzacje programu. Jeden dodany wyjątek powoduje zmianę całego stosu API , między innymi poprzez potrzebę dodania klauzuli throws. Mało tego korzystanie z wyjątków weryfikowalnych nie powoduje wycofania transakcji, przez co musiałem odpowiednio niepotrzebnie dekorować @Transactional(rollbackFor=....).


Dziedziczenie po RuntimeException prawie zawsze jest lepszym pomysłem.

Korzystaj z udogodnień dostępnych już od javy 7 : 

- try-with-resources block - jako usprawnienie obsługi wyjątków w kodzie

- catching multiple exception types- jako usprawnienie sprawdzania typów wyjątków

Korzystaj z mechanizmu propagacji Exception w Guavie

Korzystaj z google catch-exception w celu testowania wyjątków (najlepsza praktyka testowania wyjątków w javie)
 

więcej o strategiach testowania znajdziesz w tym poście
 cdn

Brak komentarzy:

Prześlij komentarz