czwartek, 6 marca 2014

Effective java programming in practice part 1


Effective java  jest podstawową książką adresowaną dla każdego członka javowej społeczności.

Dzięki niej zrozumiesz jak pisać lepszy kod, łatwiejszy w utrzymaniu, bardziej odporny na błędy.
Poza tym przybliży Ci mechanizmy GUAVY, pozostawiając Twój kod w lepszej kondycji i pomoże ominąć pułapki jakie czekają na Ciebie podczas implementacji systemów napisanych w javie. Ten post stanowi zbiór dobrych praktyk służących do wytwarzania oraz budowy obiektów w sposób wydaje mi się najbardziej efektywny.





Zacznijmy w końcu więcej kodować.
Kiedy zaczynamy coś wytwarzać jest dobrze i wygodnie posiadać jakieś fundamenty, a najlepiej solidne. Często taką podwaliną jest baza danych. W środowiskach programistycznych od kilku dobrych już lat traktuje się bazę jako coś co ma zapewnić trwałość.
Co to oznacza ? - tylko tyle, że jak pierdolnie prąd czy zdarzy się cokolwiek innego nasze dane nie znikną.
Zostaną i będą bezpieczne. Oczywiście to nadal jest "fikcja", ponieważ aby zapewnić bezpieczeństwo tych danych i mieć 100% pewności, że faktycznie nasze dane pozostaną w takim stanie jak oczekiwalibyśmy będzie nas to sporo kosztowało.
Musimy przyjąć odpowiednią politykę bezpieczeństwa jak: regularne backupy (przyrostowy, różnicowy czy pełny), użycie streamera, użycie macierzy RAID, cloud, centra danych itd. Generalnie działania zapewniające bezpieczeństwo czy wysoką dostępność dla naszych danych wiążą się z zastosowanie redundancji maszyn czy innych zasobów co implikuje zwiększeniem kosztów:)

Ale ok my zajmiemy się dzisiaj bardziej przyziemnymi sprawami jak kodowanie.
Dla mnie jako programisty obiektowego nie istnieje baza danych. Tak naprawdę powinno się tak programować jakby jej w ogóle nie było.
I tak mamy Hibernate, TopLinka, EclipseLinka, iBATISa oraz inne twory.
W groovy dostaniemy GROM, w scali jest np  Slick, SORM czy inne.

Fajnie podchodzić do tego jak do programowania na kolekcjach. Baza jest gdzieś tam w tle. Jeśli chcesz zostawić sobie większą elastyczność na przyszłość np zmienić providera persystencji postaw JPA API.

Jest też druga strona medalu. Zapytania bazodanowe (natywne), optymalizację struktur danych, indeksację, normalizację warto znać. Bez tego Twój ORM i tak może zacząć kuleć, nawet jak cache'ujesz już dane. Jeśli nie ma podziału na bazodanowców i programistów będziesz musiał zmierzyć się wcześniej czy później z tymi problemami. Należy też pamiętać, że ORM się nie nadaje do wszystkich zastosować. Masowe przetwarzanie danych na operacjach typu delete, insert czy update mogę poważnie zaboleć jak wszystko opierasz na ORM lub spowodować OutOfMemoryException.
ORM jest świetny ale narzut na cache i mechanizmy sprawdzające świeżość danych "Dirty pattern" zrobią swoje. Niektóre specyficzne zapytania dla konkretnych baz danych mogą nie zadziałać jak powinny we współpracy z dedykowanym  dialektem lub zadziałać po prostu źle. Wtedy będziemy musieli zrobić coś natywnie - takie życie:)

Mój osobisty wybór to Hibernate  poprzez JPA 2.1 a do tego koniecznie Spring Data.
Spring Data to jest coś co też wypada znać. Kiedyś bawiłem się w swoje rozwiązania generycznego DAO potem powstało coś na kształt wczesnej wersji Spring Data JPA, oczywiście dużo gorsze rozwiązanie ale co było w tym najlepsze? - Specifications, który jest sercem JPA API.
Wkrótce okazało się, że to mój największy osobisty sukces w programowaniu, bo twórcy JPA API wykorzystali taki same podejście i niemal identyczny interfejs toPredicate() jak ja w swoim dawnym rozwiązaniu generycznej wyszukiwarki.

