Twoje pytanie.
Ale, ale - z mojej naiwności pozwól mi krzyczeć - czasami wartość MOGĄ być wyraźnie nieobecna!
W pełni tam na pokładzie. Są jednak pewne zastrzeżenia, które omówię za chwilę. Ale najpierw:
Wartości zerowe są złe i należy ich unikać, gdy tylko jest to możliwe.
Myślę, że jest to oświadczenie o dobrych intencjach, ale zbyt ogólne.
Nulls nie są złe. Nie są antypatternem. Nie należy ich za wszelką cenę unikać. Jednak wartości null są podatne na błędy (od braku sprawdzenia wartości null).
Ale nie rozumiem, w jaki sposób brak sprawdzenia wartości null jest dowodem, że użycie wartości null jest z natury niewłaściwe.
Jeśli wartość null jest zła, to również indeksy tablic i wskaźniki C ++. Oni nie są. Łatwo jest strzelać sobie w stopę, ale nie należy ich unikać za wszelką cenę.
W pozostałej części tej odpowiedzi zamierzam dostosować zamiar „zerowe są złe” do bardziej dopracowanych „ zerowych należy traktować odpowiedzialnie ”.
Twoje rozwiązanie
Chociaż zgadzam się z twoją opinią na temat stosowania wartości null jako reguły globalnej; W tym konkretnym przypadku nie zgadzam się z użyciem przez Ciebie wartości null.
Podsumowując poniższe opinie: nie rozumiesz celu dziedziczenia i polimorfizmu. Podczas gdy twój argument, aby użyć null, jest ważny, twój dalszy „dowód”, dlaczego drugi sposób jest zły, opiera się na niewłaściwym wykorzystaniu dziedziczenia, co czyni twoje punkty nieco nieistotnymi dla problemu zerowego.
Większość aktualizacji wiąże się oczywiście z graczem - w końcu trzeba wiedzieć, który gracz wykonał ruch w tej turze. Jednak niektóre aktualizacje nie mogą mieć z nimi istotnego powiązania.
Jeśli te aktualizacje są znacząco różne (którymi są), potrzebne są dwa osobne typy aktualizacji.
Weźmy na przykład wiadomości. Masz komunikaty o błędach i wiadomości czatu IM. Ale chociaż mogą zawierać bardzo podobne dane (komunikat w postaci ciągu i nadawcę), nie zachowują się w ten sam sposób. Powinny być używane osobno, jako różne klasy.
To, co teraz robisz, polega na rozróżnieniu dwóch typów aktualizacji na podstawie nieważności właściwości Player. Jest to równoważne z podejmowaniem decyzji między komunikatem IM a komunikatem o błędzie na podstawie istnienia kodu błędu. Działa, ale nie jest to najlepsze podejście.
- Kod JS będzie teraz po prostu odbierał obiekt bez Player jako zdefiniowaną właściwość, która jest taka sama, jakby była pusta, ponieważ oba przypadki wymagają sprawdzenia, czy wartość jest obecna, czy nie;
Twój argument opiera się na wadach polimorfizmu. Jeśli masz metodę, która zwraca GameUpdate
obiekt, generalnie nie powinno się nigdy starać rozróżniać GameUpdate
, PlayerGameUpdate
lub NewlyDevelopedGameUpdate
.
Klasa podstawowa musi mieć w pełni działającą umowę, tzn. Jej właściwości są zdefiniowane w klasie podstawowej, a nie w klasie pochodnej (to powiedziawszy, wartość tych właściwości podstawowych można oczywiście zastąpić w klasach pochodnych).
To sprawia, że twój argument jest dyskusyjny. W dobrej praktyce nigdy nie powinieneś się przejmować, że twój GameUpdate
obiekt ma lub nie ma gracza. Jeśli próbujesz uzyskać dostęp do Player
właściwości a GameUpdate
, robisz coś nielogicznego.
Wpisane języki, takie jak C #, nawet nie pozwolą ci uzyskać dostępu do Player
właściwości, GameUpdate
ponieważ GameUpdate
po prostu nie ma tej właściwości. JavaScript jest znacznie bardziej luźny w swoim podejściu (i nie wymaga wstępnej kompilacji), więc nie zadaje sobie trudu, aby cię poprawić i zamiast tego powoduje wybuch w czasie wykonywania.
- Gdyby do klasy GameUpdate dodano kolejne pole zerowalne, musiałbym mieć możliwość wielokrotnego dziedziczenia, aby kontynuować to projektowanie - ale MI jest samo w sobie złe (według bardziej doświadczonych programistów) i, co ważniejsze, C # nie mam, więc nie mogę go użyć.
Nie powinieneś tworzyć klas opartych na dodawaniu właściwości zerowalnych. Powinieneś tworzyć klasy w oparciu o funkcjonalnie unikalne typy aktualizacji gier .
Jak w ogóle uniknąć problemów z zerowym?
Uważam, że warto opracować sposób, w jaki można w sposób znaczący wyrazić brak wartości. Jest to zbiór rozwiązań (lub złych podejść) w dowolnej kolejności.
1. W każdym razie użyj null.
To jest najłatwiejsze podejście. Jednak rejestrujesz się, aby uzyskać mnóstwo kontroli zerowych i (gdy tego nie zrobisz) rozwiązywanie problemów z wyjątkami referencji zerowych.
2. Użyj niepustej wartości bez znaczenia
Prostym przykładem jest tutaj indexOf()
:
"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1
Zamiast tego null
dostajesz -1
. Na poziomie technicznym jest to poprawna wartość całkowita. Jednak wartość ujemna to nonsensowna odpowiedź, ponieważ oczekuje się, że indeksy będą dodatnimi liczbami całkowitymi.
Logicznie można tego użyć tylko w przypadkach, w których typ zwracany pozwala na więcej możliwych wartości, niż można je w sposób znaczący zwrócić (indexOf = liczby całkowite dodatnie; int = liczby całkowite ujemne i dodatnie)
W przypadku typów referencyjnych nadal można zastosować tę zasadę. Na przykład, jeśli masz encję o identyfikatorze całkowitym, możesz zwrócić fikcyjny obiekt o jego identyfikatorze ustawionym na -1, w przeciwieństwie do wartości null.
Musisz po prostu zdefiniować „stan obojętny”, za pomocą którego można zmierzyć, czy obiekt jest rzeczywiście użyteczny, czy nie.
Zauważ, że nie powinieneś polegać na ustalonych wcześniej wartościach ciągów, jeśli nie kontrolujesz wartości ciągów. Możesz rozważyć ustawienie nazwy użytkownika na „null”, gdy chcesz zwrócić pusty obiekt, ale możesz napotkać problemy, gdy Christopher Null zarejestruje się w Twojej usłudze .
3. Zamiast tego rzuć wyjątek.
Nie, po prostu nie. Wyjątki nie powinny być obsługiwane dla przepływu logicznego.
To powiedziawszy, istnieją pewne przypadki, w których nie chcesz ukryć wyjątku (zerowego odniesienia), ale raczej pokazać odpowiedni wyjątek. Na przykład:
var existingPerson = personRepository.GetById(123);
Może to rzucić PersonNotFoundException
(lub podobny), gdy nie ma osoby o identyfikatorze 123.
W pewien sposób jest to dopuszczalne tylko w przypadkach, w których nie znalezienie przedmiotu uniemożliwia dalsze przetwarzanie. Jeśli nie można znaleźć przedmiotu, a aplikacja może być kontynuowana, NIGDY nie należy rzucać wyjątku . Nie mogę tego wystarczająco podkreślić.