Praktyczna Java. Lombok – boilerplate generator

By 31 października 2015Praktyczna Java

hashCode, equals, toString, loggery, gettery, settery – kod, który niby inc nie robi, a jest potrzebny. Do tego może przysporzyć nam niemało problemów. A co byś powiedziała na to, żeby zastąpić go jedną adnotacją? Uwaga: po przeczytaniu tego wpisu nie będziesz chciała pracować w projektach bez Lombok’a ;)

Boilerplate

Być może spotkałaś się już z tym terminem, ogólnie oznacza on kod, który nie realizuje żadnej funkcji biznesowej, a jedynie służy połączeniu elementów w całości, udostępnieniu jakichś danych, logowaniu informacji itp – innymi słowy jest niezbędny, ale powtarzalny, nieciekawy do pisania i ‚zaśmiecający’ kod aplikacji.

Teminem tym określamy wszystkie metody takie jak gettery, settery, toString, hashCode oraz equals a także deklaracje loggerów i podobne. Wprawdzie wiele IDE pozwala nam generować ten kod automatycznie, to nadal musimy o nim pamiętać oraz ‚zaśmieca’ nam on resztę aplikacji.

Lombok

W odpowiedzi na te problemy powstał Lombok – stosunkowo niewielka biblioteka, która daje nam kilka adnotacji pozwalających na generowanie tego typu kodu automatycznie.

Co wyróżnia Lombok na tle innych narzędzi? O ile wcześniejsze rozwiązania opierały się o generyczny kod, najczęściej korzystający z refleksji w czasie działania programu, Lombok po prostu ‚dopisuje’ kod do aplikacji w czasie jej kompilacji. Dzięki temu nie tracimy na wydajności, a jednocześnie honoruje on wszystkie ‚nadpisane’ przez nas metody, dzięki czemu integruje się w sposób nieinwazyjny.

Co ciekawe, wykorzystuje ona nieudokumentowaną funkcjonalność kompilatory Javy – o ile po szczegóły zainteresowanych odsyłam do literatury (mówimy tutaj o modyfikacji AST), ogólnie chodzi o to, że w momencie kiedy Java ‚czyta’ kod przed kompilacją i zamienia go na reprezentację w pamięci, Lombok modyfikuje tą reprezentację dodając odpowiednie metody i pola. Dzięki temu dla Javy dodane funkcjonalności wyglądają jak standardowy kod, a jednocześnie nie mamy dodatkowych ograniczeń związanych z koniecznością dziedziczenia po jakiejś klasie lub odwoływania się do innych klas. Ponadto Lombok jest wymagany tylko w momencie kompilacji i nie musi być obecny w skompilowanej aplikacji.

Instalacja i użycie

Korzystanie z Lomboka wymaga jedynie jego dołączenia do projektu jako biblioteki – jeśli używamy Mavena, wystarczy skorzystać z poniższego fragmentu:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.6</version>
    <scope>provided</scope>
</dependency>

Pozostaje jeszcze jeden krok – ‚powiedzenie’ naszemu IDE, aby widział także metody generowane przez Lomboka. Na szczęście sam project Lombok się tym zajmie w przypadku Eclipse – wystarczy uruchomić plik JAR i postępować wg wskazówek na ekranie. W przypadku IntelliJ dostępny jest plugin, który możemy zainstalować w standardowy sposób z poziomu IntelliJ.

Adnotacje

Lombok oferuje funkcjonalność za pośrednictwem adnotacji – zarówno na poziomie klas jak i metod. Poniżej zbiór najważniejszych, pełną dokumentację możesz znaleźć na stronie projektu pod adresem https://projectlombok.org/features/index.html .

@Getter / @Setter

Adnotacje te pozwalają na dodanie getterów lub setterów dla wybranych pól. Adnotacja umieszczona nad klasą spowoduje dodanie metod dla wszystkich pól tej klasy. Przykładowe zastosowanie:

@Getter
@Setter
public class Aplikacja {
    @Setter(AccessLevel.PROTECTED) private String prywatnePole;
    private String publicznePole;
}

