Poprawnie używając null
Istnieją różne sposoby korzystania null
. Najczęstszym i semantycznie poprawnym sposobem jest użycie go, gdy możesz mieć lub nie mieć jednej wartości. W tym przypadku wartość jest równa null
lub jest czymś znaczącym, jak zapis z bazy danych lub coś takiego.
W takich sytuacjach najczęściej używasz go w następujący sposób (w pseudokodzie):
if (value is null) {
doSomethingAboutIt();
return;
}
doSomethingUseful(value);
Problem
I ma bardzo duży problem. Problem polega na tym, że do czasu wywołania doSomethingUseful
wartość mogła nie zostać sprawdzona null
! Jeśli tak nie było, program najprawdopodobniej ulegnie awarii. A użytkownik może nawet nie zobaczyć żadnych dobrych komunikatów o błędach, pozostawiając coś w rodzaju „strasznego błędu: pożądana wartość, ale dostała zero!” (po aktualizacji: chociaż może występować jeszcze mniej informacji, takich jak Segmentation fault. Core dumped.
lub, co gorsza, brak błędów i nieprawidłowe manipulowanie wartością NULL w niektórych przypadkach)
Niezwykle częstym błędem jest zapominanie o pisaniu czeków null
i radzeniu sobie w null
sytuacjach . Właśnie dlatego Tony Hoare, który wynalazł, powiedział podczas konferencji programowej QCon London w 2009 roku, że popełnił błąd miliarda dolarów w 1965 roku:
https://www.infoq.com/presentations/Null-References-The-Billion-Dollar- Błąd-Tony-Hoarenull
Unikanie problemu
Niektóre technologie i języki sprawiają, że sprawdzanie pod kątem null
niemożności zapomnienia na różne sposoby, zmniejsza liczbę błędów.
Na przykład Haskell ma Maybe
monadę zamiast zer. Załóżmy, że DatabaseRecord
jest to typ zdefiniowany przez użytkownika. W Haskell wartość typu Maybe DatabaseRecord
może być równa Just <somevalue>
lub może być równa Nothing
. Możesz następnie używać go na różne sposoby, ale bez względu na to, jak go używasz, nie możesz zastosować niektórych operacji Nothing
bez wiedzy o tym.
Na przykład ta funkcja o nazwie zeroAsDefault
zwraca x
dla Just x
i 0
dla Nothing
:
zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
Nothing -> 0
Just x -> x
Christian Hackl mówi, że C ++ 17 i Scala mają swoje własne sposoby. Możesz więc spróbować dowiedzieć się, czy twój język ma coś takiego i użyć go.
Wartości zerowe są nadal w powszechnym użyciu
Jeśli nie masz nic lepszego, używanie null
jest w porządku. Tylko uważaj na to. W każdym razie pomocne będą deklaracje typu w funkcjach.
Może to również brzmieć niezbyt progresywnie, ale powinieneś sprawdzić, czy twoi koledzy chcą z niego skorzystać, null
czy czegoś innego. Mogą być konserwatywne i z pewnych powodów mogą nie chcieć korzystać z nowych struktur danych. Na przykład obsługa starszych wersji języka. Takie rzeczy powinny być zadeklarowane w standardach kodowania projektu i odpowiednio omówione z zespołem.
Na twoją propozycję
Sugerujesz użycie osobnego pola boolowskiego. Ale i tak musisz to sprawdzić i nadal możesz zapomnieć o sprawdzeniu. Więc nic tu nie wygrało. Jeśli możesz nawet zapomnieć o czymś innym, takim jak aktualizacja obu wartości za każdym razem, jest jeszcze gorzej. Jeśli problem zapomnienia o sprawdzeniu null
nie zostanie rozwiązany, nie ma sensu. Unikanie null
jest trudne i nie należy robić tego w sposób, który pogorszy sytuację.
Jak nie używać null
Wreszcie istnieją powszechne sposoby null
nieprawidłowego użycia . Jednym z takich sposobów jest użycie go zamiast pustych struktur danych, takich jak tablice i łańcuchy. Pusta tablica jest właściwą tablicą, jak każda inna! Prawie zawsze jest ważne i przydatne dla struktur danych, które mogą pasować do wielu wartości, aby mogły być puste, tzn. Mają zerową długość.
Z punktu widzenia algebry pusty ciąg znaków dla ciągów jest podobny do 0 dla liczb, tj. Tożsamość:
a+0=a
concat(str, '')=str
Pusty ciąg znaków pozwala ogólnie stać się monoidem:
https://en.wikipedia.org/wiki/Monoid
Jeśli go nie dostaniesz, nie jest to dla ciebie ważne.
Zobaczmy teraz, dlaczego jest to ważne dla programowania w tym przykładzie:
for (element in array) {
doSomething(element);
}
Jeśli podamy tutaj pustą tablicę, kod będzie działał poprawnie. Po prostu nic nie zrobi. Jeśli jednak przejdziemy null
tutaj, prawdopodobnie nastąpi awaria z błędem typu „przepraszam, nie można przepuścić przez zero”. Możemy to owinąć, if
ale to jest mniej czyste i znowu, możesz zapomnieć to sprawdzić
Jak obsłużyć zerowy
To, co doSomethingAboutIt()
powinno być zrobione, a zwłaszcza czy powinno to spowodować wyjątek, to kolejna skomplikowana kwestia. Krótko mówiąc, zależy to od tego, czy null
była to dopuszczalna wartość wejściowa dla danego zadania i czego oczekuje się w odpowiedzi. Wyjątek stanowią zdarzenia, których nie oczekiwano. Nie zajmę się tym tematem. Ta odpowiedź jest już bardzo długa.
std::optional
lubOption
. W innych językach może być konieczne zbudowanie odpowiedniego mechanizmu lub skorzystanie znull
czegoś podobnego, ponieważ jest to bardziej idiomatyczne.