¿Se verifica la condición redundante contra las mejores prácticas?

16

He estado desarrollando software durante los últimos tres años, pero recientemente me desperté de lo ignorante que soy de las buenas prácticas. Esto me ha llevado a comenzar a leer el libro Clean Code , que está cambiando mi vida para mejor, pero estoy luchando por comprender algunos de los mejores enfoques para escribir mis programas.

Tengo un programa de Python en el que ...

  1. use argparse required=Truepara imponer dos argumentos, que son ambos nombres de archivo. el primero es el nombre del archivo de entrada, el segundo es el nombre del archivo de salida
  2. tener una función readFromInputFileque primero verifica que se haya ingresado un nombre de archivo de entrada
  3. tener una función writeToOutputFileque primero verifica que se haya ingresado un nombre de archivo de salida

Mi programa es lo suficientemente pequeño como para hacerme creer que la verificación en los números 2 y 3 es redundante y debe eliminarse, liberando así ambas funciones de una ifcondición innecesaria . Sin embargo, también se me ha hecho creer que "la doble verificación está bien" y puede ser la solución correcta en un programa donde las funciones podrían llamarse desde una ubicación diferente donde no se realiza el análisis de los argumentos.

(Además, si falla la lectura o escritura, tengo una try excepten cada función para generar un mensaje de error apropiado).

Mi pregunta es: ¿es mejor evitar todas las verificaciones de condición redundantes? ¿Debería la lógica de un programa ser tan sólida que las comprobaciones solo deben hacerse una vez? ¿Hay buenos ejemplos que ilustren esto o lo contrario?

EDITAR: ¡Gracias a todos por las respuestas! He aprendido algo de cada uno. Ver tantas perspectivas me da una mejor comprensión de cómo abordar este problema y determinar una solución basada en mis requisitos. ¡Gracias!

tesis
fuente
Aquí hay una versión muy generalizada de su pregunta: softwareengineering.stackexchange.com/questions/19549/… . No diría que es duplicado ya que tiene un enfoque bastante más amplio, pero tal vez ayude.
Doc Brown

Respuestas:

15

Lo que está pidiendo se llama "robustez", y no hay una respuesta correcta o incorrecta. Depende del tamaño y la complejidad del programa, la cantidad de personas que trabajan en él y la importancia de detectar fallas.

En los programas pequeños que escribe solo y solo para usted, la robustez suele ser una preocupación mucho menor que cuando va a escribir un programa complejo que consta de múltiples componentes, tal vez escritos por un equipo. En tales sistemas, existen límites entre los componentes en forma de API públicas, y en cada límite, a menudo es una buena idea validar los parámetros de entrada, incluso si "la lógica del programa debe ser tan sólida que esas comprobaciones sean redundantes". ". Eso hace que la detección de errores sea bastante más fácil y ayuda a mantener los tiempos de depuración más pequeños.

En su caso, debe decidir por sí mismo, qué tipo de ciclo de vida espera para su programa. ¿Es un programa que espera utilizar y mantener durante años? A continuación, agregar una comprobación redundante es, probablemente, mejor, ya que no será poco probable que su código será reprogramado en el futuro y sus e ready writefunciones podría ser utilizado en un contexto diferente.

¿O es un pequeño programa solo para fines de aprendizaje o diversión? Entonces esas verificaciones dobles no serán necesarias.

En el contexto del "Código limpio", uno podría preguntar si una doble verificación viola el principio DRY. En realidad, a veces lo hace, al menos en un grado menor: la validación de entrada puede interpretarse como parte de la lógica empresarial de un programa, y ​​tener esto en dos lugares puede provocar los problemas de mantenimiento habituales causados ​​por la violación de DRY. La robustez frente a DRY a menudo es una compensación: la robustez requiere redundancia en el código, mientras que DRY intenta minimizar la redundancia. Y con la creciente complejidad del programa, la robustez se vuelve cada vez más importante que estar SECO en la validación.

Finalmente, permítanme dar un ejemplo de lo que eso significa en su caso. Supongamos que sus requisitos cambian a algo como

  • el programa también funcionará con un argumento, el nombre del archivo de entrada, si no se proporciona un nombre de archivo de salida, se construye automáticamente a partir del nombre del archivo de entrada reemplazando el sufijo.

¿Eso hace que sea probable que necesite cambiar su doble validación en dos lugares? Probablemente no, tal requisito lleva a un cambio cuando se llama argparse, pero ningún cambio en writeToOutputFile: esa función aún requerirá un nombre de archivo. Entonces, en su caso, votaría por hacer la validación de entrada dos veces, el riesgo de tener problemas de mantenimiento por tener dos lugares para cambiar es, en mi humilde opinión, mucho menor que el riesgo de tener problemas de mantenimiento debido a errores enmascarados causados ​​por muy pocas verificaciones.