Wyszukiwarka ta miała dużo niedociągnięć i niedoróbek ale potrafiła poprzez poprawne użycie adnotacji na modelu przekazywanym do kontrolera wyszukiwać, sortować, grupować i stronicować dane bez użycia SQL'a czy HSQL. W przypadku kiedy poziom czy stopień skompilowania faktycznie nie pozwalał użyć standardowych moich adnotacji trzeba było wywołać metodę  toPredicate() opartą na Criteria API.

TIP : Spring Data - możesz używać dao w rozszerzonej formie CRUD'a bez potrzeby pisania implementacji kodu. Co więcej jeśli będzie potrzeba użyć innej nie relacyjnej bazy danych, otrzymasz ogromną pomoc i zrobić dużo rzeczy w dziecinnie łatwy sposób :) Redukcja kodu w obu przypadkach jest ogromna.

W założeniu Spring Data wykorzystujesz generalnie interfejsy a zapominasz o implementacji. Czasem jednak bez implementacji kawałka kodu nie może się obejść..

Spring Data JPA = wszystkie bazy relacyjne
Spring Data Neo4j, MongoDB etc - bazy nosql

Projekt umożliwia dostęp do operacji CRUD poprzez swoje interfejsy :


- Saves the given entity.

- Returns the entity identified by the given id.

- Returns all entities.

- Returns the number of entities.

- Deletes the given entity.

- Indicates whether an entity with the given id exists.
Głupio było by robić to samemu skoro Spring podaje Ci to na tacy.

TIP : Spring REST DATA - udostępnia interfejs CRUD przez REST
(możesz teraz wykonywać operacje na modelu domenowym poprzez REST)

TIP : Zawsze kastomizuj swoje repozytorium.

TIP : Repository populators - zasil bazę danymi formatu JSON czy XML (oxm)

Dokładny opis i konfigurację projektów Spring Data i Spring Data Rest znajdziesz tutaj

W effective java jest następujący rozdział:

 Tworzenie i usuwanie obiektów (Creating and Destroying Objects)

Tworzenie statycznych metod factory zamiast konstruktorów
(Consider static factory methods instead of constructors)

Najczęściej znaną techniką na udostępnienie egzemplarza danej klasy jest utworzenie publicznego konstruktora. Metoda ta sprawdza się doskonale kiedy konstruktor zawiera max do dwóch parametrów.
Inna metodą stworzenia obiektu jest wykorzystanie statycznej metody factory występująca też pod inną nazwą jak Creation Method.

Ważne jest też to aby nie mylić tych metod ze wzorcem Factory Method, ponieważ zadaniem tego wzorca jest generowanie obiektów różnych klas w oparciu o polimorfizm. Fabryka ta jako wzorzec nie może być metodą statyczną, a implementacje muszą znajdować się w co najmniej dwóch klasach.


Bardzo dobrym przykładem takiego zachowania jest : klasa Boolean
Zalety (wykorzystania creation method):

możliwość posiadania wymownej nazwy w stosunku do standardowych konstruktorów. Takie podejście sprawia, że powstały kod jest łatwiejszy do analizy.

-  nie musimy tworzyć nowego obiektu podczas ich wywołania. Dzięki czemu możemy tworzyć klasy niezmienne, korzystające z wstępnie tworzonych obiektów lub tak zarządzające procesem tworzenia, aby uniknąć duplikacji tworzenia obiektów.

- możliwość zwracania typu, który jest podtypem zdefiniowanego, zwracanego typu

- ograniczenie objętości tworzonych instancji typów parametryzowanych.

Jak rozpoznać takie metody albo jaką konwencję nazewniczą stosować :

valueOf - zwraca obiekt , który ma tę samą wartość co parametr

of - bardziej zwięzła alternatywa powyższego przykładu

getInstance - zwraca obiekt opisany przez parametr. Charakterystyczne podejście dla wzorca Singleton. Często stosowana konwencja przez dostawcę usług.

newInstance - podobna do powyższego, lecz gwarantuje za każdym razem inną instancję obiektu.

getType , newType - podobna do getInstance ale używana gdy metoda factory znajduje się w osobnej klasie.

Lombok pomoże stworzyć Ci metody na kształt Creation Method.
@RequiredArgsConstructor = creation method

