¿Cuál puede ser la causa de la aparición de nuevos errores en otro lugar cuando se resuelve un error conocido?

14

Durante una discusión, uno de mis colegas dijo que tiene algunas dificultades con su proyecto actual al tratar de resolver errores. "Cuando resuelvo un error, otra cosa deja de funcionar en otro lugar", dijo.

Empecé a pensar en cómo podría suceder esto, pero no puedo entenderlo.

  • A veces tengo problemas similares cuando estoy demasiado cansado / somnoliento para hacer el trabajo correctamente y tener una visión general de la parte del código en la que estaba trabajando. Aquí, el problema parece ser por unos días o semanas, y no está relacionado con el enfoque de mi colega.
  • También puedo imaginar que este problema surja en un proyecto muy grande, muy mal administrado , donde los compañeros de equipo no tienen idea de quién hace qué, y qué efecto en el trabajo de otros puede tener un cambio que están haciendo. Este tampoco es el caso aquí: es un proyecto bastante pequeño con un solo desarrollador.
  • También puede ser un problema con una base de código antigua, mal mantenida y nunca documentada , donde los únicos desarrolladores que realmente pueden imaginar las consecuencias de un cambio habían dejado la compañía hace años. Aquí, el proyecto acaba de comenzar y el desarrollador no usa la base de código de nadie.

Entonces, ¿cuál puede ser la causa de este problema en una base de código nueva y de pequeño tamaño escrita por un único desarrollador que se mantiene enfocado en su trabajo ?

¿Qué puede ayudar?

  • Pruebas unitarias (no hay)
  • Arquitectura adecuada (estoy bastante seguro de que la base de código no tiene ninguna arquitectura y se escribió sin pensar previamente), ¿requiere toda la refactorización?
  • ¿Programación en pareja?
  • ¿Algo más?
Arseni Mourzenko
fuente
14
Ah, el buen patrón de diseño de "olas de fracaso en cascada". :-)
Brian Knoblauch
1
Lo comparo con una burbuja en una hoja de contacto. Empújalo hacia abajo, aparece en otro lugar. El mejor mi codificación obtiene, menos lo veo
johnc
2
En una nota al margen, tenía exactamente eso en un sistema embebido. Agregué una llamada de función para solucionar un problema. Esa llamada de función fue demasiado para la pila (el microcontrolador no tenía detección de desbordamiento de pila) y, por lo tanto, escribió algunas cosas aleatorias en otra parte de la memoria, lo que, por supuesto, rompió algo en algún lugar completamente diferente. Entonces, esto PUEDE suceder en una pequeña base de código con un solo desarrollador y buena arquitectura.
risingDarkness
... y eso fue una pesadilla para depurar.
risingDarkness

Respuestas:

38

No tiene mucho que ver con el enfoque, el tamaño del proyecto, la documentación u otros problemas del proceso. Problemas como ese generalmente son el resultado de un acoplamiento excesivo en el diseño, lo que hace que sea muy difícil aislar los cambios.

Karl Bielefeldt
fuente
15
esto combinado con pruebas de regresión pobres o
nulas
3
Es cierto, @Ryathal, aunque las pruebas de regresión no evitarán ese tipo de errores, solo le informaremos antes.
Karl Bielefeldt
Si los conoce pronto (por ejemplo, a los pocos minutos de crear los errores), puede deshacer los cambios y fingir que nunca ocurrieron.
bdsl
14

Una de las causas puede ser un acoplamiento estrecho entre los componentes de su software: si no hay interfaces simples y bien definidas entre los componentes, incluso un pequeño cambio en una parte del código puede introducir efectos secundarios inesperados en otras partes del código.

Como ejemplo, últimamente estaba trabajando en una clase que implementa un componente GUI en mi aplicación. Durante semanas se informaron nuevos errores, los arreglé, y aparecieron nuevos errores en otro lugar. Me di cuenta de que esa clase había crecido demasiado, estaba haciendo demasiadas cosas, y muchos métodos dependían de que se llamaran a otros métodos en la secuencia correcta para funcionar correctamente.

En lugar de corregir los últimos tres errores, realicé una fuerte refactorización: dividí el componente en una clase principal más clases de MVC (tres clases adicionales). De esta manera tuve que dividir el código en piezas más pequeñas y simples y definir interfaces más claras. Después de la refactorización se resolvieron todos los errores y no se informaron nuevos errores.

Giorgio
fuente
7

Es fácil para un error enmascarar a otro. Suponga que el error "A" da como resultado que se llame a una función incorrecta para manejar la entrada. Cuando se soluciona el error "A", de repente se llama a la función correcta, que nunca se ha probado.

ddyer
fuente
5

Bueno, la causa inmediata es dos errores haciendo un bien, o al menos haciendo un no obviamente equivocado. Una parte del código está compensando el comportamiento incorrecto de la otra parte. O si la primera parte no está "mal" como tal, hay un acuerdo no escrito entre las dos partes que se está violando cuando se cambia el código.