Powyższy kod utworzy publiczne gettery i settery do obu pól, za wyjątkiem metody setPrywatnePole, która będzie miała widoczność protected.

@EqualsAndHashCode

Dzięki tej adnotacji, wygenerowane zostaną metody hashCode oraz equals uwzględniające wszystkie pola. Metody te oczywiście spełniają kontrakt hashCode-equals, o którym pisaliśmy więcej w osobnym wpisie. Przykładowe użycie:

@EqualsAndHashCode
public class Aplikacja {
    private String prywatnePole;
    private String publicznePole;
}

Powyższy kod utworzy metody hashCode oraz equals, które nadpisują te domyślne, z klasy Object.

@ToString

Generuje metodę toString() dla wybranej klasy. Metoda ta domyślnie wypisze wartość wszystkich pól w czytelny sposób. Przykładowe użycie:

@ToString
public class Aplikacja {
    private String prywatnePole;
    private String publicznePole;
}

@RequiredArgsConstructor / @AllArgsConstructor / @NoArgsConstructor

Generuje konstruktor dla klasy, który przyjmuje jako argumenty odpowiednio: pola wymagane (prywatne i finalne), wszystkie pola, nie przyjmuje argumentów. Przykładowe użycie:

@AllArgsConstructor
public class Aplikacja {
    private String prywatnePole;
    private String publicznePole;
}

Ta adnotacja utworzy konstruktor przyjmujący dwa argumenty typu String, które zostaną przypisane do pól.

@Data

Adnotacja ta łączy w sobie kilka innych: @Getter, @Setter, @HashCodeAndEquals, @RequiredArgsConstructor oraz @ToString. Zapis:

@Data
public class Aplikacja {
    private String prywatnePole;
    private String publicznePole;
}

Jest więc równoważny z poniższym:

@Getter
@Setter
@HashCodeAndEquals
@RequiredArgsConstructor
@ToString
public class Aplikacja {
    private String prywatnePole;
    private String publicznePole;
}

@Log i podobne

Ta adnotacja pozwala dodać logger, jako pole o nazwie log. Do wyboru mamy najpopularniejsze biblioteki:

@Log4j2 – Log4j w wersji 2
@Slf4j – Slf4J
@CommonsLog – Apache Commons Logging

Uwaga – adnotacje te nie dodają odpowiednich bibliotek! Musisz sama dodać zależności do tych bibliotek.



		

Pozostałe adnotacje

Lombok oferuje także kilka innych, użytecznych adnotacji. @Builder czy @Synchronized pozwalają na uproszczenie kodu w specyficznych sytuacjach, ale ich zastosowanie jest bardziej specyficzne. Po szczegółowy opis każdej z nich odsyłamy do dokumentacji pod adresem https://projectlombok.org/features/index.html

Podsumowanie

