Não use se você puder evitá-lo. Use uma função de fábrica retornando um tipo de opção, uma tupla ou um tipo de soma dedicado. Em outras palavras, represente um valor potencialmente padrão com um tipo diferente de um valor garantido para não ser padronizado.
Primeiro, vamos listar os desiderata e depois pensar em como podemos expressar isso em algumas linguagens (C ++, OCaml, Python)
- captura o uso indesejado de um objeto padrão em tempo de compilação.
- torne óbvio se um determinado valor é potencialmente padrão ou não ao ler o código.
- escolha nosso valor padrão sensível, se aplicável, uma vez por tipo . Definitivamente, não escolha um padrão potencialmente diferente em cada site de chamada .
- facilite a busca de ferramentas de análise estática ou de um ser humano
grep
em busca de possíveis erros.
- Para alguns aplicativos , o programa deve continuar normalmente se receber um valor padrão inesperado. Para outras aplicações , o programa deve parar imediatamente se receber um valor padrão, idealmente de maneira informativa.
Eu acho que a tensão entre o Padrão de Objeto Nulo e os ponteiros nulos vem de (5). Se pudermos detectar erros cedo o suficiente, (5) se torna discutível.
Vamos considerar esse idioma por idioma:
C ++
Na minha opinião, uma classe C ++ geralmente deve ser construtiva por padrão, pois facilita as interações com as bibliotecas e facilita o uso da classe em contêineres. Isso também simplifica a herança, pois você não precisa pensar em qual construtor de superclasse chamar.
No entanto, isso significa que você não pode ter certeza se um valor do tipo MyClass
está no "estado padrão" ou não. Além de inserir um bool nonempty
campo ou um campo semelhante à superfície padrão em tempo de execução, o melhor que você pode fazer é produzir novas instâncias de MyClass
uma maneira que obrigue o usuário a verificá-la .
Eu recomendo usar uma função de fábrica que retorne uma std::optional<MyClass>
, std::pair<bool, MyClass>
ou uma referência de valor r para a, std::unique_ptr<MyClass>
se possível.
Se você deseja que sua função de fábrica retorne algum tipo de estado de "espaço reservado" que seja diferente de um construído por padrão MyClass
, use a std::pair
e certifique-se de documentar que sua função faz isso.
Se a função de fábrica tiver um nome exclusivo, será fácil grep
identificá-lo e procurar desleixo. No entanto, é difícil grep
para casos em que o programador deveria ter usado a função de fábrica, mas não o fez .
OCaml
Se você estiver usando um idioma como OCaml, poderá usar apenas um option
tipo (na biblioteca padrão do OCaml) ou um either
tipo (não na biblioteca padrão, mas fácil de criar). Ou um defaultable
tipo (estou inventando o termo 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
A, defaultable
como mostrado acima, é melhor que um par, porque o usuário deve corresponder ao padrão para extrair o 'a
e não pode simplesmente ignorar o primeiro elemento do par.
O equivalente do defaultable
tipo mostrado acima pode ser usado em C ++ usando a std::variant
com duas instâncias do mesmo tipo, mas partes da std::variant
API não podem ser usadas se os dois tipos forem iguais. Também é um uso estranho, std::variant
já que seus construtores de tipo não têm nome.
Python
Você não recebe nenhuma verificação em tempo de compilação para o Python. Mas, sendo digitado dinamicamente, geralmente não há circunstâncias em que você precise de uma instância de espaço reservado de algum tipo para aplacar o compilador.
Eu recomendaria apenas lançar uma exceção quando você fosse forçado a criar uma instância padrão.
Se isso não for aceitável, eu recomendaria criar um DefaultedMyClass
que herda MyClass
e retorne esse da sua função de fábrica. Isso oferece flexibilidade em termos de funcionalidade "stubbing out" em instâncias padrão, se necessário.