Ok , ok ale co ze Springiem ?
W przypadku wykorzystania Java Config (to podejście bardzo polecam) problemu w ogóle nie ma. Używany swoich implementacji beanów poprzez użycie @Bean.
Dlaczego mamy stosować ten cały xml skoro piszemy w javie ?
Dlaczego mam pisać cały czas web.xml skoro kilka lat temu już dostępny jest standard servlet 3.0. (no chyba, że używam starego serwera aplikacyjnego czy też starego kontenera serwletów)

 Wady :
 - klasy nie posiadające publicznego lub zabezpieczonego konstruktora nie mogą być dziedziczone.

Problem : liczne parametry w konstruktorze.

Rozwiazanie 1 (właściwe):
 
Zastosowanie budowniczego do obsługi wielu parametrów konstruktora.
Metody factory oraz konstruktory mają jedne wspólne ograniczenie - słabo obsługują dużą liczbę parametrów.

Jak stworzyć swój własny builder : 
   - konstruktor powinien być prywatny co oznacza, że klient nie może bezpośrednio stworzyć obiektu klasy
   - klasa powinna być niezmienna
  - udostępnić statyczną metodę do budowy buildera np newInstance()
  - udostępnić metodę build() - która służy do budowy obiektu

TIP : Zawsze użyj buildera gdy projektujesz klasę, której liczba argumentów konstruktora czy statycznej metody factory przekraczała by liczbę kilku parametrów.

Generalnie wzorzec builder służy do uproszczenia tworzenie struktury obiektów,
ukrywając przez klientem prawdziwy proces budowy oraz jego implementacje.

Rozwiązanie 2:
Wzorzec konstruktora teleskopowego działa, ale dla klienta, który na wpisać ponad trzy, cztery i więcej parametrów. Ale jest to koszmar oraz zwiększa ryzyko popełnienia błędu.

Tip : Zobacz i zrozum jak działa HandlerMethodArgumentResolver + Pageable
(super fajne rozwiązanie zastosowane w Springu, pozwalające ograniczyć ilość przekazywanych argumentów. Możesz pisać swoje spersonalizowane rozwiązania. Opis zastosowania i przykład znajdziesz w tym poście)


Problem : liczne konstruktory 

Rozwiązanie: Zastąp konstruktory statycznymi metodami factory, których nazwy niosą informację o ich przeznaczeniu. Drugą zaletą tego rozwiązania jest brak ograniczeń konstruktora czyli możliwość definiowania dwóch różnych konstruktorów o takich samych typach i ilości argumentów.
Trzecią zaletą jest szybsza eliminacja niewykorzystywanego, martwego kodu z systemu.

Mechnika rozwiązania:
Zaczynamy od zdefiniowania konstruktora zbierającego (należy skorzystać z przekształcenia 'chain constructors'). Następnie konstruktory teleskopowe zamieniamy na creation methods (można wykorzystać przekształcenie inline method). Na koniec dostajemy jeden konstruktor o zakresie public i zbiór metod statycznych pomagających wytwarzać potrzebne typy obiektów.

Jeśli potrzebujesz stworzyć singleton pamiętaj aby użyć konstruktora prywatnego w celu uniemożliwienia stworzenie egzemplarza obiektu przez klienta. Obiekt pobieraj metodą getInstance().
Publiczne pola statyczne zawsze oznacz jako final. Aby umożliwić serializację klasy singleton nie wystarczy jedynie dodać interfejs Serializable. Musimy nadpisać jeszcze metodę readResolve. Jeśli tego nie uczynimy każda serializacja i deserializacja obiektu spowoduje utworzenie nowego egzemplarza klasy. Nie będzie więc już ona dłużej singletonem.

TIP : Najbardziej odpowiednim i jedynym właściwym rodzajem singletonu w javie  jest enum!!!

Jeśli budujesz klasy narzędziowe nie powinny one mieć możliwości tworzenia swojej własnej instancji. Należy zawsze zadeklarować konstruktor prywatny jako konstruktor domyślny.


Obiekty niezmienne to obiekty których stan po utworzeniu nie może ulec zmianie.

    Zalety :
     -  thread-safe - współdzielenie obiektów - problemy związane z wątkowością
     -  łatwe do stworzenia, przetestowania i użycia
     -  nie potrzebują konstruktora kopiującego
     -  nie potrzebują  implementacji metody clone     
     -  doskonale pasują się na klucze w kontenerach opartych na hashCode
     -  pozwala na bezpieczne przekazywanie argumentów ( przykład : String)
     -  lepiej zarządzana przez jvm i gc
  Jak stworzyć klasę finalną :
     - klasa nie może pozwalać na dziedziczenie czyli musi być finalna
     - wszystkie pola powinny być finalne
     - wszystkie pola powinny być prywatne
     - klasa nie powinna wystawiać setterów



