¿La verificación redundante de la condición se compara con las mejores prácticas?

15

He estado desarrollando software durante los últimos tres años, pero recientemente me di cuenta de lo ignorante que soy de las buenas prácticas. Esto me ha llevado a comenzar a leer el libro Código limpio , que está mejorando mi vida, pero estoy luchando por conocer algunos de los mejores enfoques para escribir mis programas.

Tengo un programa en Python en el que ...

  1. use argparse required=True para 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. tiene una función readFromInputFile que primero verifica que se haya ingresado un nombre de archivo de entrada
  3. tiene una función writeToOutputFile que primero verifica que se haya ingresado un nombre de archivo de salida

Mi programa es lo suficientemente pequeño como para que crea que la comprobación en los números 2 y 3 es redundante y debería eliminarse, por lo que se liberan ambas funciones de una condición if innecesaria. Sin embargo, también me han llevado a creer que "la doble verificación está bien" y puede ser la solución correcta en un programa donde las funciones se pueden llamar desde una ubicación diferente donde no se realiza el análisis de los argumentos.

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

Mi pregunta es: ¿es mejor evitar todas las comprobaciones 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!

    
pregunta thesis 26.11.2016 - 06:59

9 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 para usted mismo, 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, hay 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 es tan sólida que esas comprobaciones son redundantes. ". Eso hace que la detección de errores sea mucho más fácil y ayuda a que los tiempos de depuración sean más pequeños.

En su caso, tiene que decidir por sí mismo, qué tipo de ciclo de vida espera para su programa. ¿Es un programa que espera que se use y se mantenga durante años? Entonces, agregar un cheque redundante es probablemente mejor, ya que no será improbable que su código sea refactorizado en el futuro y que las funciones read y write puedan usarse en un contexto diferente.

¿O es un programa pequeño solo para fines de aprendizaje o diversión? Entonces esos controles dobles no serán necesarios.

En el contexto de "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 se puede interpretar como parte de la lógica de negocios de un programa, y tener esto en dos lugares puede llevar a los problemas de mantenimiento habituales causados por la violación de DRY. La robustez contra DRY es a menudo un compromiso: la robustez requiere redundancia en el código, mientras que DRY intenta minimizar la redundancia. Y a medida que aumenta la complejidad del programa, la solidez se vuelve más y más importante que estar SECO en la validación.

Finalmente, déjame dar un ejemplo de lo que eso significa en tu caso. Asumamos 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 al reemplazar el sufijo.

¿Eso hace que sea probable que necesites cambiar tu doble validación en dos lugares? Probablemente no, tal requisito lleva a un cambio cuando se llama a argparse , pero no hay 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 debido a que tiene dos lugares para cambiar es, en mi opinión, mucho más bajo que el riesgo de tener problemas de mantenimiento debido a errores enmascarados causados por muy pocas revisiones. / p>     

respondido por el Doc Brown 26.11.2016 - 08:29
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 por alguien que evite argparse por completo. . Eso significa que cuando dejan de lado los argumentos, no pueden ver su mensaje de error argparse personalizado.

  2. Si readFromInputFile() y writeToOutputFile() verifican los parámetros en sí mismos, nuevamente se mostrará un mensaje de error personalizado que explica la necesidad de los nombres de archivo.

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

Todo se reduce a 3. Escriba un código que realmente use estas funciones evitando argparse y produzca el mensaje de error. Imagina que no has mirado dentro de estas funciones y solo estás confiando en sus nombres para proporcionar suficiente comprensión para usar. Cuando eso es todo lo que sabe, ¿hay alguna manera de confundirse con la excepción? ¿Es necesario un mensaje de error personalizado?

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

    
respondido por el candied_orange 26.11.2016 - 13:45
4

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

Si un programador tiene el hábito de escribir métodos que hacen suposiciones sobre lo que se pasa, basándose en ideas como "si esto no funciona, tenemos cosas más importantes de las que preocuparnos" o "el parámetro X no puede tener el valor Y porque el resto del código lo impide ", entonces, de repente, ya no tiene componentes independientes y desacoplados. Sus componentes son esencialmente dependientes del sistema más amplio. Ese es un tipo de acoplamiento sutil y estrecho que conduce a un incremento exponencial del costo total de propiedad 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 de componentes independientes desacoplados, y un cambio a 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 pasarlas a Y. Y tiene la responsabilidad de verificar sus propias entradas para sus necesidades .

    
respondido por el Brad Thomas 27.11.2016 - 03:11
1

