Walidowanie anotacjami - zło konieczne?

0

Witam. Ostatnio uczęszczam na spotkania JVMowe i sporo się mówi o tym, żeby nie wrzucać wszędzie adnotacji. Tymczasem przychodzę sobie do pracy a tam w każdej encji adnotacje walidujące pola oraz na metodach weryfikujące parametry - seniory podają powód, że to pięknie oddzielna biznesową i walidacyjną logikę, sprawiając, że kod jest czystszy.

No tylko, że według mnie to utrudnia sprawy i do tego wygląda to przerażająco (po LJUG/Confitura mam małpofobie@). Powinno się tak robić, czy jest może jakaś przyjaźniejsza alternatywa? Taki naive approach to walidowanie funkcjami które siedzą w jakiejś klasie NazwaKlasyValidator, ale wtedy trzeba to wywoływać w kodzie biznesowym i robią się kluchy.

Wzywam maga @jarekr000000 :D

EDIT: Chodzi tu o customowe validatory

0

Pomijajac emocje w tę czy tamtą stronę, adnotacje / atrybuty C# są niezłą formą podawania METADANYCH do danych.
A opisy pól pod względem walidacji (czy labeli/captionów w chrześcijańskim języku, jakie dawane są userowi) to właśnie metadane. *)
Czy (również zygam do @jarekr000000 ) do tego miejsca jest zgoda?

Owszem takie adnotacje-metadane nadal coś musi zinterpretować, ale to jest prosta lub "prosta" libka walidacyjna (z elementami UI)

Kontrowersje są, jak adnotacje (rzadziej atrybuty C#, a moze tam mniej nerwowi ludzie ????) mają zastąpić aktywny kod.
Staje się to magiczne, nietestowalne, puchnące z każdym wydaniem kontenery które tą magie robią, które 100 ludzi na świecie w pełni rozumie (jak Teorię Względnosci) itd...

*) z czym niekoniecznie jest zbieżne konwertowanie encji / dto / itd na granicy warstw coraz to do nowych klas, w tym przebitka na TS/JS

1

Ja nie rozumiem tego argumentu z tym oddzielaniem logiki od walidacji - albo walidacja jest biznesowa i walidujemy tam gdzie jest kod biznesowy czyli logika, albo walidacja nie jest biznesowa i walidujemy to gdzieś wyżej. To i to można zrobić za pomocą adnotacji lub bez, jeden rabin powie tak drugi tak, ja nie korzystam w tym celu z adnotacji bo wole czytać kod Javy - a w niektórych przypadkach przy złożonej logice walidacji chcę móc skorzystać z DI.

Adnotacje w stylu tych z javax.validation.constraints na obiektach latających po całej domenie to jest właśnie sprzęganie logiki z walidacją a nie oddzielanie jej, bo później jakiś serwis z logiką operuję na tych polach oznaczonych jakimś @AssertTrue albo @Size

2

Zawsze można zmienić piec język programowania i już nie mieć adnotacji (co ja uczyniłem ponad rok temu).

Co do samego pytania to oczywiście można, ale to jak pisanie w Javie bez springa, a bez getterozy i setterozy. Jeden projekt na dziesięć taki znajdziesz że nie będą tego robić (wiem bo szukałem i tylko w takich starałem się pracować przez 9 lat bycia Javowcem). Ale IMHO ostatecznie się kończy to tak że miłośnicy adnotacji, springa i getterozy z setterozą zostają w javie a przeciwnych migrują do innych języków

3

Ja nie mam problemu z adnotacjami, tylko z tym co one robią np. w Javie, w większości frameworków. Przykład oderwany z od konkretnych bibliotek:

Załóżmy, że mamy klasę:

public class Person(){
  private int age;
  public int getAge(){
    return age;
  }

  public setAge(int age){
    if(age<0 || age > 140){
      throw new DataValidationException("Age out of limit");
    }
    this.age = age;
  }
}

Teraz jakiś spryciarz stwierdził, że napisze do tego framework do walidacji i w rezultacie może być tak:

public class Person(){
  private int age;
  
  public int getAge(){
    return age;
  }

