No lo use tampoco si puede evitarlo. Utilice una función de fábrica que devuelva un tipo de opción, una tupla o un tipo de suma dedicada. En otras palabras, represente un valor potencialmente predeterminado con un tipo diferente de un valor garantizado que no debe ser predeterminado.
Primero, enumeremos los desiderata, luego pensemos cómo podemos expresar esto en algunos lenguajes (C ++, OCaml, Python)
- detectar el uso no deseado de un objeto predeterminado en tiempo de compilación.
- haga obvio si un valor dado está potencialmente predeterminado o no mientras lee el código.
- elija nuestro valor predeterminado razonable, si corresponde, una vez por tipo . Definitivamente no elija un valor predeterminado potencialmente diferente en cada sitio de llamada .
- facilite que las herramientas de análisis estático o un humano
grep
busquen posibles errores.
- Para algunas aplicaciones , el programa debería continuar normalmente si inesperadamente se le da un valor predeterminado. Para otras aplicaciones , el programa debe detenerse inmediatamente si se le da un valor predeterminado, idealmente de manera informativa.
Creo que la tensión entre el Patrón de objetos nulos y los punteros nulos proviene de (5). Sin embargo, si podemos detectar errores lo suficientemente temprano, (5) se vuelve discutible.
Consideremos este idioma por idioma:
C ++
En mi opinión, una clase de C ++ generalmente debería ser construible por defecto, ya que facilita las interacciones con las bibliotecas y facilita el uso de la clase en contenedores. También simplifica la herencia ya que no necesita pensar a qué constructor de superclase llamar.
Sin embargo, esto significa que no puede saber con certeza si un valor de tipo MyClass
está en el "estado predeterminado" o no. Además de poner en un bool nonempty
campo o similar a la superficie predeterminada en tiempo de ejecución, lo mejor que puede hacer es producir nuevas instancias de MyClass
una manera que obligue al usuario a verificarlo .
Recomiendo usar una función de fábrica que devuelva a std::optional<MyClass>
, std::pair<bool, MyClass>
o una referencia de valor r a a std::unique_ptr<MyClass>
si es posible.
Si desea que su función de fábrica devuelva algún tipo de estado de "marcador de posición" que sea distinto de un estado predeterminado MyClass
, use std::pair
ay asegúrese de documentar que su función lo hace.
Si la función de fábrica tiene un nombre único, es fácil grep
para el nombre y buscar descuido. Sin embargo, es difícil grep
para los casos en que el programador debería haber utilizado la función de fábrica pero no lo hizo .
OCaml
Si está utilizando un lenguaje como OCaml, puede usar un option
tipo (en la biblioteca estándar de OCaml) o un either
tipo (no en la biblioteca estándar, pero fácil de usar ). O un defaultable
tipo (estoy inventando el término 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 se muestra arriba es mejor que un par porque el usuario debe combinar patrones para extraer el 'a
y no puede simplemente ignorar el primer elemento del par.
El equivalente del defaultable
tipo que se muestra arriba se puede usar en C ++ usando a std::variant
con dos instancias del mismo tipo, pero partes de la std::variant
API no se pueden usar si ambos tipos son iguales. También es un uso extraño std::variant
ya que sus constructores tipo no tienen nombre.
Pitón
De todos modos, no obtiene ninguna comprobación de tiempo de compilación para Python. Pero, al escribir de forma dinámica, generalmente no hay circunstancias en las que necesite una instancia de marcador de posición de algún tipo para aplacar el compilador.
Recomendaría simplemente lanzar una excepción cuando se vea obligado a crear una instancia predeterminada.
Si eso no es aceptable, recomendaría crear uno DefaultedMyClass
que herede MyClass
y devolverlo de su función de fábrica. Eso le da flexibilidad en términos de funcionalidad de "desconexión" en casos predeterminados si es necesario.