He estado pensando en este tema por un tiempo y me gustaría tener opiniones de otros desarrolladores.
Tiendo a tener un estilo de programación muy defensivo. Mi bloque o método típico se ve así:
T foo(par1, par2, par3, ...)
{
// Check that all parameters are correct, return undefined (null)
// or throw exception if this is not the case.
// Compute and (possibly) return result.
}
Además, durante el cálculo, verifico todos los punteros antes de desreferenciarlos. Mi idea es que, si hay algún error y algún puntero NULL debería aparecer en alguna parte, mi programa debería manejarlo bien y simplemente negarse a continuar el cálculo. Por supuesto, puede notificar el problema con un mensaje de error en el registro o algún otro mecanismo.
Para decirlo de una manera más abstracta, mi enfoque es
if all input is OK --> compute result
else --> do not compute result, notify problem
Otros desarrolladores, entre ellos algunos colegas míos, utilizan otra estrategia. Por ejemplo, no verifican los punteros. Asumen que un código debe recibir la entrada correcta y no debe ser responsable de lo que sucede si la entrada es incorrecta. Además, si una excepción de puntero NULL bloquea el programa, se encontrará un error más fácilmente durante las pruebas y tendrá más posibilidades de ser reparado.
Mi respuesta a esto es normalmente: ¿pero qué pasa si el error no se encuentra durante las pruebas y aparece cuando el cliente ya está utilizando el producto? ¿Cuál es la forma preferida para que el error se manifieste? ¿Debería ser un programa que no realiza una determinada acción, pero que aún puede continuar funcionando, o un programa que se bloquea y necesita reiniciarse?
Resumiendo
¿Cuál de los dos enfoques para manejar una entrada incorrecta recomendaría?
Inconsistent input --> no action + notification
o
Inconsistent input --> undefined behaviour or crash
Editar
Gracias por las respuestas y sugerencias. Soy fanático del diseño por contrato también. Pero incluso si confío en la persona que ha escrito el código llamando a mis métodos (tal vez soy yo mismo), todavía puede haber errores, lo que lleva a una entrada incorrecta. Por lo tanto, mi enfoque es nunca suponer que un método pasa la entrada correcta.
Además, usaría un mecanismo para detectar el problema y notificarlo. En un sistema de desarrollo, por ejemplo, abriría un cuadro de diálogo para notificar al usuario. En un sistema de producción, solo escribiría alguna información en el registro. No creo que las comprobaciones adicionales puedan generar problemas de rendimiento. No estoy seguro de si las afirmaciones son suficientes, si se desactivan en un sistema de producción: tal vez ocurra alguna situación en la producción que no se haya producido durante las pruebas.
De todos modos, me sorprendió mucho que muchas personas sigan el enfoque opuesto: dejan que la aplicación se bloquee "a propósito" porque sostienen que esto hará que sea más fácil encontrar errores durante las pruebas.
fuente
Respuestas:
Lo has entendido bien. Se paranoico. No confíes en otro código, incluso si es tu propio código. Olvidas las cosas, haces cambios, el código evoluciona. No confíes en el código externo.
Se hizo un buen punto arriba: ¿qué pasa si las entradas no son válidas pero el programa no se bloquea? Luego obtienes basura en la base de datos y errores en la línea.
Cuando se me solicita un número (por ejemplo, precio en dólares o número de unidades), me gusta ingresar "1e9" y ver qué hace el código. Puede pasar.
Hace cuatro décadas, cuando obtuve mi licenciatura en Ciencias de la Computación de UCBerkeley, nos dijeron que un buen programa es un 50% de manejo de errores. Se paranoico.
fuente
Ya tienes la idea correcta
¿Cuál de los dos enfoques para manejar una entrada incorrecta recomendaría?
o mejor
Realmente no puede adoptar un enfoque de corte de cookies para la programación (sí podría), pero terminaría con un diseño formulado que hace las cosas por costumbre en lugar de por elección consciente.
Tempera el dogmatismo con el pragmatismo.
Steve McConnell lo dijo mejor
Steve McConnell prácticamente escribió el libro ( Código completo ) sobre programación defensiva y este fue uno de los métodos que aconsejó que siempre debe validar sus entradas.
No recuerdo si Steve mencionó esto, sin embargo, debe considerar hacerlo para métodos y funciones no privados , y solo para otros cuando lo considere necesario.
fuente
Aquí no hay una respuesta "correcta", particularmente sin especificar el idioma, el tipo de código y el tipo de producto en el que podría entrar el código. Considerar:
El lenguaje importa. En Objective-C, a menudo está bien enviar mensajes a nil; no pasa nada, pero el programa tampoco se bloquea. Java no tiene punteros explícitos, por lo que los punteros nulos no son una gran preocupación allí. En C, debes ser un poco más cuidadoso.
Ser paranoico es sospechar o desconfiar de forma injustificada e injustificada. Probablemente no sea mejor para el software que para las personas.
Su nivel de preocupación debe ser acorde con el nivel de riesgo en el código y la probable dificultad de identificar cualquier problema que aparezca. ¿Qué pasa en el peor de los casos? ¿El usuario reinicia el programa y continúa donde lo dejó? La empresa pierde millones de dólares?
No siempre se puede identificar una entrada incorrecta. Puede comparar religiosamente sus punteros a cero, pero eso solo atrapa uno de los 2 ^ 32 valores posibles, casi todos los cuales son malos.
Existen muchos mecanismos diferentes para tratar los errores. De nuevo, depende hasta cierto punto del idioma. Puede usar macros de aserción, declaraciones condicionales, pruebas unitarias, manejo de excepciones, diseño cuidadoso y otras técnicas. Ninguno de ellos es infalible, y ninguno es apropiado para cada situación.
Por lo tanto, se reduce principalmente a donde desea poner la responsabilidad. Si está escribiendo una biblioteca para que otros la usen, probablemente quiera ser tan cuidadoso como sea razonablemente posible con las entradas que reciba, y hacer todo lo posible para emitir errores útiles cuando sea posible. En sus propias funciones y métodos privados, puede usar afirmaciones para detectar errores tontos, pero de lo contrario responsabilizar a la persona que llama (que es usted) de no pasar basura.
fuente
Definitivamente debería haber una notificación, como una excepción lanzada. Sirve para avisar a otros codificadores que pueden estar haciendo mal uso del código que escribió (tratando de usarlo para algo que no estaba destinado a hacer) de que su entrada no es válida o genera errores. Esto es muy útil para rastrear errores, mientras que si simplemente devuelve nulo, su código continuará hasta que intenten usar el resultado y obtengan una excepción de un código diferente.
Si su código encuentra un error durante una llamada a otro código (tal vez una actualización fallida de la base de datos) que está más allá del alcance de ese código en particular, realmente no tiene control sobre él y su único recurso es lanzar una excepción que explique qué ya sabes (solo lo que te indica el código que has llamado). Si sabe que ciertas entradas conducirán inevitablemente a ese resultado, simplemente no puede molestarse en ejecutar su código y lanzar una excepción que indique qué entrada no es válida y por qué.
En una nota más relacionada con el usuario final, es mejor devolver algo descriptivo pero simple para que cualquiera pueda entenderlo. Si su cliente llama y dice "el programa se bloqueó, corríjalo", tiene mucho trabajo en sus manos para rastrear qué salió mal y por qué, y con la esperanza de poder reproducir el problema. El uso de un manejo adecuado de las excepciones no solo puede evitar un bloqueo, sino también proporcionar información valiosa. Una llamada de un cliente que dice "El programa me está dando un error. Dice 'XYZ no es una entrada válida para el método M, porque Z es demasiado grande", o algo así, incluso si no tienen idea de lo que significa, usted saber exactamente dónde mirar. Además, dependiendo de las prácticas comerciales de su empresa, es posible que ni siquiera sea usted el que solucione estos problemas, por lo que es mejor dejarles un buen mapa.
Entonces, la versión corta de mi respuesta es que su primera opción es la mejor.
fuente
Luché con este mismo problema mientras asistía a una clase universitaria de programación. Me incliné hacia el lado paranoico y tiendo a verificar todo, pero me dijeron que se trataba de un comportamiento equivocado.
Nos enseñaron "Diseño por contrato". El énfasis es que las condiciones previas, las invariantes y las condiciones posteriores se especifiquen en los comentarios y documentos de diseño. Como la persona que implementa mi parte del código, debería confiar en el arquitecto de software y empoderarlos siguiendo las especificaciones que incluirían las condiciones previas (qué entradas deben poder manejar mis métodos y qué entradas no se me enviarán) . La comprobación excesiva en cada llamada a método produce hinchazón.
Las afirmaciones deben usarse durante las iteraciones de compilación para verificar la corrección del programa (validación de precondiciones, invariantes, condiciones posteriores). Las afirmaciones se convertirían en la compilación de producción.
fuente
El uso de "afirmaciones" es el camino a seguir para notificar a los demás desarrolladores que están haciendo mal, en los métodos de "privado" solamente por supuesto . Habilitar / deshabilitarlos es solo un indicador para agregar / eliminar en el momento de la compilación y, como tal, es fácil eliminar aserciones del código de producción. También hay una gran herramienta para saber si de alguna manera lo estás haciendo mal en tus propios métodos.
En cuanto a la verificación de los parámetros de entrada dentro de los métodos públicos / protegidos, prefiero trabajar a la defensiva y verificar los parámetros y lanzar InvalidArgumentException o similares. Por eso hay aquí para. También depende de si está escribiendo una API o no. Si es una API, y aún más si es de código cerrado, valide mejor todo para que los desarrolladores sepan con precisión qué salió mal. De lo contrario, si la fuente está disponible para otros desarrolladores, no es en blanco y negro. Solo sea consistente con sus elecciones.
Editar: solo para agregar que si mira, por ejemplo, en el Oracle JDK, verá que nunca verifican "nulo" y dejan que el código se bloquee. Dado que de todos modos va a lanzar una NullPointerException, ¿por qué molestarse en buscar nulo y lanzar una excepción explícita? Supongo que tiene sentido.
fuente