Wydaje mi się, że to dość powszechny problem, z którym w pewnym momencie spotykają się programiści od młodszych do średniozaawansowanych: albo nie znają kontraktów, w których uczestniczą, ani nie ufają im, i defensywnie sprawdzają zerowe wartości. Dodatkowo, podczas pisania własnego kodu, zwykle polegają na zwracaniu wartości null w celu wskazania czegoś, co wymaga od dzwoniącego sprawdzenia wartości null.
Innymi słowy, istnieją dwa przypadki, w których pojawia się sprawdzanie wartości NULL:
Gdzie null jest prawidłową odpowiedzią w odniesieniu do umowy; i
Gdzie nie jest to prawidłowa odpowiedź.
(2) jest łatwe. Użyj assert
instrukcji (twierdzeń) lub zezwól na niepowodzenie (na przykład NullPointerException ). Asercje to wysoce niewykorzystana funkcja Java, która została dodana w wersji 1.4. Składnia jest następująca:
assert <condition>
lub
assert <condition> : <object>
gdzie <condition>
jest wyrażeniem logicznym i <object>
jest obiektem, którego wynik toString()
metody zostanie uwzględniony w błędzie.
assert
Oświadczenie wyrzuca Error
( AssertionError
) jeśli warunek nie jest prawdą. Domyślnie Java ignoruje twierdzenia. Można włączyć asercje, przekazując opcję -ea
do JVM. Możesz włączać i wyłączać asercje dla poszczególnych klas i pakietów. Oznacza to, że możesz sprawdzać poprawność kodu z asercjami podczas opracowywania i testowania oraz wyłączać je w środowisku produkcyjnym, chociaż moje testy wykazały prawie żaden wpływ na wydajność z asercji.
Nieużywanie asercji w tym przypadku jest OK, ponieważ kod po prostu się nie powiedzie, co stanie się, jeśli użyjesz asercji. Jedyną różnicą jest to, że w przypadku stwierdzeń może się to zdarzyć wcześniej, w bardziej znaczący sposób i być może z dodatkowymi informacjami, które mogą pomóc ci dowiedzieć się, dlaczego tak się stało, jeśli się tego nie spodziewałeś.
(1) jest trochę trudniejszy. Jeśli nie masz kontroli nad kodem, który dzwonisz, utkniesz. Jeśli null jest prawidłową odpowiedzią, musisz ją sprawdzić.
Jeśli jednak kontrolujesz kod (i często tak się dzieje), to inna historia. Unikaj używania wartości null jako odpowiedzi. Dzięki metodom, które zwracają kolekcje, jest to łatwe: zwracaj puste kolekcje (lub tablice) zamiast prawie zer.
W przypadku braku kolekcji może być trudniej. Rozważ to jako przykład: jeśli masz te interfejsy:
public interface Action {
void doSomething();
}
public interface Parser {
Action findAction(String userInput);
}
gdzie Parser pobiera dane wejściowe od użytkownika i znajduje coś do zrobienia, być może, jeśli implementujesz interfejs wiersza poleceń. Teraz możesz sprawić, że umowa zwróci wartość zerową, jeśli nie zostaną podjęte odpowiednie działania. To prowadzi do sprawdzenia zerowego, o którym mówisz.
Alternatywnym rozwiązaniem jest nigdy nie zwracać wartości null, a zamiast tego używać wzorca Null Object :
public class MyParser implements Parser {
private static Action DO_NOTHING = new Action() {
public void doSomething() { /* do nothing */ }
};
public Action findAction(String userInput) {
// ...
if ( /* we can't find any actions */ ) {
return DO_NOTHING;
}
}
}
Porównać:
Parser parser = ParserFactory.getParser();
if (parser == null) {
// now what?
// this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
// do nothing
} else {
action.doSomething();
}
do
ParserFactory.getParser().findAction(someInput).doSomething();
co jest znacznie lepszym projektem, ponieważ prowadzi do bardziej zwięzłego kodu.
To powiedziawszy, być może jest całkowicie właściwe, aby metoda findAction () zgłosiła wyjątek z sensownym komunikatem o błędzie - szczególnie w tym przypadku, gdy polegasz na danych wejściowych użytkownika. O wiele lepiej byłoby, gdyby metoda findAction zgłosiła wyjątek, niż metoda wywołująca wysadzenie prostego wyjątku NullPointerException bez wyjaśnienia.
try {
ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
userConsole.err(anfe.getMessage());
}
Lub jeśli uważasz, że mechanizm try / catch jest zbyt brzydki, zamiast nic nie rób, domyślna akcja powinna dostarczyć użytkownikowi informacji zwrotnej.
public Action findAction(final String userInput) {
/* Code to return requested Action if found */
return new Action() {
public void doSomething() {
userConsole.err("Action not found: " + userInput);
}
}
}