Zastanawiam się, czy powinienem bronić się przed wartością zwracaną wywołania metody, sprawdzając, czy spełniają one moje oczekiwania, nawet jeśli wiem, że metoda, którą wywołuję, spełni takie oczekiwania.
DANY
User getUser(Int id)
{
User temp = new User(id);
temp.setName("John");
return temp;
}
CZY POWINNAM
void myMethod()
{
User user = getUser(1234);
System.out.println(user.getName());
}
LUB
void myMethod()
{
User user = getUser(1234);
// Validating
Preconditions.checkNotNull(user, "User can not be null.");
Preconditions.checkNotNull(user.getName(), "User's name can not be null.");
System.out.println(user.getName());
}
Pytam o to na poziomie koncepcyjnym. Jeśli znam wewnętrzne działanie metody, którą wzywam. Albo dlatego, że to napisałem, albo sprawdziłem. A logika możliwych wartości, które zwraca, spełnia moje warunki wstępne. Czy „lepsze” lub „bardziej odpowiednie” jest pominięcie sprawdzania poprawności, czy powinienem nadal bronić się przed niewłaściwymi wartościami, zanim przejdę do metody, którą obecnie wdrażam, nawet jeśli zawsze powinna ona przejść.
Mój wniosek ze wszystkich odpowiedzi (nie wahaj się przyjść do swoich):
Podaj kiedy- W przeszłości metoda ta okazała się niewłaściwa
- Metoda pochodzi z niezaufanego źródła
- Metodę stosuje się z innych miejsc i nie określa ona wyraźnie warunków dodatkowych
- Metoda żyje blisko Ciebie (szczegóły znajdziesz w wybranej odpowiedzi)
- Metoda ta wyraźnie określa, że jest to umowa z czymś takim, jak odpowiedni dokument, bezpieczeństwo typu, test jednostkowy lub kontrola po spełnieniu warunku
- Wydajność ma kluczowe znaczenie (w takim przypadku aserowanie w trybie debugowania może działać jako podejście hybrydowe)
java
validation
null
return-type
defensive-programming
Didier A.
źródło
źródło
Null
)? Prawdopodobnie jest to równie problematyczne, jeśli nazwa to""
(nie null, ale pusty ciąg) lub"N/A"
. Albo możesz zaufać wynikowi, albo musisz być odpowiednio paranoikiem.Odpowiedzi:
Zależy to od prawdopodobieństwa zmiany getUser i myMethod, a co ważniejsze, od prawdopodobieństwa zmiany niezależnie od siebie .
Jeśli w jakiś sposób wiesz na pewno, że getUser nigdy, nigdy, nigdy, nigdy się nie zmieni w przyszłości, to tak, to strata czasu na sprawdzanie poprawności, tak samo jak marnowanie czasu na sprawdzanie poprawności, która
i
ma wartość 3 natychmiast poi = 3;
instrukcji. W rzeczywistości tego nie wiesz. Ale są pewne rzeczy, które wiesz:Czy te dwie funkcje „żyją razem”? Innymi słowy, czy mają te same osoby, które je utrzymują, czy są częścią tego samego pliku, a zatem czy prawdopodobnie same pozostaną „w synchronizacji”? W tym przypadku dodawanie kontroli sprawdzania poprawności jest prawdopodobnie przesadą, ponieważ oznacza to po prostu więcej kodu, który musi się zmienić (i potencjalnie spawnować błędy) za każdym razem, gdy obie funkcje zmienią się lub zostaną przekształcone w inną liczbę funkcji.
Czy funkcja getUser jest częścią udokumentowanego API z określoną umową, a myMethod jest jedynie klientem wspomnianego API w innej bazie kodu? Jeśli tak, możesz przeczytać tę dokumentację, aby dowiedzieć się, czy powinieneś sprawdzać poprawność zwracanych wartości (lub wstępnie sprawdzać parametry wejściowe!), Czy też naprawdę bezpiecznie jest ślepo podążać szczęśliwą ścieżką. Jeśli dokumentacja tego nie wyjaśnia, poproś opiekuna o naprawienie tego.
Wreszcie, jeśli ta konkretna funkcja nagle i nieoczekiwanie zmieniła swoje zachowanie w przeszłości, w sposób, który złamał kod, masz pełne prawo do paranoi. Błędy mają tendencję do skupiania się.
Pamiętaj, że wszystkie powyższe mają zastosowanie, nawet jeśli jesteś oryginalnym autorem obu funkcji. Nie wiemy, czy oczekuje się, że te dwie funkcje będą „żyć razem” przez resztę życia, czy będą powoli rozpadały się na osobne moduły, czy też jakoś postrzeliłeś się w stopę z błędem w starszym wieku wersje getUser. Ale pewnie można się domyślić.
źródło
getUser
później, aby mógł zwrócić null, czy jawne sprawdzenie dostarczyłoby ci informacji, których nie można uzyskać z „NullPointerException” i numeru wiersza?Odpowiedź Ixreca jest dobra, ale przyjmuję inne podejście, ponieważ uważam, że warto to rozważyć. Na potrzeby tej dyskusji będę mówić o twierdzeniach, ponieważ jest to nazwa, pod którą
Preconditions.checkNotNull()
tradycyjnie znacie wasze .Chciałbym zasugerować, że programiści często przeceniają stopień, w jakim są pewni, że coś zachowuje się w określony sposób, i nie doceniają konsekwencji takiego zachowania. Ponadto programiści często nie zdają sobie sprawy z mocy asercji jako dokumentacji. Z mojego doświadczenia wynika, że programiści twierdzą o wiele rzadziej niż powinni.
Moim mottem jest:
Oczywiście, jeśli jesteś absolutnie pewien, że coś zachowuje się w określony sposób, powstrzymasz się od stwierdzenia, że tak właśnie się zachowało, i to w przeważającej części jest rozsądne. Naprawdę nie jest możliwe napisanie oprogramowania, jeśli nie możesz niczego zaufać.
Ale są wyjątki nawet od tej zasady. Co ciekawe, aby wziąć przykład z Ixreca, istnieje pewien rodzaj sytuacji, w której całkowicie pożądane jest podążanie
int i = 3;
za twierdzeniem, którei
rzeczywiście znajduje się w pewnym zakresie. Jest to sytuacja, w któreji
może ulec zmianie ktoś, kto próbuje różnych scenariuszy „co, jeśli”, a poniższy kod opiera się nai
posiadaniu wartości z określonego zakresu, i nie jest od razu oczywiste, szybko patrząc na poniższy kod, co dopuszczalny zakres to.int i = 3; assert i >= 0 && i < 5;
Może więc z początku wydawać się bezsensowne, ale jeśli się nad tym zastanowić, to mówi ci, że możesz graći
, i określa zasięg, w którym musisz pozostać. Asercja jest najlepszym rodzajem dokumentacji, ponieważjest egzekwowane przez maszynę , więc jest doskonałą gwarancją, a wraz z kodem jest refaktoryzowane , więc pozostaje zawsze aktualne.Twój
getUser(int id)
przykład nie jest bardzo odległym względnym krewnymassign-constant-and-assert-in-range
przykładu. Z definicji widzisz, że błędy zdarzają się, gdy rzeczy wykazują zachowanie inne niż oczekiwane. To prawda, że nie możemy potwierdzić wszystkiego, więc czasami będzie trochę debugowania. Ale w branży jest dobrze ustalonym faktem, że im szybciej wykryjemy błąd, tym mniej go kosztuje. Pytania, które powinieneś zadać, to nie tylko to, czy masz pewność, żegetUser()
nigdy nie zwróci wartości zerowej (i nigdy nie zwróci użytkownika o pustej nazwie), ale także, jakie złe rzeczy będą się działo z resztą kodu, jeśli tak się stanie fakt, że pewnego dnia zwróci coś nieoczekiwanego i jak łatwo jest szybko spojrzeć na resztę kodu, aby dokładnie wiedzieć, czego się spodziewaćgetUser()
.Jeśli nieoczekiwane zachowanie spowodowałoby uszkodzenie bazy danych, być może powinieneś to stwierdzić, nawet jeśli masz 101% pewności, że tak się nie stanie. Jeśli
null
nazwa użytkownika spowodowałaby jakiś dziwny, tajemniczy, niewykrywalny błąd, tysiące linii w dół i miliardy cykli zegara później, być może najlepiej jest zawieść tak wcześnie, jak to możliwe.Tak więc, chociaż nie zgadzam się z odpowiedzią Ixreca, sugeruję, abyście poważnie zastanowili się nad faktem, że twierdzenia nic nie kosztują, więc tak naprawdę nie ma nic do stracenia, tylko do zdobycia poprzez ich swobodne wykorzystanie.
źródło
... && sureness < 1)
.Zdecydowanie nie.
Dzwoniący nie powinien nigdy sprawdzać, czy funkcja, którą wywołuje, przestrzega jego umowy. Powód jest prosty: potencjalnie bardzo wielu dzwoniących i jest niepraktyczne, a prawdopodobnie nawet błędne z koncepcyjnego punktu widzenia dla każdego dzwoniącego, aby powielić logikę sprawdzania wyników innych funkcji.
Jeśli mają zostać wykonane jakiekolwiek kontrole, każda funkcja powinna sprawdzić swoje własne warunki końcowe. Warunki dodatkowe dają pewność, że funkcja została poprawnie zaimplementowana, a użytkownicy będą mogli polegać na umowie z tą funkcją.
Jeśli dana funkcja nie ma nigdy zwracać wartości null, należy to udokumentować i stać się częścią umowy tej funkcji. Właśnie o to chodzi w umowach: zaoferuj użytkownikom niezmienniki, na których mogą polegać, aby napotykali mniej przeszkód podczas pisania własnych funkcji.
źródło
Jeśli null jest prawidłową wartością zwracaną metody getUser, wówczas twój kod powinien obsłużyć to za pomocą If, a nie wyjątku - nie jest to wyjątkowy przypadek.
Jeśli null nie jest prawidłową wartością zwracaną metody getUser, oznacza to błąd w getUser - metoda getUser powinna zgłosić wyjątek lub w inny sposób zostać naprawiona.
Jeśli metoda getUser jest częścią biblioteki zewnętrznej lub w inny sposób nie można jej zmienić, a chcesz, aby wyjątki były zgłaszane w przypadku zwrotu zerowego, zapakuj klasę i metodę, aby wykonać kontrole i wyrzuć wyjątek, tak aby każde wystąpienie wywołanie getUser jest spójne w kodzie.
źródło
Argumentowałbym, że przesłanka twojego pytania jest wyłączona: po co używać zerowego odniesienia na początek?
Wzór zerowy obiekt jest sposobem, aby powrócić obiekty, które w zasadzie nic nie robić „Żaden użytkownik” zamiast wartości null zamian za użytkownika, zwraca obiekt, który reprezentuje
Ten użytkownik byłby nieaktywny, miałby pustą nazwę i inne niepuste wartości wskazujące „nic tutaj”. Podstawową korzyścią jest to, że nie trzeba zaśmiecać, ponieważ program zeruje kontrole, logowanie itp., Wystarczy użyć obiektów.
Dlaczego algorytm powinien dbać o wartość zerową? Kroki powinny być takie same. Równie łatwo i znacznie wyraźniej jest mieć obiekt zerowy, który zwraca zero, pusty ciąg znaków itp.
Oto przykład sprawdzania kodu dla wartości null:
Oto przykład kodu używającego obiektu zerowego:
Który jest jaśniejszy? Myślę, że drugi jest.
Chociaż pytanie jest oznaczone java , warto zauważyć, że wzorzec zerowy jest naprawdę przydatny w C ++ podczas korzystania z referencji. Odwołania Java są bardziej podobne do wskaźników C ++ i pozwalają na null. Referencje w C ++ nie mogą być przechowywane
nullptr
. Jeśli ktoś chciałby mieć coś analogicznego do pustego odwołania w C ++, wzorzec zerowego obiektu jest jedynym sposobem, w jaki mogę to osiągnąć.źródło
getCount()
dokumentuje gwarancję, że nie zwraca teraz 0, i nie zrobi tego w przyszłości, z wyjątkiem sytuacji, gdy ktoś zdecyduje się dokonać przełomowej zmiany i chce zaktualizować wszystko, co wywołuje to, aby poradzić sobie z nowy interfejs. Zobaczenie, co robi obecnie kod, nie jest pomocne, ale jeśli zamierzasz sprawdzić kod, to kod do sprawdzenia to testy jednostkowegetCount()
. Jeśli sprawdzą, czy nie zwraca 0, to wiesz, że jakakolwiek przyszła zmiana zwrotu 0 zostanie uznana za przełomową, ponieważ testy się nie powiodą.Ogólnie rzecz biorąc, powiedziałbym, że zależy to głównie od następujących trzech aspektów:
odporność: czy metoda wywołująca może poradzić sobie z
null
wartością? Jeślinull
wartość może skutkowaćRuntimeException
poleceniem sprawdzenia - chyba że złożoność jest niska i metody wywoływania / wywoływania są tworzone przez tego samego autora inull
nie są oczekiwane (np. Obaprivate
w tym samym pakiecie).odpowiedzialność za kod: jeśli programista A jest odpowiedzialny za
getUser()
metodę, a programista B korzysta z niej (np. jako część biblioteki), zdecydowanie zalecam sprawdzenie jej wartości. Tylko dlatego, że programista B może nie wiedzieć o zmianie, która powoduje potencjalnąnull
wartość zwrotu.złożoność: im wyższa ogólna złożoność programu lub środowiska, tym bardziej zalecam sprawdzenie wartości zwracanej. Nawet jeśli masz pewność, że nie może to być
null
dzisiaj w kontekście metody wywoływania, być może będziesz musiał zmienićgetUser()
na inny przypadek użycia. Po kilku miesiącach lub latach i dodaniu kilku tysięcy linii kodów może to być pułapka.Ponadto polecam udokumentować potencjalne
null
zwracane wartości w komentarzu JavaDoc. Ze względu na wyróżnianie opisów JavaDoc w większości IDE, może to być pomocne ostrzeżenie dla każdego, kto używagetUser()
.źródło
Zdecydowanie nie musisz.
Na przykład, jeśli już wiesz, że zwrot nigdy nie może być zerowy - Dlaczego chcesz dodać czek zerowy? Dodanie czeku zerowego niczego nie zepsuje, ale jest po prostu zbędne. I lepiej nie kodować logiki redundantnej, o ile można tego uniknąć.
źródło