  public setAge(@ValidateRange(0, 140, "Age out of range") int age){
    this.age = age;
  }
}

Powiedzmy, że czytelniej.
Teraz piszę sobie test:

person = new Person();
expect(DataValidationException.class person.setAge(-1))

I test się sypie, bo logika adnotacji nie miała szansy zadziałać.
Żeby mogła zadziałać test powinien wyglądać tak:

person = ValidationProxy.wrap(new Person());
expect(DataValidationException.class person.setAge(-1))

Czyli obiekt musi zostać przetworzony, zapakowany w DynamicProxy, które ten obiekt owinie, sprawdzi, czy przypadkiem nie ma na nim jakiejś adnotacji i przepuszczając przez siebie zapytanie do owiniętego obiektu sprawdzi wcześniej, czy parametr metody jest zgodny z atrybutami zawartymi w adnotacji.

Jednocześnie adnotacja nie zadziała w takim przypadku:

public class Person(){
  private int age;
  
  public int getAge(){
    return age;
  }

  public setAge(@ValidateRange(0, 140, "Age out of range") int age){
    this.age = age;
  }

  public doSomething(){
    setAge(-1)
  }
}

Bo doSomething() ominie proxy.

Nie każda biblioteka używająca adnotacji używa też DynamicProxy, można te adnotacje obsługiwać na etapie prekompilacji (przynajmniej w Javie) i wygenerować dodatkowy kod. Tylko obsługę w taki sposób ciężej wykonać i na ogół trochę trudniej sie tego użya, więc przeważająca większość adnotacji jest obsługiwana w trybie runtime, z ostrym rzeźbieniem refleksjami po wszystkim co wpadnie w łapy, łącznie z takimi magiami jak zmiana dostępności prywatnego pola na publiczne i wstawienie tam jakiejś wartości z pominięciem interface'u klasy.

Czyli tak - uważam, ze część adnotacji to zło, część bardzo popularnych frameworków to również zło, a jest na rynku od groma narzędzi, które próbują tworzyć jakieś ~DSLe tam gdzie jest to kompletnie zbędne i nie rozwiązuje żadnych problemów.

6

Nie wszystkie adnotacje to z zło.
Nie wszystkie adnotacje runtime to czyste zło - czasem to zło konieczne, bo java obsysa i nie ma za bardzo sensownej refleksji compile time.
Przykład jackson to biblioteka, która cała jest oparta na adnotacjach (i konwencji), ale działa przewidywalnie i bezproblemowo.
Chyba głównie dlatego, że core nie ma nic wspólnego z JavaEE, Springiem i Beanami.

Niestety javax.validation i springowy "wrapper" cierpi na wspomniane problemy z dynamic proxy (i nie tylko) - przez to dość często (z mojego doświadczenia) powoduje zaskakujące problemy.
Ratowałem nawet jeden projekt, który (między innymi) utkwił przez validation (konflikt jarów od jsr303, ale tylko na produkcji ... ).

Co do samej walidacji - to jest ona chyba wszędzie (inne języki, frameworki) dość żmudna. Prawdziwa logika biznesowa jest nudna.

! Walidacja zwykle tak naprawdę pokazuje, że mamy do czynienia z nowym typem.
Czyli zamiast robienia Stringa, który jest PESELem i karkołomnego opisywania tego walidacjami, które i tak padnie, bo ktoś kiedyś użyje jakiegoś setera, warto mieć typ PESEL, którego obiektów nie da się stworzyć "żle". Make invalid states unrepresentable

To samo dotyczy Nazwiska, które nie może być dłuższe niż 30 znaków i nie może zawierać apostrofów, bo nie obsługujemy arystokracji i francuzów. To powinien być specjalny typ, a nie annotowany string (znowu - żeby zło, w postaci niepoprawnego Stringa/Nazwiska się nie prześliznęło).

Sama konwersja ze Stringa na Pesel itd. może już zwracać https://www.javadoc.io/doc/io.vavr/vavr/0.9.3/io/vavr/control/Validation.html i w ten sposób można robić sensownie* całe łańcuchy walidacji, gdzie na koniec będziemy mieli listę errorów (lub graf poprawnych obiektów) - do ewentualnego przedstawienia użyszkodnikowi.