Suponga que tiene una función (en C)

void readInputFile (const char* path);

Y no puedes encontrar ninguna documentación sobre la ruta. Y luego nos fijamos en 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 no se permite que la ruta sea NULL o una cadena vacía.

    
respondido por el gnasher729 26.11.2016 - 17:19
0

En general, la doble comprobación no es siempre buena o mala. Siempre hay muchos aspectos de la pregunta en su caso particular de los cuales depende el asunto. En su 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 crece, es más importante especificar exactamente cuáles son las condiciones previas y las condiciones posteriores de cada rutina.
  • los argumentos ya están verificados por el módulo argparse . 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 se reutilice en un contexto en el que la persona que llama no comprueba los argumentos? Cuanto más probable es que sea, más importante es validar los argumentos.
  • ¿Qué sucede si un argumento hace desaparece? No encontrar un archivo de entrada probablemente detendrá el procesamiento por completo. Ese es probablemente un modo de falla obvio que es fácil de rectificar. El tipo de errores insidiosos son aquellos en los que el programa continúa funcionando alegremente y produce resultados erróneos sin que te des cuenta .
respondido por el Kilian Foth 26.11.2016 - 08:30
0

Sus controles dobles parecen estar en lugares donde se usan raramente. Así que estas verificaciones simplemente hacen que su programa sea más robusto:

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

Sin embargo, si está revisando dentro de un bucle que se repite con frecuencia, debería pensar en eliminar la redundancia, incluso si la verificación en sí misma no es costosa en comparación con lo que sigue después de la verificación.

    
respondido por el Thomas Kilian 26.11.2016 - 09:37
0

Quizás puedas cambiar tu punto de vista:

Si algo sale mal, ¿cuál es el resultado? ¿Hará daño a tu 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 está tratando con el software real world , hay consecuencias en el mundo real.

Desde el contexto que estás dando:

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

Supongo que estás 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) Se olvidó de especificar de dónde leer: entonces el resultado es nada . Y el tiempo de ejecución será más corto de lo esperado. Observa el resultado, o mejor aún: busca un resultado faltante, observa que invocaste el comando de manera incorrecta, comienza de nuevo y todo está bien de nuevo

2) Olvidaste especificar el archivo de salida. Esto da lugar a diferentes escenarios:

a) La entrada se lee a la vez. A continuación, se inicia la transformación y se debe escribir el resultado, pero en su lugar se recibe un error. Dependiendo del tiempo, su usuario tiene que esperar (dependiendo de la cantidad de datos que deben 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 comprobación descuidada puede verse como OK en algunas circunstancias. Depende totalmente de su caso de uso y de cuál sea su intención.

Además: Debes evitar la paranoia y no hacer demasiadas comprobaciones dobles.

    
respondido por el Thomas Junk 26.11.2016 - 09:48
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. Conviene validar sus parámetros. Las funciones podrían potencialmente usarse en cualquier programa que necesite su funcionalidad.
  • Tienes un programa que requiere dos argumentos que deben ser nombres de archivo. Sucede usar las funciones. Es apropiado que el programa verifique sus parámetros.

Mientras los nombres de los archivos se verifican dos veces, se revisan para diferentes propósitos. En un programa pequeño en el que puede confiar que los parámetros de las funciones que se han verificado, las verificaciones de 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 haya especificado 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.

Uso dos reglas para cuándo realizar acciones:

  • Hazlos lo antes posible. Esto funciona bien para cosas que siempre serán necesarias. Desde el punto de vista de este programa, esta es la verificación de los valores de argv, y las validaciones posteriores en la lógica de los programas 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 serán necesarias. Desde el punto de vista de este programa, esta es la verificación de los parámetros de la función.
respondido por el BillThor 27.11.2016 - 00:43
0

El cheque es redundante. Sin embargo, para solucionar este problema, es necesario eliminar readFromInputFile y writeToOutputFile y reemplazarlos con readFromStream y writeToStream.

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

Luego puede preguntar, bueno, todavía necesita abrir la transmisión en algún lugar. Sí, pero eso sucede internamente en el método de análisis de argumentos. Tiene dos comprobaciones 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, el directorio de salida es de escritura). Esos son diferentes tipos de controles, 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.

    
respondido por el Lie Ryan 17.02.2017 - 14:07

Lea otras preguntas en las etiquetas