A menudo llego a posiciones en mi código donde me encuentro comprobando una condición específica una y otra vez.
Quiero darles un pequeño ejemplo: supongamos que hay un archivo de texto que contiene líneas que comienzan con "a", líneas que comienzan con "b" y otras líneas y en realidad solo quiero trabajar con los dos primeros tipos de líneas. Mi código se vería así (usando python, pero léelo como pseudocódigo):
# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
if (line.startsWith("a")):
# do stuff
elif (line.startsWith("b")):
# magic
else:
# this else is redundant, I already made sure there is no else-case
# by using clear_lines()
# ...
Puedes imaginar que no solo comprobaré esta condición aquí, sino que tal vez también en otras funciones, etc.
¿Lo considera ruido o agrega algún valor a mi código?
coding-style
clean-code
Marktani
fuente
fuente
assert()
allí para ayudar con las pruebas, pero más allá de eso probablemente sea excesivo. Dicho esto, variará según la situación.elif (line.startsWith("b"))
? por cierto, puede eliminar de forma segura los paréntesis que rodean las condiciones, no son idiomáticos en Python.Respuestas:
Esta es una práctica extremadamente común y la forma de tratarla es a través de filtros de orden superior .
Esencialmente, pasa una función al método de filtro, junto con la lista / secuencia contra la que desea filtrar y la lista / secuencia resultante contiene solo aquellos elementos que desea.
No estoy familiarizado con la sintaxis de Python (aunque contiene una función como la que se ve en el enlace de arriba), pero en c # / f # se ve así:
C#:
f # (se supone que no es numerable, de lo contrario se usaría List.filter):
Entonces, para ser claros: si usa códigos / patrones probados y probados, es un mal estilo. Eso, y al mutar la lista en la memoria de la forma en que pareces a través de clear_lines (), pierdes la seguridad del hilo y cualquier esperanza de paralelismo que podrías haber tenido.
fuente
(line for line in lines if line.startswith("a") or line.startswith("b"))
.clear_lines
es realmente una mala idea. En Python probablemente usaría generadores para evitar cargar el archivo completo en la memoria.lines
es una colección generada.skip
,take
,reduce
(aggregate
en .NET),map
(select
en .NET), y no hay más, pero eso es un comienzo muy sólido.Recientemente tuve que implementar un programador de firmware usando el formato S-record de Motorola , muy similar a lo que usted describe. Como teníamos cierta presión de tiempo, mi primer borrador ignoró las redundancias e hizo simplificaciones basadas en el subconjunto que realmente necesitaba usar en mi aplicación. Pasó mis pruebas fácilmente, pero falló mucho tan pronto como alguien más lo intentó. No había idea de cuál era el problema. Llegó hasta el final pero falló al final.
Así que no tuve más remedio que implementar todas las verificaciones redundantes, para reducir dónde estaba el problema. Después de eso, me tomó alrededor de dos segundos encontrar el problema.
Me llevó tal vez dos horas más hacerlo de la manera correcta, pero también desperdicié un día del tiempo de otras personas en la solución de problemas. Es muy raro que unos pocos ciclos de procesador valgan un día de desperdicio de solución de problemas.
Dicho esto, en lo que respecta a la lectura de archivos, a menudo es beneficioso diseñar su software para que funcione con la lectura y el procesamiento de una línea a la vez, en lugar de leer todo el archivo en la memoria y procesarlo en la memoria. De esa manera, seguirá funcionando en archivos muy grandes.
fuente
Puede plantear una excepción en el
else
caso. De esta manera no es redundante. Las excepciones no son cosas que no se supone que sucedan, pero que se verifican de todos modos.fuente
"c"
, podría ser menos clara.En el diseño por contrato , uno adivina que cada función debe hacer su trabajo como se describe en su documentación. Por lo tanto, cada función tiene una lista de condiciones previas, es decir, condiciones en las entradas de la función, así como condiciones posteriores, es decir, condiciones de salida de la función.
La función debe garantizar a sus clientes que, si las entradas respetan las condiciones previas, la salida será como se describe en las condiciones posteriores. Si al menos una de las condiciones previas no se respeta, la función puede hacer lo que quiera (bloquearse, devolver cualquier resultado, ...). Por lo tanto, las condiciones previas y posteriores son una descripción semántica de la función.
Gracias al contrato, una función está segura de que sus clientes la usan correctamente y un cliente está seguro de que la función hace su trabajo correctamente.
Algunos idiomas manejan contratos de forma nativa oa través de un marco dedicado. Para los demás, lo mejor es verificar las condiciones previas y posteriores gracias a las afirmaciones, como dijo @Lattyware. Pero no llamaría a eso programación defensiva, ya que, en mi opinión, este concepto está más centrado en la protección contra las entradas (humanas) del usuario.
Si explota contratos, puede evitar la condición verificada de forma redundante ya que la función llamada funciona perfectamente y no necesita la doble verificación, o la función llamada es disfuncional y la función de llamada puede comportarse como lo desea.
La parte más difícil es definir qué función es responsable de qué y documentar estrictamente estos roles.
fuente
En realidad, no necesita clear_lines () al comienzo. Si la línea no es "a" o "b", los condicionales simplemente no se dispararán. Si desea deshacerse de esas líneas, convierta el resto en clear_line (). Tal como está, está haciendo dos pases a través de su documento. Si omite clear_lines () al principio y lo hace como parte del bucle foreach, entonces reduce el tiempo de procesamiento a la mitad.
No es solo un mal estilo, es malo computacionalmente.
fuente
"a"
/"b"
. No digo que sea probable (el nombre claro implica que se están descartando), solo que existe la posibilidad de que sea necesario. Si ese conjunto de líneas se repite repetidamente en el futuro, también podría valer la pena eliminarlas de antemano para evitar una gran cantidad de iteraciones sin sentido.Si realmente quiere hacer algo si encuentra una cadena no válida (texto de depuración de salida, por ejemplo), entonces diría que está absolutamente bien. Un par de líneas adicionales y unos meses más adelante cuando deja de funcionar por alguna razón desconocida, puede mirar la salida para averiguar por qué.
Sin embargo, si es seguro simplemente ignorarlo, o si sabe con certeza que nunca obtendrá una cadena no válida, entonces no hay necesidad de una rama adicional.
Personalmente, siempre estoy por poner al menos una salida de rastreo para cualquier condición inesperada: hace la vida mucho más fácil cuando tienes un error con salida adjunta que te dice exactamente qué salió mal.
fuente
Odio las
if...then...else
construcciones. Evitaría todo el problema:fuente