TIP : Obiekt niezmienny to Twój przyjaciel - wykorzystaj go

 W powyższym przykładzie użyłem również biblioteki Lombok do stworzenia klasy niezmiennej. Wszystkie pola mam finalne i tworzone przez konstruktor
za pomocą adnotacji @RequiredArgsConstructor = creation method. Wszystkie zmienne mogę jedynie pobierać  a dostaje to dzięki właściwości @Getter. Dodałem jeszcze generowanie funkcji equals i hashCode wykorzystując @EqualsAndHashCode oraz @ToString aby nadpisać standardowy toString(). Co też jest dobrą praktyką.

Piszą w groovy tworzymy podobne struktury. Właśnie tak poprzez początkowe doświadczenia w Groovy jakieś dwa lata temu znalazłem bibliotekę Lombok.
Oba podejścia są bardzo fajne gdyż redukujemy niepotrzebne settery i gettery, które są w standardzie Java Bean i tak czy inaczej pisząc w javie musimy je w ten czy inny sposób zaimplementować.

Tip : @Cannonical@ToString , @EqualsAndHashCode i @TupleConstructor a to jest w prostej linii odpowiednikiem @Data w lomboku.

Co zaoferuje nam Guava ? - Object common methods
czyli :
  equals i hashCode :
Z wykorzystaniem guavy:

Z wykorzystaniem Lomboka:  
ale zaraz zaraz equals i hashCode z guavy ?....:)  - jeśli używasz  java 7 już to masz  - zgadza się :) Warto jednak mieć świadomość skąd to przyszło do javy 7.

Guava daje mam jeszcze :

 - toStringHelper

 - compare/compareTo

- comparisonChain

- kolekcje niezmienne, operacje  na kolekcjach i Stringach i wiele innych features, które Guavy opiszę  już w następnym poście.

Czego się dziś nauczyłeś ? - Sam odpowiedz sobie na to pytanie. Kilka lat temu pisałem system, który integrował się z innymi systemami za pomocą plików. Wszystko mogło wydawać się na pierwszy rzut oka ok. Najnowszy wtedy dostępny spring, hibernate oraz pozostałe biblioteki. Działały profile stworzone w mavenie. Podział na moduły i pakiety też wydawał się być ok i taki był.
Dlaczego więc z perspektywy czasu ten projekt uważam na jeden z najbardziej spieprzonych w moim życiu ? Bo był ciężki w utrzymaniu. Miał masę kodu, którego trzy lata potem już wyeliminowałem stosując wczesną wersję Lomboka i Guavę. Nie znałem wtedy jeszcze Camela. Cały kod związany z obsługą plików spoczywał na moich barkach, to samo ze scheduling'iem. Dalej doszła do tego konwersja formatów, obsługa błędów itd. Sporo rzeczy było poprawnie zrobione  dzięki zastosowaniu AOP. Ale problem utrzymania i czystości kodu pozostał. Jak jakiś czas temu włączyłem sonara i przeprowadziłem analizę kodu i ....to co pokazał ....porażka.
Dzięki zastosowaniu features z Guavy i Lomboka w kilka godz tak dla treningu poprawiałem statystyki w znaczący sposób. Ilość kodu zmniejszyła się o 34% .
Gdybym zastosował jeszcze wzorce EIP oparte na Apache Camel myślę, że zmniejszyłbym ilość kodu o następne 30-40%. A utrzymanie, elastyczność i inne metryki projektu na pewno znacznie by wzrosły.

Na koniec chce dodać, że jeśli używasz Spring, ORM  czy innego zrębu oprogramowania nie zwalnia Cię to z posiadania elementarnej wiedzy jak tworzyć i projektować obiekty.

Otwórz się na nowe języki oparte na JVM : Groovy i Scale wiele rzeczy można zrobić tam prościej.

Kod basic_entity znajdziecie tutaj.

dalej


Brak komentarzy:

Prześlij komentarz