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
grep
szukanie 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 MyClass
jest w stanie domyślnym, czy nie. Oprócz wprowadzenia w bool nonempty
polu wykonawczym podobnego lub podobnego pola do domyślnej powierzchni, najlepszym, co możesz zrobić, jest tworzenie nowych instancji MyClass
w 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::pair
i upewnij się, że dokumentuje to, że twoja funkcja to robi.
Jeśli funkcja fabryczna ma unikalną nazwę, łatwo jest grep
ją nazwać i poszukać niechlujstwa. Jednak trudno jest grep
w 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ć option
typu (w standardowej bibliotece OCaml) lub either
typu (nie w standardowej bibliotece, ale łatwo ją rozwinąć). Lub defaultable
typ (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 defaultable
pokazano powyżej, jest lepszy niż para, ponieważ użytkownik musi wyodrębnić dopasowanie wzorca, aby wyodrębnić 'a
i nie może po prostu zignorować pierwszego elementu pary.
Ekwiwalent defaultable
typu pokazanego powyżej może być używany w C ++ przy użyciu a std::variant
z dwoma instancjami tego samego typu, ale części std::variant
interfejsu API są bezużyteczne, jeśli oba typy są takie same. Jest to również dziwne zastosowanie, std::variant
ponieważ 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 DefaultedMyClass
odziedziczonego MyClass
i zwrócenie go z funkcji fabrycznej. Dzięki temu zyskujesz elastyczność w zakresie „eliminowania” funkcji w domyślnych instancjach, jeśli zajdzie taka potrzeba.