Lombok jest naprawdę świetnym rozwiązaniem na ‚boilerplate code’ – wg nas zwiększa czytelność kodu i poprawia sensowność metryk takich jak np. pokrycie kodu. Zanim jednak użyjesz go w projekcie w pracy, upewnij się, że zespół nie ma nic przeciwko! Jeśli jednak do tej pory nie używaliście w zespole – może warto, żebyś zaproponowała?

  •  
  •  
  •  
  •  
  •  
  • Damian Nowak

    Nice ;-)

  • Marcinho92

    Przyda się :)

  • sedzisz

    Minusy:
    * Narzucamy komuś przymus instalowania wtyczek w IDE!
    * Czytelność?!?! proszę przykład mojej pola w encji

    @Setter
    @Getter

    @ElementCollection(targetClass = User.class, fetch = FetchType.EAGER)
    @CollectionTable(name = „CONTACT_USERS”, joinColumns = @JoinColumn(name = „contact_id”),
    foreignKey = @ForeignKey(name = „fk_contact_to_contact_users”))
    @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    private Set users = new HashSet();

    Więc ten argument jest moim zdaniem nie do końca trafiony.

    • Jeśli chodzi o narzucanie komuś instalowania wtyczek to jest to częściowo prawda – faktycznie korzystanie z Lomboka wymaga korzystania z wtyczki, ale jak w przypadku każdego narzędzia decyzja o jego użyciu powinna być decyzją zespołu a nie pojedynczych osób – nie można wtedy powiedzieć, że coś jest komuś narzucane, jeśli decyzja zapadła wspólnie.

      Z argumentem czytelności chyba nie bardzo rozumiem – poza dwoma adnotacjami, które zastępują getter i setter (i mogą być na poziomie klasy), pozostałe adnotacje nie mają związku z lombokiem – jakie mają one znaczenie w kontekście czytelności samego Lombok’a?

      • sedzisz

        Ponoć adnotacje są jak łupież :)
        Moja uwaga po prostu robi się tego aż nadto za dużo i zaciemnia kod „właściwy” imo!

        • To zarówno prawa, jak i nieprawda ;) Jeśli aplikacja jest podzielona na warstwy, a biblioteki i narzędzia dobierane z uwagą i myśląc w przód, to nie będzie to problemem. Czasem jednak faktycznie nadmiar adnotacji może być mniej czytelny niż byśmy tego chcieli

      • sedzisz

        Czytelności całego kodu, nie adnotacji lomboka. Niech do powyższego przykładu dojdą po dwie adnotacje walidatora hibernate i mamy 7 adnotacji do jednej linijki właściwego kodu. Czytanie takiego kodu już się robi mało przyjemne i tak naprawdę to był główny powód dlaczego schemat koloru w moim IDE lekko tonuje wyrazistość adnotacji.

        Cały zespół zdecyduje że chce go, rozradowane gremium zacznie korzystać. Po pewnym czasie idzie do innego projektu jednak ten jeszcze jest wspierany przez „kogoś” -> Czy pytał ktoś „kogoś” czy chce ? :) Mnie się nikt nie pytał warto wspomnieć że pierwszy mój kontakt był równie entuzjastyczny.
        Osobiście nie spotkałem się jeszcze nigdy z wykorzystaniem lomboka w projektach dostępnych na github-ie ze źródłami.

        PS. To nie jest hejt bardzo lubię ten blog ;)

        • Co do adnotacji ogólnie – oczywiście może być ich wiele, ale mając to na uwadze rozważ alternatywę – zewnętrzny plik XML’a a pół kartki A4 ;) Myślę, że nie ma tutaj idealnego rozwiązania, ale w ogólnym przypadku adnotacje nadal są czytelniejsze od konfigurowania rzeczy w ‚czystej’ Javie.
          Co do projektów na GitHubie faktycznie raczej nie spotkasz takich, projekty open source rządzą się nieco innymi prawami niż komercyjne. W firmach jednak, przynajmniej tam, gdzie było dane mi pracować, Lombok jest dość popularnym ‚przyspieszaczem’ pracy.
          Problem o którym wspomniałes oczywiście może mieć miejsce – wszystko zależy od zespołu i sposobu jego pracy. Lombok nie jest panaceum na wszystko, ale zdecydowanie narzędziem wartym rozważenia ;)

          • sedzisz

            Żywię szczerą niechęć do XML’i i a szczególnie w Springu i jak tylko mogę przepisuję to na konfigurację Javo’wą, która jak wspominasz jest dużo łatwiejsza w czytaniu oraz utrzymaniu poza innymi zaletami.

            Jednak nie do końca rozumie co ma na myśli pisząc abym rozważył XML adnotacje ?

            U mnie w pracy był bom na lomboka zachwyt jednak jak widzę wycofują się z niego okrakiem, zważywszy że IDE za nas może wygenerować gettery i settery a equals zwykle jest dostosowywany do potrzeb. Dla mnie lombok jest ciekawostką :)

          • Nie ma czegoś takiego jak adnotacje XML – wspomniałem, że czasem warto rozważyć wszystkie opcje, w tym XML, jak i adnotacje Javowe.

            Opisywane przez Ciebie doświadczenia są oczywiście wartościową informacją – czasem dane narzędzie po prostu ‚nie pasuje’ zespołowi. Często logika aplikacji jest też zaszywana w zapytaniach SQL, przez co obiekty stają się właściwie tylko transferowymi. W zespole, w których miałem okazję pracować, Lombok zaoszczędził nam bardzo wielu problemów – mieliśmy kilka trudnych do ‚wychwycenia’ problemów związanych z tym, że ktoś dodał pole, a nie zaktualizował metody equals, zapominaliśmy o toString (przez co logi znacznie traciły na wartości) – Lombok okazał się w tej sytuacji idealny, bo nie musieliśmy się tym więcej przejmować. Oczywiście IDE także może wygenerować je za Ciebie, możesz użyć Spring Roo czy dowolnego innego narzędzia, które zrobi to samo, możesz też pisać je ręcznie – każda z tych metod jest na tyle dobra, na ile przynosi korzyści zespołowi, i żadna nie jest lepsza od innej ‚z założenia’.

  • Jakub

    Widzę następujące problemy w tym krótkim opisie:

    1. Lombok dalej nie rozwiązuje problemu który w każdym projekcie się pojawia, mianowicie Mutability. Pisanie kodu z setterami to dla mnie pierwsza oznaka problemu który w końcu się pojawi w projekcie. Koszt naprawy takiego błędu będzie wtedy ogromny.
    2. Ilość adnotacji które trzeba dodać jest zdecydowanie zbyt duża
    3. Pisanie kodu z polskimi nazwami zmiennych jest mało profesjonalne, nawet jeżeli jest to tylko blog należy dbać o dobre praktyki. Potem do zespołu dołącza jakiś Junior co z blogów się uczył i dostajemy kwiatki w kodzie w czasie Code Review.

    Co do samego Lomboka, jeżeli ktoś potrzebuje tylko i wyłącznie generowanie metod equals, hashCode, toString itp to polecam Auto-Value https://github.com/google/auto/tree/master/value

    Zalety:
    -możliwość tworzenia własnych rozszerzeń do silnika.
    -testy, testy, testy – nie musimy pisać już więcej testów dla własnych equals i hashCode
    -nie potrzeba żadnych wtyczek, kod jest automatycznie wykrywany
    -Immutability obiektów – koniec race condition

    Wady:
    -ograniczona ilość adnotacji (patrz pisanie własnych rozszerzeń)

    I jeszcze jedna rzecz, auto-value działa w czasie kompilacji. Jedyne co potrzeba żeby użyć nowej klasy to przebudować projekt.

    • Cześć,
      dzięki za uwagi! Co do dobrych praktyk to jak najbardziej należy się do nich stosować (w najbliższym czasie pojawi się też wpis na ten temat na blogu!), ale jak ze wszystkim nie można przesadzać. Pewne rzeczy łatwiej wytłumaczyć stosując określenia z języka polskiego – nie wydaje mi się jednak żeby ktoś przez to miał problemy z współpracą w zespole, w końcu przychodząc do projektu pewne rzeczy po prostu muszą być przekazane :)
      Co do problemu z mutability, nie do końca się zgodzę – Lombok nie ma go rozwiązywać (a jeśli potrzebujesz obiektów immutable, to wystarczy deklaracje pól poprzedzić słówkiem ‚final’ lub skonfigurować gettery tak, żeby były prywatne). W przynajmniej części przypadków obiekty jednak muszą być mutable – choćby przez to, że niektóre biblioteki mapujące wymagają setterów do prawidłowej (i optymalnej) pracy.

      Lombok, podobnie jak auto-value, także działa w czasie kompilacji. Jego przewagą jest jednak popularność, dzięki czemu większość narzędzi go ‚rozumie’ (np. Sonar Qube; przy użyciu Mavena można wygenerować kod jeszcze przed kompilacją, jeśli jest to potrzebne np. do obliczania code coverage czy mapowania kodu). Dodatkowo każda zmiana nie wymaga ‚przebudowania’ projektu (co w dużych projektach może trwać po kilka-kilkanaście minut) i ponieważ działa jako Java-agent, jest zgodny z dowolnym systemem budowania, nie tylko Maven i Gradle. Choć faktycznie nie wspiera własnych rozszerzeń na ten moment, nie wydają się one potrzebne – nie spotkałem się z przypadkiem, kiedy Lombokowi czegoś brakowało. Są to po prostu różne narzędzia do różnych celów :)