Pobieranie wartości bez konieczności zerowania w Javie

15

Wiele razy sprawdzam, czy sprawdzam wartość zerową podczas pobierania wartości z jakiejś hierarchii danych, aby uniknąć wyjątków NullPointerExcept, które uważam za podatne na błędy i wymagają dużo dodatkowej analizy.

Napisałem bardzo prostą procedurę, która pozwala mi pominąć sprawdzanie wartości zerowej podczas pobierania obiektu ...

public final class NoNPE {

    public static <T> T get(NoNPEInterface<T> in) {
        try {
            return in.get();
        } catch (NullPointerException e) {
            return null;
        }
    }

    public interface NoNPEInterface<T> {
        T get();
    }
}

Używam tego trochę tak ...

Room room = NoNPE.get(() -> country.getTown().getHouses().get(0).getLivingRoom());

Powyższe skutkuje otrzymaniem obiektu pokoju lub wartości zerowej, bez konieczności sprawdzania wszystkich poziomów nadrzędnych.

Co sądzisz o powyższym? Czy tworzę problematyczny wzór? Czy Twoim zdaniem jest na to lepszy sposób?

Eurig Jones
źródło
1
Ponieważ najwyraźniej używasz języka Java 8, czy mogę zasugerować, aby rozważyć zmianę projektu aplikacji w celu użycia java.util.Optionalzamiast wartości zerowych w celu reprezentowania brakujących danych? Zapewnia to przydatne narzędzia zarówno dla opisywanego przypadku, jak i przypadków, w których chciałbyś nadal korzystać z danych domyślnych, a nie tylko zwracać stan awarii na końcu łańcucha.
Periata Breatta
Myślę, że zasadniczo odkryłeś Option(lub Maybe) monadę :)
Andres F.
Może być możliwe zwrócenie Opcjonalne zamiast T lub null: w ten sposób można bezpośrednio użyć metody orElse (). 18 miesięcy później, ale może komuś pomóc.
Benj
W tym poście wspomniano o innych podejściach: nielegalnyargumentexception.blogspot.com/2015/03/... , jedno z nich korzysta z biblioteki o nazwie kludje, która ma bardzo interesującą składnię
Benj

Odpowiedzi:

13

Twoje rozwiązanie jest bardzo inteligentne. Problem, który widzę, polega na tym, że nie wiesz, dlaczego masz null? Czy to dlatego, że dom nie miał pokoi? Czy to dlatego, że miasto nie ma domów? Czy to dlatego, że kraj nie miał miast? Czy to dlatego, że byłnull kolekcja w pozycji 0 z powodu błędu, nawet jeśli domy znajdują się w pozycjach 1 i wyższych?

Jeśli użyjesz NonPEklasy extensibe , będziesz mieć poważne problemy z debugowaniem. Myślę, że lepiej wiedzieć, gdzie dokładnie łańcuch jest zerwany, niż po cichu zdobyćnull co może kryć głębszy błąd.

Narusza to również prawo Demeter :country.getTown().getHouses().get(0).getLivingRoom() . Częściej niż nie, naruszenie jakiejś dobrej zasady powoduje, że musisz wdrożyć niekonwencjonalne rozwiązania, aby rozwiązać problem spowodowany naruszeniem tej zasady.

Radzę , abyś używał go ostrożnie i starał się rozwiązać usterkę projektową, która powoduje, że musisz ponieść atak na wrak pociągu (abyś nie musiał używać NonPEwszędzie). W przeciwnym razie możesz mieć błędy, które będą trudne do wykrycia.