Doc Brown
fuente
"... límites entre componentes en forma de API públicas ..." Observo que "las clases saltan los límites", por así decirlo. Entonces, lo que se necesita es una clase; Una clase de dominio empresarial coherente. De este OP deduzco que el principio omnipresente de "es simple, así que no necesito una clase" está funcionando aquí. Podría haber una clase simple que envuelva el "objeto primario", aplicando reglas de negocio como "un archivo debe tener un nombre" que no solo SECA el código existente sino que lo mantiene SECO en el futuro.
radarbob
@radarbob: lo que escribí no está restringido a OOP o componentes en forma de clases. Esto también se aplica a las bibliotecas arbitrarias con una API pública, orientada a objetos o no.
Doc Brown,
5

La redundancia no es el pecado. La redundancia innecesaria es.

  1. Si readFromInputFile()y writeToOutputFile()son funciones públicas (y según las convenciones de nomenclatura de Python, ya que sus nombres no comenzaron con dos guiones bajos), las funciones podrían ser utilizadas algún día por alguien que evitó por completo el argumento. Eso significa que cuando dejan de lado los argumentos, no pueden ver su mensaje de error de argparse personalizado.

  2. Si readFromInputFile()y writeToOutputFile()comprobar los parámetros de sí mismos, de nuevo la oportunidad de mostrar un mensaje de error que explica la necesidad de que los nombres de archivo.

  3. Si readFromInputFile()y writeToOutputFile()no comprobar si los parámetros a sí mismos, no se muestra ningún mensaje de error personalizado. El usuario tendrá que descubrir la excepción resultante por su cuenta.

Todo se reduce a 3. Escriba algún código que realmente use estas funciones, evite argparse y genere el mensaje de error. Imagine que no ha mirado dentro de estas funciones en absoluto y solo está confiando en sus nombres para proporcionar la comprensión suficiente para usar. Cuando eso es todo lo que sabes, ¿hay alguna forma de confundirse con la excepción? ¿Es necesario un mensaje de error personalizado?

Apagar la parte de tu cerebro que recuerda el interior de esas funciones es difícil. Tanto es así que algunos recomiendan escribir el código de uso antes del código que se usa. De esa manera, llega al problema sabiendo cómo se ven las cosas desde afuera. No tiene que hacer TDD para hacerlo, pero si lo hace, ya entrará desde afuera primero.

naranja confitada
fuente
4

La medida en que hace que sus métodos sean independientes y reutilizables es algo bueno. Eso significa que los métodos deben ser indulgentes en lo que aceptan y deben tener resultados bien definidos (precisos en lo que devuelven). Eso también significa que deberían ser capaces de manejar con gracia todo lo que se les pasa y no hacer suposiciones sobre la naturaleza de la entrada, la calidad, el tiempo, etc.

Si un programador tiene la costumbre de escribir métodos que hacen suposiciones sobre lo que se pasa, en base a ideas como "si esto se rompe, tenemos cosas más importantes de qué preocuparse" o "el parámetro X no puede tener el valor Y porque el resto de el código lo impide ", de repente ya no tienes componentes independientes y desacoplados. Sus componentes dependen esencialmente del sistema más amplio. Ese es un tipo de acoplamiento sutil y ajustado y conduce a un costo total de propiedad que aumenta exponencialmente a medida que aumenta la complejidad del sistema.

Tenga en cuenta que esto puede significar que está validando la misma información más de una vez. Pero esto está bien. Cada componente es responsable de su propia validación a su manera . Esto no es una violación de DRY, porque las validaciones son por componentes independientes desacoplados, y un cambio en la validación en uno no necesariamente tiene que ser replicado exactamente en el otro. No hay redundancia aquí. X tiene la responsabilidad de verificar sus entradas para sus propias necesidades y pasar algunas a Y. Y tiene su propia responsabilidad de verificar sus propias entradas para sus necesidades .

Brad Thomas
fuente
1

Suponga que tiene una función (en C)

void readInputFile (const char* path);

Y no puede encontrar ninguna documentación sobre el camino. Y luego miras la implementación y dice