* sensownie to modulo składnia javy, która flatMapów nie lubi.

7

Najlepsza libka, jaką znam w JVM do walidacji to https://yavi.ik.am/. Polecam.
Dodatkowo polecam nie rzucać wyjątkami, gdy email ma zły format. Tylko raczej zwracać błąd w jakimś Either.

Rzucanie wyjątkiem śmierdzi. Budować stacktrace, i rozpraszać JIT tylko po to, żeby wyświetlić powód błędu w odpowiedzi - dajcie spokój.

1
jarekr000000 napisał(a):

Nie wszystkie adnotacje to z zło.

Co do samej walidacji - to jest ona chyba wszędzie (inne języki, frameworki) dość żmudna. Prawdziwa logika biznesowa jest nudna.

Dla tych, co mówią że Java jest be i nadmiernie "verbose".
Java, refleksja (kolejny z wrogów, prawda?) mądrze użyta jest całkiem OK. Wystarczy co jakiś czas w C/C++ zrobić "refleksję dla ubogich" / "walidację dla ubogich" aby to docenić *)

https://4programmers.net/Forum/C_i_C++/362046-dynamiczne_tworzenie_gui_do_edycji_struktur_jak_zadeklarowac_walidacje_danych?p=1854607#id1854607

*) w czasach C++ Burdela to robiłem, never again

3

@ZrobieDobrze: Java w części zastosowań była ogromnym postępem w stosunku do C++. Nawet to "verbose" o którym piszesz miało swoje zalety dla każdego kto zetknął się z jakimś absurdalnym wykorzystaniem wskaźników. Refleksja, jak sam napisałeś "mądrze wykorzystana" - czemu nie. Tylko w sensownie pisanym kodzie bardzo rzadko istnieje konieczność użycia refleksji, bo ogólnie rzecz biorąc, w językach silnie typowanych wiesz co dostajesz, więc sprawdzanie co dostałeś rzadko kiedy ma sens.
Dla mnie problemem jest to, że naprawdę jest od groma bibliotek, które wnoszą niewiele, poza tym, że trzeba napisać jakąś adnotację zamiast napisania linijki kodu, albo prowokują do pisania klas, które nie mają prawa działać w "normalnej" Javie. Powszechny przykład to wstrzykiwanie zależności na polach, co skutkuje tym, że takiego obiektu nie da się stworzyć się zwykłym new Whatever(). Nie wiem jakie jest twoje podejście, ale ja dość religijnie traktuję zasadę, że wywołanie dowolnego konstruktora powinno zwracać gotowy do użycia obiekt.

0

Faktycznie, w porównaniu do C++ (które dopiero dorobi się refleksji) javowa Runtime Refleksja to był duży postęp.
Ale 20 lat to w programowaniu duży postęp.

W Scali mamy od dawna compile time reflection - makra od dawna są naprawę spoko, a w Scali 3 automatic class derivation wymiata.
W Haskellu tez jest nieźle (choć tu problemem jest mnogość rozwiązań - wychodzi eksperymentalność języka). Ale deriving jest naprawdę ok.

Compile time reflection, aż tak bardzo się nie różni (w sposobie kodowania), ale ma jedną przewagę - wiele błędów wyjdzie już na etapie kompilacji.
Np. jeśli wsadzimy do DTO pole typu Thread to już podczas komoilacji się dowiemy, że JSONa z tego nie będzie. Runtime refleksja jest też podatna na błedy związane z konfiguracją - coś zamieszamy z classpath, classloaderami i kod zaczyna się magicznie wywalać (randomowe NPE), i nawet nie wiadomo gdzie szukać przyczyny. Nawet zmiana JDK potrafi namieszać.

Tym niemniej ta refleksja nie jest aż tak tragiczna, jak jej nadużywanie do rzeczy, które można zupełnie dobrze zrobić bez refleksji (czyli cały spring praktycznie, szczególnie DI).

1 użytkowników online, w tym zalogowanych: 0, gości: 1