Por ejemplo, suponga que las funciones A y B usan una convención basada en cero para cierta cantidad, por lo que funcionan juntas correctamente, pero C usa una, puede "arreglar" A para que funcione con C y luego descubrir un problema con B.

El problema más profundo es la falta de verificación independiente de la corrección de las partes individuales. Las pruebas unitarias están diseñadas para abordar esto. También actúan como una especificación de las entradas adecuadas. Por ejemplo, un buen conjunto de pruebas dejaría en claro que las funciones A y B esperaban una entrada basada en 0 y una basada en C 1.

Obtener las especificaciones correctas también se puede hacer de otras maneras, desde documentos oficiales hasta buenos comentarios en el código, dependiendo de las necesidades del proyecto. La clave es comprender qué espera cada componente y qué promete, para que pueda encontrar inconsistencias.

Una buena arquitectura ayuda con el problema de comprender el código, lo que lo hace más fácil. La programación de pares es útil para evitar errores en primer lugar, o encontrarlos más rápidamente.

Espero que esto ayude.

Mike B
fuente
5

Acoplamiento apretado, falta de pruebas, estos son probablemente los culpables más comunes. Básicamente, el problema común es simplemente normas y procedimientos de mala calidad. Otro es el código incorrecto que logra tener suerte por un tiempo con el comportamiento correcto. Considere el memcpyerror de Linus Torvalds en el que al cambiar su implementación, se exponen errores (no causados) en clientes que se usaron memcpyen lugares donde deberían haberse usado memmovecon origen y destino superpuestos.


fuente
4

Parece que estos errores "nuevos" no son realmente errores "nuevos". Simplemente no eran un problema, hasta que el otro código que estaba roto, en realidad, se solucionó. En otras palabras, su colega no se da cuenta de que en realidad tuvo dos errores todo el tiempo. Si el código que no está resultando roto no se rompió, no habría fallado, una vez que se solucionó la otra parte del código.

En ambos casos, un mejor régimen de prueba automatizado podría ser útil. Parece que su colega necesita probar la unidad de la base de código actual. En el futuro, las pruebas de regresión verificarán que el código existente continúa funcionando.

Ramhound
fuente
0

Mejore la amplitud de su régimen de prueba automatizado. SIEMPRE ejecute el conjunto completo de pruebas antes de confirmar los cambios de código. De esa manera, detectará el efecto pernicioso de sus cambios.

Stephen Gross
fuente
0

Acabo de encontrar esto cuando una prueba fue incorrecta. La prueba verificó un estado de permiso dado que era correcto. Actualicé el código y ejecuté la prueba de permiso. Funcionó. Luego realicé todas las pruebas. Todas las otras pruebas que usaron el recurso verificado fallaron. Corrigí la prueba y la verificación de permisos, pero al principio hubo un poco de pánico.

Las especificaciones inconsistentes también ocurren. Entonces está casi garantizado que arreglar un error creará otro (emocionante cuando esa parte particular de la especificación no se ejerza hasta más adelante en el proyecto).

ccoakley
fuente
0

Imagina que tienes un producto completo. Luego agrega algo nuevo, todo parece estar bien, pero rompió algo más, que depende de algún código que cambie para que la nueva característica funcione. Incluso si no cambia ningún código, solo agregue funcionalidad a la existente, podría romper algo más.

Entonces, básicamente, casi te respondes a ti mismo:

  • bajo acoplamiento
  • falta de pruebas

Simplemente aprenda a adaptar el principio TDD (al menos para las nuevas funciones) e intente probar cada estado posible que pueda ocurrir.

La programación en pareja es excelente, pero no siempre está "disponible" (tiempo, dinero, ambos ...). Pero las revisiones de código (por ejemplo, por parte de sus colegas) una vez al día / semana / conjunto de confirmaciones también serán de gran ayuda, especialmente cuando la revisión incluye el conjunto de pruebas. (Me resulta difícil no escribir errores en las suites de prueba ... a veces debo probar la prueba internamente (verificación de cordura) :)).

Dalibor Filus
fuente
0

Digamos que el desarrollador A escribió un código con un error. El código no es exactamente lo que se supone que debe hacer, sino algo ligeramente diferente. El desarrollador B escribió un código que confiaba en que el código de A hiciera exactamente lo que se especifica que debe hacer, y el código de B no funciona. B investiga, encuentra el comportamiento incorrecto en el código de A y lo corrige.

Mientras tanto, el código del desarrollador C solo funcionaba correctamente porque confiaba en el comportamiento incorrecto del código de A. El código de A ahora es correcto. Y el código de C deja de funcionar. Lo que significa que cuando arreglas el código, debes verificar con mucho cuidado quién usa este código y cómo cambiará su comportamiento con el código fijo.

He tenido otra situación: algunos códigos se comportaron mal y dejaron de funcionar completamente una característica en alguna situación X. Así que cambié el mal comportamiento e hice que la característica funcionara. El desafortunado efecto secundario fue que toda la característica tuvo problemas significativos en la situación X y falló por todas partes; esto era completamente desconocido para nadie porque la situación nunca antes había surgido. Bueno, eso es duro.

gnasher729
fuente