void readInputFile (const char* path)
{
    assert (path != NULL && strlen (path) > 0);

Esto no solo prueba la entrada a la función, sino que también le dice al usuario de la función que la ruta no puede ser NULL o una cadena vacía.

gnasher729
fuente
0

En general, la doble verificación no siempre es buena o mala. Siempre hay muchos aspectos de la pregunta en su caso particular de los que depende el asunto. En tu caso:

  • ¿Qué tan grande es el programa? Cuanto más pequeño es, más obvio es que la persona que llama hace lo correcto. Cuando su programa se hace más grande, se vuelve más importante especificar exactamente cuáles son las condiciones previas y posteriores de cada rutina.
  • los argumentos ya están verificados por el argparsemódulo. A menudo es una mala idea usar una biblioteca y luego hacer su trabajo usted mismo. ¿Por qué usar la biblioteca entonces?
  • ¿Qué tan probable es que su método sea reutilizado en un contexto donde la persona que llama no verifica los argumentos? Cuanto más probable es, más importante es validar los argumentos.
  • ¿Qué ocurre si un argumento no van a faltar? No encontrar un archivo de entrada probablemente dejará de procesar directamente. Ese es probablemente un modo de falla obvio que es fácil de rectificar. El tipo insidioso de errores son aquellos en los que el programa sigue trabajando alegremente y produce resultados incorrectos sin que usted lo note .
Kilian Foth
fuente
0

Sus comprobaciones dobles parecen estar en lugares donde se usan raramente. Entonces, estas comprobaciones simplemente están haciendo que su programa sea más robusto:

Un cheque demasiado no dolerá, uno menos podría hacerlo.

Sin embargo, si está verificando dentro de un ciclo que se repite con frecuencia, debe pensar en eliminar la redundancia, incluso si el cheque en sí mismo en la mayoría de los casos no es costoso en comparación con lo que sigue después del cheque.

qwerty_so
fuente
Y como ya lo tiene, no vale la pena eliminarlo, a menos que esté en un bucle o algo así.
StarWeaver
0

Quizás podrías cambiar tu punto de vista:

Si algo sale mal, ¿cuál es el resultado? ¿Hará daño a su aplicación / al usuario?

Por supuesto, siempre se podría discutir si más o menos controles son mejores o peores, pero esa es una pregunta bastante escolástica. Y dado que se trata de software del mundo real , hay consecuencias en el mundo real.

Por el contexto que estás dando:

  • un archivo de entrada A
  • un archivo de salida B

Asumo que está haciendo la transformación de A a B . Si A y B son pequeños y la transformación es pequeña, ¿cuáles son las consecuencias?

1) Olvidó especificar dónde leer: entonces el resultado es nada . Y el tiempo de ejecución será más corto de lo esperado. Mire el resultado, o mejor: busque un resultado faltante, vea que invocó el comando de manera incorrecta, comience de nuevo y todo está bien nuevamente

2) Olvidó especificar el archivo de salida. Esto da como resultado diferentes escenarios:

a) La entrada se lee de una vez. Entonces comienza la transformación y el resultado debe escribirse, pero en su lugar recibirá un error. Dependiendo del tiempo, su usuario tiene que esperar (dependiendo de la masa de datos que podría procesarse), esto podría ser molesto.

b) La entrada se lee paso a paso. Luego, el proceso de escritura se cierra inmediatamente como en (1) y el usuario comienza de nuevo.

La verificación descuidada puede verse como aceptable en algunas circunstancias. Depende totalmente de su caso de uso y cuál es su intención.

Además: debe evitar la paranoia y no hacer demasiadas verificaciones dobles.

Thomas Junk
fuente
0

Yo diría que las pruebas no son redundantes.

  • Tiene dos funciones públicas que requieren un nombre de archivo como parámetro de entrada. Es apropiado validar sus parámetros. Las funciones podrían utilizarse potencialmente en cualquier programa que necesite su funcionalidad.
  • Tiene un programa que requiere dos argumentos que deben ser nombres de archivo. Resulta usar las funciones. Es apropiado que el programa verifique sus parámetros.

Si bien los nombres de los archivos se verifican dos veces, se verifican para diferentes propósitos. En un pequeño programa en el que puede confiar que se han verificado los parámetros para las funciones, las comprobaciones en las funciones podrían considerarse redundantes.

Una solución más robusta tendría uno o dos validadores de nombre de archivo.

  • Para un archivo de entrada, es posible que desee verificar que el parámetro especifique un archivo legible.
  • Para un archivo de salida, es posible que desee verificar que el parámetro sea un archivo grabable o un nombre de archivo válido que se pueda crear y escribir.

Utilizo dos reglas para cuándo realizar acciones:

  • Hazlos lo antes posible. Esto funciona bien para las cosas que siempre serán necesarias. Desde el punto de vista de este programa, esta es la verificación de los valores argv, y las validaciones posteriores en la lógica del programa serían redundantes. Si las funciones se mueven a una biblioteca, ya no son redundantes, ya que la biblioteca no puede confiar en que todos los llamantes hayan validado los parámetros.
  • Hazlos lo más tarde posible. Esto funciona extremadamente bien para cosas que rara vez se requerirán. Desde el punto de vista de este programa, se trata de las comprobaciones de los parámetros de la función.
BillThor
fuente
0

El cheque es redundante. Sin embargo, solucionar esto requiere que elimine readFromInputFile y writeToOutputFile y los reemplace con readFromStream y writeToStream.

En el punto donde el código recibe la secuencia del archivo, sabe que tiene una secuencia válida conectada a un archivo válido o cualquier otra cosa a la que se pueda conectar una secuencia. Esto evita verificaciones redundantes.

Entonces puede preguntar, bueno, aún necesita abrir la transmisión en algún lugar. Sí, pero eso ocurre internamente en el método de análisis de argumentos. Tiene dos verificaciones allí, una para verificar que se requiere un nombre de archivo, la otra es una verificación de que el archivo señalado por el nombre de archivo es un archivo válido en el contexto dado (por ejemplo, el archivo de entrada existe, el directorio de salida se puede escribir). Esos son diferentes tipos de comprobaciones, por lo que no son redundantes y ocurren dentro del método de análisis de argumentos (perímetro de la aplicación) en lugar de dentro de la aplicación principal.

Lie Ryan
fuente