Nie do końca rozumiem konsekwentne niszczenie referencji zerowych przez niektórych ludzi w języku programowania. Co jest w nich takiego złego? Jeśli poproszę o dostęp do odczytu pliku, który nie istnieje, cieszę się, że otrzymam wyjątek lub odwołanie zerowe, a jednak wyjątki są uważane za dobre, ale odniesienia zerowe są uważane za złe. Jakie jest tego uzasadnienie?
programming-languages
exceptions
null
davidk01
źródło
źródło
Odpowiedzi:
Odwołania zerowe nie są „unikane” bardziej niż wyjątki, przynajmniej przez kogokolwiek, kogo znałem lub czytałem. Myślę, że nie rozumiesz konwencjonalnej mądrości.
Zła jest próba uzyskania dostępu do pustego odwołania (lub wyrejestrowanie pustego wskaźnika itp.). Jest to złe, ponieważ zawsze oznacza błąd; nigdy nie zrobiłby czegoś takiego celowo, a jeśli są robi to celowo, to jeszcze gorzej, bo to co niemożliwe do odróżnienia od oczekiwanego zachowanie buggy zachowań.
Istnieją pewne grupy skrajne, które z jakiegoś powodu tak naprawdę nienawidzą pojęcia nieważności, ale jak zauważa Ed , jeśli nie masz,
null
albonil
będziesz musiał po prostu zastąpić to czymś innym, co może prowadzić do czegoś gorsze niż awaria (np. uszkodzenie danych).W rzeczywistości wiele ram obejmuje obie koncepcje; na przykład w .NET częstym wzorcem, który zobaczysz, jest para metod, z których jedna poprzedza słowo
Try
(np.TryGetValue
). W takimTry
przypadku odwołanie jest ustawione na wartość domyślną (zwyklenull
), aw innym przypadku zgłaszany jest wyjątek. W obu podejściach nie ma nic złego; oba są często używane w środowiskach, które je obsługują.To naprawdę wszystko zależy od semantyki. Jeśli
null
jest poprawną wartością zwracaną, tak jak w ogólnym przypadku przeszukiwania kolekcji, to zwróćnull
. Z drugiej strony, jeśli to nie jest prawidłową wartością powrót - na przykład, patrząc rekordu przy użyciu klucza podstawowego dostarczonej z własnej bazy danych - to powrótnull
byłby zły pomysł, ponieważ rozmówca nie będzie spodziewał i zapewne nie będzie tego sprawdzać.Naprawdę bardzo łatwo jest ustalić, którego semantycznego użyć: Czy ma sens nieokreślony wynik działania funkcji? Jeśli tak, możesz zwrócić odwołanie zerowe. Jeśli nie, wyrzuć wyjątek.
źródło
null
, tak naprawdę, Haskell jest całkiem nieistotne tutaj.null
przetrwała tak dobrze przez tak długi czas, a większość ludzi, którzy mówią inaczej wydają się być akademicy z małymi projektowania doświadczenie aplikacjach rzeczywistych z ograniczeniami jak niekompletnych wymogów lub ewentualnego konsystencji.Duża różnica polega na tym, że jeśli pominiesz kod do obsługi wartości NULL, twój kod będzie nadal prawdopodobnie zawieszał się na późniejszym etapie z jakimś niepowiązanym komunikatem o błędzie, przy czym tak jak w przypadku wyjątków, wyjątek byłby zgłaszany w początkowym punkcie awarii (otwarcie plik do odczytu w twoim przykładzie).
źródło
Ponieważ wartości null nie są niezbędną częścią języka programowania i stanowią spójne źródło błędów. Jak mówisz, otwarcie pliku może spowodować błąd, który może zostać przesłany z powrotem jako zerowa wartość zwracana lub za pośrednictwem wyjątku. Jeśli wartości zerowe nie są dozwolone, istnieje spójny, pojedynczy sposób komunikowania niepowodzenia.
Ponadto nie jest to najczęstszy problem z zerami. Większość ludzi pamięta, aby sprawdzić, czy nie ma wartości null po wywołaniu funkcji, która może ją zwrócić. Problem pojawia się znacznie częściej w twoim własnym projekcie, pozwalając, aby zmienne były zerowe w różnych punktach wykonania programu. Możesz zaprojektować swój kod w taki sposób, aby wartości null nigdy nie były dozwolone, ale jeśli null nie byłby dozwolony na poziomie języka, żadne z nich nie byłoby konieczne.
Jednak w praktyce nadal potrzebujesz jakiegoś sposobu na wskazanie, czy zmienna jest inicjowana czy nie. Miałbyś wtedy formę błędu, w którym twój program nie ulega awarii, ale zamiast tego kontynuuje używanie pewnej nieprawidłowej wartości domyślnej. Naprawdę nie wiem, co jest lepsze. Za moje pieniądze lubię rozbijać wcześnie i często.
źródło
null
wartość zwrotną: Żądane informacje nie istnieją. Nie ma różnicy. Tak czy inaczej, osoba dzwoniąca musi sprawdzić wartość zwracaną, jeśli faktycznie musi użyć tych informacji do określonego celu. Czy jest to sprawdzanie poprawnościnull
obiektu zerowego, czy monady, nie ma praktycznej różnicy.Tony Hoare, który stworzył pomysł zerowego odniesienia, nazywa to błędem w wysokości miliona dolarów .
Problem nie dotyczy samych odwołań zerowych, ale braku odpowiedniego sprawdzania typu w większości (w przeciwnym razie) bezpiecznych językach.
Ten brak wsparcia z języka oznacza, że błędy „null-bug” mogą czaić się w programie przez długi czas, zanim zostaną wykryte. Taka jest natura błędów, ale wiadomo, że „błędów zerowych” można teraz uniknąć.
Ten problem występuje szczególnie w C lub C ++ (na przykład) z powodu „twardego” błędu, który powoduje (natychmiastowe zawieszenie programu, bez eleganckiego przywracania).
W innych językach zawsze pojawia się pytanie, jak sobie z nimi poradzić.
W Javie lub C # dostaniesz wyjątek, jeśli spróbujesz wywołać metodę na odwołaniu zerowym, i może być w porządku. Dlatego większość programistów Java lub C # jest do tego przyzwyczajona i nie rozumie, dlaczego ktoś chciałby robić inaczej (i śmiać się z C ++).
W Haskell musisz jawnie podać akcję dla przypadku zerowego, dlatego programiści Haskell wychwalają swoich kolegów, ponieważ dobrze to zrobili (prawda?).
To tak naprawdę stara debata na temat kodu błędu / wyjątku, ale tym razem z wartością Sentinel zamiast kodu błędu.
Jak zawsze to, co jest najbardziej odpowiednie, naprawdę zależy od sytuacji i semantyki, której szukasz.
źródło
null
naprawdę jest zły, ponieważ psuje system typów . (To prawda, że alternatywa ma wadę, przynajmniej w niektórych językach).Do wskaźnika zerowego nie można dołączyć czytelnego dla człowieka komunikatu o błędzie.
(Możesz jednak zostawić komunikat błędu w pliku dziennika.)
W niektórych języków / środowiskach, które pozwalają arytmetyki wskaźników, jeśli jeden z argumentów jest null pointer i dopuszcza do obliczeń, wynik byłby nieważny , niż null pointer. (*) Więcej mocy dla ciebie.
(*) Zdarza się to często w programowaniu COM , w którym próba wywołania metody interfejsu, ale wskaźnik interfejsu jest zerowy, spowoduje wywołanie nieprawidłowego adresu, który jest prawie, ale nie do końca, dokładnie różny od zera.
źródło
Zwracanie wartości NULL (lub zera numerycznego lub logicznego fałszu) w celu zasygnalizowania błędu jest błędne zarówno pod względem technicznym, jak i koncepcyjnym.
Z technicznego punktu widzenia obciążasz programistę sprawdzaniem wartości zwracanej od razu , dokładnie w punkcie, w którym jest ona zwracana. Jeśli otworzysz dwadzieścia plików z rzędu, a sygnalizacja błędu zostanie wykonana przez zwrócenie wartości NULL, wówczas konsumujący kod musi sprawdzić każdy plik odczytywany indywidualnie i zerwać wszelkie pętle i podobne konstrukcje. To idealny przepis na zaśmiecony kod. Jeśli jednak zdecydujesz się zasygnalizować błąd poprzez zgłoszenie wyjątku, konsumujący kod może zdecydować się natychmiast obsłużyć wyjątek lub pozwolić, aby pojawił się tyle poziomów, ile potrzeba, nawet w wywołaniach funkcji. To sprawia, że kod jest znacznie czystszy.
Koncepcyjnie, jeśli otworzysz plik i coś pójdzie nie tak, zwracanie wartości (nawet NULL) jest nieprawidłowe. Nie masz nic do zwrócenia, ponieważ operacja się nie zakończyła. Zwracanie NULL jest koncepcyjnym odpowiednikiem „Udało mi się odczytać plik, a oto, co zawiera - nic”. Jeśli to właśnie chcesz wyrazić (to znaczy, jeśli NULL ma sens jako rzeczywisty wynik dla danej operacji), to w każdym razie zwróć NULL, ale jeśli chcesz zasygnalizować błąd, użyj wyjątków.
Historycznie błędy były zgłaszane w ten sposób, ponieważ języki programowania, takie jak C, nie mają wbudowanej obsługi wyjątków, a zalecany sposób (przy użyciu skoków w dal) jest nieco owłosiony i nieco sprzeczny z intuicją.
Problem ma także stronę konserwacyjną: z wyjątkami musisz napisać dodatkowy kod, aby poradzić sobie z awarią; jeśli tego nie zrobisz, program zawiedzie wcześnie i mocno (co jest dobre). Jeśli zwrócisz NULL, aby zasygnalizować błędy, domyślnym zachowaniem programu jest zignorowanie błędu i kontynuowanie, dopóki nie doprowadzi to do innych problemów na drodze - uszkodzonych danych, segfault, NullReferenceExceptions, w zależności od języka. Aby wcześnie i głośno zasygnalizować błąd, musisz napisać dodatkowy kod i zgadnij, co: to część, która zostaje pominięta, gdy masz napięty termin.
źródło
Jak już wspomniano, wiele języków nie przekształca dereferencji wskaźnika zerowego w możliwy do wychwycenia wyjątek. Jest to stosunkowo nowoczesna sztuczka. Kiedy po raz pierwszy wykryto problem ze wskaźnikiem zerowym, wyjątki jeszcze nie zostały wynalezione.
Jeśli dopuścisz wskaźniki zerowe jako prawidłowy przypadek, będzie to przypadek specjalny. Potrzebujesz logiki obsługi specjalnych przypadków, często w wielu różnych miejscach. To dodatkowa złożoność.
Bez względu na to, czy dotyczy potencjalnie zerowych wskaźników, czy nie, jeśli nie używasz wyjątków do obsługi wyjątkowych przypadków, musisz obsłużyć te wyjątkowe przypadki w inny sposób. Zazwyczaj każde wywołanie funkcji musi być sprawdzane pod kątem wyjątkowych przypadków, aby zapobiec niewłaściwemu wywołaniu funkcji lub wykryć przypadek awarii, gdy funkcja kończy działanie. To dodatkowa złożoność, której można uniknąć za pomocą wyjątków.
Większa złożoność zwykle oznacza więcej błędów.
Alternatywami do używania zerowych wskaźników w strukturach danych (np. Do oznaczenia początku / końca połączonej listy) są użycie elementów wartowniczych. Mogą zapewnić tę samą funkcjonalność przy znacznie mniejszej złożoności. Istnieją jednak inne sposoby zarządzania złożonością. Jednym ze sposobów jest zawinięcie potencjalnie pustego wskaźnika w inteligentną klasę wskaźnika, aby kontrole zerowe były potrzebne tylko w jednym miejscu.
Co zrobić ze wskaźnikiem zerowym, gdy zostanie wykryty? Jeśli nie możesz wbudować obsługi wyjątkowych przypadków, zawsze możesz zgłosić wyjątek i skutecznie przekazać to połączenie specjalnemu dzwoniącemu. I to właśnie dokładnie robią niektóre języki, gdy wyrejestrujesz wskaźnik zerowy.
źródło
Specyficzne dla C ++, ale istnieją tam odwołania zerowe, ponieważ semantyka zerowa w C ++ jest powiązana z typami wskaźników. Rozsądne jest, aby funkcja otwierania pliku zawiodła i zwróciła wskaźnik zerowy; w rzeczywistości
fopen()
funkcja robi dokładnie to.źródło
To zależy od języka.
Na przykład Objective-C pozwala bez problemu wysłać wiadomość do obiektu zerowego (zerowego). Wezwanie do zera również zwraca zero i jest uważane za funkcję języka.
Osobiście mi się podoba, ponieważ możesz polegać na tym zachowaniu i unikać tych zawiłych zagnieżdżonych
if(obj == null)
konstrukcji.Na przykład:
Można skrócić do:
Krótko mówiąc, sprawia, że kod jest bardziej czytelny.
źródło
Nie daję się zwieść, czy zwrócisz wartość zerową, czy zgłosisz wyjątek, o ile go dokumentujesz.
źródło
GetAddressMaybe
lubGetSpouseNameOrNull
.Odwołanie zerowe zwykle występuje, ponieważ coś w logice programu zostało pominięte, tj .: dotarłeś do linii kodu bez przechodzenia przez wymagane ustawienia dla tego bloku kodu.
Z drugiej strony, jeśli zgłosisz wyjątek dla czegoś, oznacza to, że rozpoznałeś, że konkretna sytuacja może wystąpić podczas normalnej pracy programu i że jest on obsługiwany.
źródło
Odwołania zerowe są często bardzo przydatne: Na przykład element na połączonej liście może mieć następcę lub nie mieć go. Używanie
null
„bez następcy” jest całkowicie naturalne. Lub, osoba może mieć małżonka lub nie - używanienull
dla „osoba nie ma małżonka” jest całkowicie naturalne, o wiele bardziej naturalne niż posiadanie pewnej „nie ma małżonka” - szczególna wartość, do którejPerson.Spouse
członek mógłby się odwoływać.Ale: Wiele wartości nie jest opcjonalnych. W typowym programie OOP powiedziałbym, że ponad połowa referencji nie może być
null
po inicjalizacji, inaczej program się nie powiedzie. W przeciwnym razie kod musiałby być wypełnionyif (x != null)
czekami. Dlaczego więc każde odwołanie powinno być domyślnie zerowane? Naprawdę powinno być na odwrót: zmienne powinny domyślnie nie mieć wartości zerowej, a Ty powinieneś wyraźnie powiedzieć „och, i ta wartość też może byćnull
”.źródło
Twoje pytanie jest myląco sformułowane. Czy masz na myśli wyjątek zerowy (który jest faktycznie spowodowany próbą dereferencja null)? Wyraźnym powodem, dla którego nie chcemy tego rodzaju wyjątku, jest to, że nie daje on żadnych informacji na temat tego, co poszło źle, a nawet kiedy - wartość można ustawić na wartość null w dowolnym momencie programu. Piszesz: „Jeśli poproszę o dostęp do odczytu pliku, który nie istnieje, to cieszę się, że otrzymałem wyjątek lub odwołanie zerowe” - ale nie powinieneś być szczęśliwy, gdy dostaniesz coś, co nie wskazuje przyczyny . Nigdzie w ciągu „nie podjęto próby wyrejestrowania wartości null” nie ma wzmianki o czytaniu ani o nieistnieniu plików. Być może masz na myśli, że byłbyś szczęśliwy, gdybyś był zerowy jako wartość zwracana z wywołania odczytu - to zupełnie inna sprawa, ale wciąż nie daje ci żadnych informacji o tym, dlaczego odczyt się nie powiódł; to'
źródło