Entonces, no sé si este es un diseño de código bueno o malo, así que pensé que sería mejor preguntar.
Con frecuencia, creo métodos que procesan datos que involucran clases y, a menudo, realizo muchas comprobaciones en los métodos para asegurarme de que no obtengo referencias nulas u otros errores de antemano.
Para un ejemplo muy básico:
// fields and properties
private Entity _someEntity;
public Entity SomeEntity => _someEntity;
public void AssignEntity(Entity entity){
_someEntity = entity;
}
public void SetName(string name)
{
if (_someEntity == null) return; //check to avoid null ref
_someEntity.Name = name;
label.SetText(_someEntity.Name);
}
Entonces, como puede ver, estoy buscando nulos cada vez. ¿Pero el método no debería tener esta verificación?
Por ejemplo, si el código externo limpia los datos de antemano para que los métodos no necesiten validarse como se muestra a continuación:
if(entity != null) // this makes the null checks redundant in the methods
{
Manager.AssignEntity(entity);
Manager.SetName("Test");
}
En resumen, si los métodos deben ser "validación de datos" y luego se procesan en los datos, o debe garantizarse antes de llamar al método, y si no lo valida antes de llamar al método, debería arrojar un error (o detectar el error)?
fuente
Respuestas:
El problema con su ejemplo básico no es la verificación nula, es la falla silenciosa .
Los errores de puntero / referencia nulos, la mayoría de las veces, son errores de programador. Los errores del programador a menudo se resuelven mejor al fallar de inmediato y en voz alta. Tiene tres formas generales de abordar el problema en esta situación:
Una tercera solución es más trabajo pero es mucho más robusta:
_someEntity
estar en un estado no válido. Una forma de hacerlo es deshacerse de élAssignEntity
y requerirlo como parámetro para la creación de instancias. Otras técnicas de inyección de dependencia también pueden ser útiles.En algunas situaciones, tiene sentido verificar la validez de todos los argumentos de la función antes de cualquier trabajo que esté haciendo, y en otras tiene sentido decirle a la persona que llama que es responsable de garantizar que sus entradas sean válidas y no verificar. El extremo del espectro en el que se encuentre dependerá de su dominio problemático. La tercera opción tiene un beneficio significativo, ya que puede, hasta cierto punto, hacer que el compilador haga cumplir que la persona que llama hace todo correctamente.
Si la tercera opción no es una opción, entonces, en mi opinión, realmente no importa siempre que no falle silenciosamente. Si no verificar nulo hace que el programa explote instantáneamente, está bien, pero si en cambio corrompe silenciosamente los datos para causar problemas en el futuro, entonces es mejor verificarlos y tratarlos de inmediato.
fuente
null
es incorrecto o no válido porque en mi hipotética biblioteca he definido los tipos de datos y he definido qué es válido y qué no. Lo llaman "suposiciones forzosas", pero adivinen qué: eso es lo que hace cada biblioteca de software existente. Hay momentos en que nulo es un valor válido, pero eso no es "siempre".null
correctamente" - Esto es un malentendido de lo que significa un manejo adecuado. Para la mayoría de los programadores de Java, significa lanzar una excepción para cualquier entrada no válida. Utilizando@ParametersAreNonnullByDefault
, significa también para todosnull
, a menos que el argumento esté marcado como@Nullable
. Hacer cualquier otra cosa significa alargar el camino hasta que finalmente sople en otro lugar y, por lo tanto, también alargar el tiempo perdido en la depuración. Bueno, al ignorar los problemas, puede lograr que nunca explote, pero el comportamiento incorrecto está bastante garantizado.Exception
son debates completamente ajenos. (Estoy de acuerdo en que ambos son problemáticos). Para este ejemplo específico,null
obviamente no tiene sentido aquí si el propósito de la clase es invocar métodos en el objeto.Como
_someEntity
se puede modificar en cualquier etapa, entonces tiene sentido probarlo cada vez queSetName
se llama. Después de todo, podría haber cambiado desde la última vez que se llamó a ese método. Pero tenga en cuenta que el códigoSetName
no es seguro para subprocesos, por lo que puede realizar esa verificación en un subproceso,_someEntity
establecerlo ennull
otro y luego el código se abrirá deNullReferenceException
todos modos.Por lo tanto, un enfoque para este problema es ponerse aún más a la defensiva y realizar una de las siguientes acciones:
_someEntity
y hacer referencia a esa copia a través del método,_someEntity
para asegurarse de que no cambie a medida que se ejecuta el método,try/catch
realice la misma acción en cualquieraNullReferenceException
de los que ocurran dentro del método como lo haría en la verificación nula inicial (en este caso, unanop
acción).Pero lo que realmente debe hacer es detenerse y hacerse la pregunta: ¿realmente necesita permitir
_someEntity
que se sobrescriba? ¿Por qué no configurarlo una vez, a través del constructor? De esa manera, solo necesita hacer esa comprobación nula una vez:Si es posible, este es el enfoque que recomendaría adoptar en tales situaciones. Si no puede tener en cuenta la seguridad de los hilos al elegir cómo manejar posibles nulos.
Como comentario adicional, siento que su código es extraño y podría mejorarse. Todo:
se puede reemplazar con:
Que tiene la misma funcionalidad, salvo por poder establecer el campo a través del configurador de propiedades, en lugar de un método con un nombre diferente.
fuente
Esta es tu elección.
Al crear un
public
método, está ofreciendo al público la oportunidad de llamarlo. Esto siempre viene junto con un contrato implícito sobre cómo llamar a ese método y qué esperar al hacerlo. Este contrato puede (o no) incluir " sí, uhhm, si lo pasanull
como un valor de parámetro, explotará en su cara ". Otras partes de ese contrato podrían ser " oh, por cierto: cada vez que dos hilos ejecutan este método al mismo tiempo, un gatito muere ".El tipo de contrato que desea diseñar depende de usted. Lo importante es
crear una API que sea útil y consistente y
documentarlo bien, especialmente para esos casos extremos.
¿Cuáles son los casos probables de cómo se llama su método?
fuente
Las otras respuestas son buenas; Me gustaría ampliarlos haciendo algunos comentarios sobre los modificadores de accesibilidad. Primero, qué hacer si
null
nunca es válido:Los métodos públicos y protegidos son aquellos en los que no controla a la persona que llama. Deberían tirar cuando pasen nulo. De esa forma entrenas a tus llamantes para que nunca te pasen un valor nulo, porque les gustaría que su programa no se bloquee.
internas y privadas métodos son aquellos en los que no controlas la persona que llama. Deben afirmarse cuando pasan nulo; probablemente se estrellarán más tarde, pero al menos primero obtuviste la afirmación y la oportunidad de entrar en el depurador. Una vez más, desea capacitar a las personas que llaman para que lo llamen correctamente haciendo que duela cuando no lo hacen.
Ahora, ¿qué haces si podría ser válido para que se pase un valor nulo? Evitaría esta situación con el patrón de objeto nulo . Es decir: haga una instancia especial del tipo y requiera que se use cada vez que se necesite un objeto no válido . Ahora está de vuelta en el agradable mundo de lanzar / afirmar sobre cada uso de nulo.
Por ejemplo, no quieres estar en esta situación:
y en el sitio de la llamada:
Debido a que (1) está entrenando a las personas que llaman que nulo es un buen valor, y (2) no está claro cuáles son las semánticas de ese valor. Más bien, haz esto:
Y ahora el sitio de la llamada se ve así:
y ahora el lector del código sabe lo que significa.
fuente
if
s para los objetos nulos, sino que use el polimorfismo para que se comporten correctamente.EmptyQueue
tipo que arroja cuando se retira, y así sucesivamente.Person
clase fuera inmutable y no contuviera un constructor de argumento cero, ¿cómo construirías tuApproverNotRequired
o tusApproverUnknown
instancias? En otras palabras, ¿qué valores pasarías al constructor?Las otras respuestas señalan que su código se puede limpiar para que no necesite una verificación nula donde lo tenga, sin embargo, para obtener una respuesta general sobre lo que puede ser una verificación nula útil para considerar el siguiente código de muestra:
Esto le da una ventaja para el mantenimiento. Si alguna vez ocurre un defecto en su código donde algo pasa un objeto nulo al método, obtendrá información útil del método sobre qué parámetro era nulo. Sin las comprobaciones, obtendrá una NullReferenceException en la línea:
Y (dado que la propiedad Something es un tipo de valor) a o b podría ser nulo y no tendrá forma de saber cuál de la traza de la pila. Con las comprobaciones, sabrá exactamente qué objeto era nulo, lo que puede ser enormemente útil al intentar eliminar un defecto.
Me gustaría señalar que el patrón en su código original:
Puede tener sus usos en ciertas circunstancias, también puede (posiblemente más a menudo) darle una muy buena forma de enmascarar los problemas subyacentes en su base de código.
fuente
No, para nada, pero depende.
Solo realiza una verificación de referencia nula si desea reaccionar al respecto.
Si realiza una verificación de referencia nula y arroja una excepción marcada , obliga al usuario del método a reaccionar y le permite recuperarse. El programa aún funciona como se esperaba.
Si no realiza una verificación de referencia nula (o arroja una excepción no verificada), podría arrojar una verificación no verificada
NullReferenceException
, que generalmente no es manejada por el usuario del método e incluso podría cerrar la aplicación. Esto significa que la función es obviamente completamente incomprendida y, por lo tanto, la aplicación es defectuosa.fuente
Algo así como una extensión de la respuesta de @ null, pero en mi experiencia, hacer comprobaciones nulas sin importar qué.
Una buena programación defensiva es la actitud de codificar la rutina actual de forma aislada, bajo el supuesto de que todo el resto del programa está tratando de bloquearla. Cuando escribe código de misión crítica donde el fracaso no es una opción, esta es la única forma de hacerlo.
Ahora, cómo manejas realmente un
null
valor, eso depende en gran medida de tu caso de uso.Si
null
es un valor aceptable, por ejemplo, cualquiera de los parámetros segundo a quinto de la rutina MSVC _splitpath (), entonces el comportamiento correcto es tratarlo en silencio.Si
null
no debería suceder nunca al llamar a la rutina en cuestión, es decir, en el caso del OP, el contrato de API requiereAssignEntity()
que se haya llamado con una validez yEntity
luego lanzar una excepción, o de lo contrario no garantizará que haga mucho ruido en el proceso.Nunca se preocupe por el costo de un cheque nulo, son muy baratos si suceden, y con los compiladores modernos, el análisis de código estático puede eliminarlos por completo si el compilador determina que no va a suceder.
fuente