Nie używaj, jeśli możesz tego uniknąć. Użyj funkcji fabrycznej zwracającej typ opcji, krotkę lub dedykowany typ sumy. Innymi słowy, reprezentują potencjalnie niewykonaną wartość z innym typem niż wartość gwarantowana, że nie zostanie niewykonana.
Najpierw wypiszmy desiderata, a następnie zastanówmy się, jak możemy to wyrazić w kilku językach (C ++, OCaml, Python)
- przechwyć niechciane użycie domyślnego obiektu w czasie kompilacji.
- wyjaśnij, czy dana wartość jest potencjalnie niewykonana podczas czytania kodu.
- wybierz naszą rozsądną wartość domyślną, jeśli dotyczy, raz dla każdego rodzaju . Zdecydowanie nie wybieraj potencjalnie różnych ustawień domyślnych w każdej witrynie z połączeniami .
- ułatwią narzędziom do analizy statycznej lub ludziom
grepszukanie potencjalnych błędów.
- W przypadku niektórych aplikacji program powinien kontynuować normalnie, jeśli nieoczekiwanie otrzyma wartość domyślną. W przypadku innych aplikacji program powinien natychmiast zatrzymać się, jeśli otrzyma wartość domyślną, najlepiej w sposób informacyjny.
Myślę, że napięcie pomiędzy zerowym wzorcem obiektu a zerowymi wskaźnikami pochodzi z (5). Jeśli wykryjemy błędy wystarczająco wcześnie, (5) stanie się dyskusyjne.
Rozważmy ten język według języka:
C ++
Moim zdaniem klasa C ++ powinna generalnie być domyślnie konstruowana, ponieważ ułatwia interakcje z bibliotekami i ułatwia korzystanie z tej klasy w kontenerach. Upraszcza także dziedziczenie, ponieważ nie trzeba myśleć o tym, który konstruktor nadklasy ma zostać wywołany.
Oznacza to jednak, że nie możesz mieć pewności, czy wartość typu MyClassjest w stanie domyślnym, czy nie. Oprócz wprowadzenia w bool nonemptypolu wykonawczym podobnego lub podobnego pola do domyślnej powierzchni, najlepszym, co możesz zrobić, jest tworzenie nowych instancji MyClassw sposób, który zmusza użytkownika do sprawdzenia .
Polecam przy użyciu funkcji fabryki, która zwraca std::optional<MyClass>, std::pair<bool, MyClass>lub odwołania R-wartość na std::unique_ptr<MyClass>ile to możliwe.
Jeśli chcesz, aby funkcja fabryczna zwróciła jakiś „symbol zastępczy”, który różni się od skonstruowanej domyślnie MyClass, użyj a std::pairi upewnij się, że dokumentuje to, że twoja funkcja to robi.
Jeśli funkcja fabryczna ma unikalną nazwę, łatwo jest grepją nazwać i poszukać niechlujstwa. Jednak trudno jest grepw przypadkach, w których programista powinien był użyć funkcji fabrycznej, ale nie zrobił tego .
OCaml
Jeśli używasz języka takiego jak OCaml, możesz po prostu użyć optiontypu (w standardowej bibliotece OCaml) lub eithertypu (nie w standardowej bibliotece, ale łatwo ją rozwinąć). Lub defaultabletyp (tworzę termin defaultable).
type 'a option =
| None
| Some of 'a
type ('a, 'b) either =
| Left of 'a
| Right of 'b
type 'a defaultable =
| Default of 'a
| Real of 'a
Jak defaultablepokazano powyżej, jest lepszy niż para, ponieważ użytkownik musi wyodrębnić dopasowanie wzorca, aby wyodrębnić 'ai nie może po prostu zignorować pierwszego elementu pary.
Ekwiwalent defaultabletypu pokazanego powyżej może być używany w C ++ przy użyciu a std::variantz dwoma instancjami tego samego typu, ale części std::variantinterfejsu API są bezużyteczne, jeśli oba typy są takie same. Jest to również dziwne zastosowanie, std::variantponieważ jego konstruktory typów są bezimienne.
Pyton
Zresztą nie dostajesz żadnego sprawdzania czasu kompilacji dla Pythona. Jednak dynamiczne pisanie nie powoduje, że do umieszczenia kompilatora potrzebna jest instancja zastępcza dowolnego typu.
Polecam po prostu zgłoszenie wyjątku, gdy będziesz zmuszony utworzyć domyślną instancję.
Jeśli nie jest to do zaakceptowania, zaleciłbym utworzenie DefaultedMyClassodziedziczonego MyClassi zwrócenie go z funkcji fabrycznej. Dzięki temu zyskujesz elastyczność w zakresie „eliminowania” funkcji w domyślnych instancjach, jeśli zajdzie taka potrzeba.