Czy parametry oznaczone adnotacją @Nonnull dla null?

19

Zaczęliśmy używać FindBugs i odpowiednio dodawać adnotacje do naszych parametrów @Nonnull, i świetnie jest wskazywać błędy na wczesnym etapie cyklu. Do tej pory sprawdzaliśmy te argumenty za nullużyciem Guawy checkNotNull, ale wolałbym sprawdzać nulltylko na krawędziach - miejscach, w których wartość może przyjść bez sprawdzania null, np. Żądania SOAP.

// service layer accessible from outside
public Person createPerson(@CheckForNull String name) {
    return new Person(Preconditions.checkNotNull(name));
}

...

// internal constructor accessed only by the service layer
public Person(@Nonnull String name) {
    this.name = Preconditions.checkNotNull(name);  // remove this check?
}

Rozumiem, że same wartości @Nonnullnie są blokowane null.

Biorąc jednak pod uwagę, że FindBugs będzie wskazywał, gdziekolwiek wartość jest przenoszona z pola nieoznaczonego do pola oznaczonego @Nonnull, nie możemy polegać na tym, aby wychwytywać te przypadki (co robi) bez konieczności sprawdzania tych wartości w nullkażdym miejscu, w którym są przekazywane system? Czy naiwnie chcę zaufać narzędziu i unikać pełnych kontroli?

Konkluzja: Chociaż usunięcie drugiej nullkontroli poniżej wydaje się bezpieczne, czy jest to zła praktyka?

To pytanie jest być może zbyt podobne do opcji Należy sprawdzić, czy nie ma wartości null , jeśli nie spodziewa się wartości null , ale pytam konkretnie w odniesieniu do @Nonnulladnotacji.

David Harkness
źródło

Odpowiedzi:

6

Jeśli nie ufasz FindBug, może być możliwe użycie asercji. W ten sposób unikniesz problemów wymienionych przez użytkownika MSalters, ale nadal sprawdzisz. Oczywiście to podejście może nie mieć zastosowania, jeśli takie szybkie podejście nie jest możliwe, tj. Musisz złapać jakiś wyjątek.

I dalszą odpowiedź na pytanie, czy jest to zła praktyka. Cóż, jest to dyskusyjne, ale nie sądzę. Uważam tę adnotację FindBug za lekką specyfikację zgodną z zasadą projektowania na podstawie umowy.

Projektując na podstawie umowy, zawierasz umowę między metodą a wywołującym. Umowa składa się z warunków wstępnych, które osoba dzwoniąca zobowiązuje się spełnić podczas wywoływania metody, oraz warunek końcowy, który z kolei jest spełniony przez metodę po wykonaniu, jeśli warunek ten jest spełniony.

Jedną z zalet tej metody programowania jest to, że można uniknąć tak zwanego defensywnego stylu programowania (w którym jawnie sprawdzane są wszystkie nieprawidłowe wartości parametrów itp.). Zamiast tego możesz polegać na umowie i unikać zbędnych kontroli w celu zwiększenia wydajności i czytelności.

Kontrakty mogą być używane do sprawdzania asercji w czasie wykonywania, która jest zgodna z wyżej wymienioną zasadą pierwszego niepowodzenia, w której chcesz, aby Twój program zawiódł w przypadku zerwania umowy, dając ci wskazówkę, aby zidentyfikować źródło błędu. (Nieudany warunek wstępny oznacza, że ​​program wywołujący zrobił coś złego, nieudany warunek końcowy oznacza błąd implementacji danej metody)

Ponadto kontrakty mogą być wykorzystywane do analizy statycznej, która próbuje wyciągać wnioski na temat kodu źródłowego bez jego uruchamiania.

Adnotacja @nonnull może być postrzegana jako warunek wstępny używany do analizy statycznej przez narzędzie FindBugs. Jeśli wolisz takie podejście, jak sprawdzanie asercji w czasie wykonywania, tj. Strategię pierwszego niepowodzenia, powinieneś rozważyć użycie instrukcji asercji jako wbudowanej Java. A może dostosować się do bardziej wyrafinowanej strategii specyfikacji przy użyciu języka specyfikacji interfejsu behawioralnego, takiego jak Java Modeling Language (JML).

JML jest rozszerzeniem języka Java, w którym specyfikacja jest osadzona w specjalnych komentarzach Java. Zaletą tego podejścia jest to, że można użyć wyrafinowanej specyfikacji, nawet przy użyciu podejścia programistycznego, w którym polegasz tylko na specyfikacji, a nie na szczegółach implementacji i używasz różnych narzędzi korzystających ze specyfikacji, takich jak wspomniane narzędzia do sprawdzania asercji, automatyczne generowanie testów jednostkowych lub dokumentacji.

Jeśli podzielasz mój punkt widzenia, że ​​@nonnull jest (lekką) umową, powszechną praktyką byłoby nie dodawanie dodatkowych kontroli, ponieważ pierwotną ideą stojącą za tą zasadą jest unikanie programowania obronnego.

FabianB
źródło
2
Właśnie tego używam @Nonnull: jako specyfikacji kontraktu. Usuwam kontrole obronne wynikające z kontraktu i do tej pory nie miałem żadnych problemów.
David Harkness
4

Zastanów się, dlaczego nie piszesz

this.name = Preconditions.checkNotNull(name);  // Might be necessary
that.name = Preconditions.checkNotNull(this.name);  // Who knows?

Programowanie nie jest czarną magią. Zmienne nie stają się nagle zerowe. Jeśli wiesz, że zmienna nie ma wartości Null i wiesz, że nie została zmieniona, nie sprawdzaj jej.

Jeśli to zrobisz, jakiś programista w przyszłości (być może ty) poświęci dużo czasu, aby dowiedzieć się, dlaczego ta kontrola jest dostępna. W końcu czek musi być z jakiegoś powodu, więc czego przeoczysz?

MSalters
źródło
3
Do @Nonnullprzewiduje umowa, że dzwoniący nie musi przechodzić nulldo konstruktora i FindBugs wykorzystuje analizę statyczną zlokalizować połączenia, które może pozwolą null--specifically połączeń, które przechodzą wartości nieoznaczone @Nonnullsiebie. Ale dzwoniący może przejść nulli zignorować ostrzeżenie FindBugs.
David Harkness
Programowanie idealnie nie jest czarną magią. Niestety, jest mnóstwo ostrzeżeń i niespodzianek, zwłaszcza podczas pracy z niektórymi kodami współbieżności ♬ ~ ᕕ (ᐛ) ᕗ
Tim Harper