Tulains Córdova
źródło
Świetna odpowiedź. Tak, nie będę wiedział, gdzie mam wartość zerową w łańcuchu. W wielu przypadkach jednak mnie to nie obchodzi i nie muszę zerować, co oznacza, że ​​kod jest bardziej czytelny i mniej podatny na błędy na tablicy. Ale tak, masz rację w niektórych przypadkach, gdy muszę podjąć inną logiczną decyzję, jeśli obiekt nadrzędny ma wartość NULL, spowoduje to problem. Metoda konwencjonalna lub klasa opcjonalna może być bezpieczniejszym rozwiązaniem.
Eurig Jones
Zasadniczo podczas korzystania z Optionmonady nie ma znaczenia, gdzie w łańcuchu znajduje się nieobecna wartość. Kiedy dbasz o to, prawdopodobnie użyłbyś innego typu, np Either.
Andres F.,
Podejście PO jest podobne do C # 6 ?.i ?[]operatorów. Jednym z przykładów, kiedy możesz chcieć użyć takiej rzeczy, są hierarchiczne ustawienia po stronie serwera. var shouldDoThing = settings?.a?.b?.c ?? defaultSetting;Kogo obchodzi, dlaczego jakakolwiek jego część była zerowa? Może nie można pobrać ustawień. Być może zdecydowałeś się usunąć część ustawień. W każdym razie nigdy tak naprawdę nie możesz liczyć na uzyskanie ustawień serwera, więc ustawienie domyślne jest zwykle dobrym pomysłem i nie jest prawdopodobne, że przejmujesz się, dlaczego nie udało ci się uzyskać rzeczywistych ustawień, chyba że zdarza się to bardzo często, gdy nie powinno .
Chris
Teraz nie mówię, że jest to zdecydowanie lepsze lub gorsze niż zlokalizowanie ustawień domyślnych i po prostu odzyskanie pożądanej wartości przez normalne dostępy settings.a.b.c. Z drugiej strony jest to pojedynczy izolowany przykład.
Chris
10

Pomysł jest w porządku, naprawdę dobry. Ponieważ Optionaltypy Java 8 istnieją, szczegółowe objaśnienie znajduje się w części Opcjonalne Java . Przykładem tego, co opublikowałeś, jest

Optional.ofNullable(country)
    .map(Country::getTown)
    .map(Town::Houses);

I dalej.

J. Pichardo
źródło
1
Tak, byłem świadomy klasy Opcjonalnej, zarówno z Java 8, jak i Guava i są one bardzo przydatne. Ale nie możesz po prostu pobrać obiektu, ponieważ normalnie kod byłby nieco trudniejszy do odczytania i nieco mniej wydajny. Ale zaletą jest to, że istnieje wiele bardzo przydatnych operatorów, które zapewnia klasa Optional.
Eurig Jones
3
@EurigJones Nie sądzę, aby kod stał się mniej wydajny. Czytelność w oczach patrzącego, ale twierdzę, że Optionaljest to bardziej czytelne rozwiązanie, choćby dlatego, że - w przeciwieństwie do twojej propozycji - jest to bardzo powszechny idiom. Jest jeszcze bardziej zwięzły niż twój!
Andres F.,
0

Twoja metoda działa wystarczająco dobrze, zgodnie z jej przeznaczeniem, choć zwraca nulls, gdy NullPointerExceptionbrzmi jak zły projekt.

Staraj się unikać nulls, kiedy możesz, i mijaj je tylko wtedy, gdy coś reprezentują lub mają specjalne znaczenie i zwracaj je tylko wtedy, gdy coś reprezentują / znaczą coś - w przeciwnym razie powinieneś rzucić NullPointerException. Pozwala to uniknąć błędów i zamieszania. Jeśli Objectnie powinno być null, NullPointernależy rzucić. Jeśli obiekt może być, nullwtedy nic nie pójdzie źle po przekazaniu go. W przeciwnym razie powyższa metoda działa.

Luke Melaia
źródło
0

Czuję twój ból, ale proponowane rozwiązanie to zły pomysł.

  • Jeśli jeden z pobierających rzuci NPE z innego powodu, zignorujesz go.
  • Istnieje ryzyko, że wewnętrzna lambda przerodzi się w okropny kod. Na przykład, jeśli istnieje nowy wymóg zwrócenia specjalnej stałej, gdy w mieście nie ma domów, leniwy programista może przedłużyć lamdę, pozostawiając wszystko owinięte NoNPE.get.
  • Jak już wspomniano, Optional.mapwłaśnie tego szukasz.
  • Kara za utworzenie nowej instancji NullPointerException jest często znacząca. To wiele mikrosekund, zwłaszcza gdy Twój stos połączeń staje się większy. Trudno przewidzieć, gdzie ostatecznie zostanie użyte Twoje narzędzie.

Na marginesie, NoNPEInterfacejest duplikatem java.util.function.Supplier.

W niektórych przypadkach możesz rozważyć użycie narzędzi do oceny wyrażeń, które są obecne w wielu ramach (na przykład: EL, SpEL):

evaluateProperty(country, "town.houses[0].livingRoom")
Mateusz Stefek
źródło
Ok dla szablonów stron internetowych, ale generalnie wolno się rozwija (bez sprawdzania czasu kompilacji) i wolno się uruchamia.
kevin cline