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 ...
- 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 - tener una función
readFromInputFile
que primero verifica que se haya ingresado un nombre de archivo de entrada - tener 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 hacerme creer que la verificación en los números 2 y 3 es redundante y debe eliminarse, liberando así ambas funciones de una if
condició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 except
en 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!
fuente
Respuestas:
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
read
ywrite
funciones 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
¿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 enwriteToOutputFile
: 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.fuente
La redundancia no es el pecado. La redundancia innecesaria es.
Si
readFromInputFile()
ywriteToOutputFile()
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.Si
readFromInputFile()
ywriteToOutputFile()
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.Si
readFromInputFile()
ywriteToOutputFile()
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.
fuente
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 .
fuente
Suponga que tiene una función (en C)
Y no puede encontrar ninguna documentación sobre el camino. Y luego miras la implementación y dice
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.
fuente
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:
argparse
módulo. A menudo es una mala idea usar una biblioteca y luego hacer su trabajo usted mismo. ¿Por qué usar la biblioteca entonces?fuente
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.
fuente
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:
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.
fuente
Yo diría que las pruebas no son redundantes.
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.
Utilizo dos reglas para cuándo realizar acciones:
fuente
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.
fuente