¿Cómo abordas los errores intermitentes?

votos
31

Guión

Tienes varios informes de errores que muestran el mismo problema. Todos son crípticos con historias similares de cómo ocurrió el problema. Siga los pasos pero no reproduce de manera confiable el problema. Después de investigar un poco y buscar en la web, sospecha lo que podría estar pasando y está seguro de que puede solucionarlo.

Problema

Desafortunadamente, sin una manera confiable de reproducir el problema original, no se puede verificar que realmente solucione el problema en lugar de no tener ningún efecto en absoluto o exacerbar y enmascarar el problema real. Simplemente no podría arreglarlo hasta que se vuelva reproducible todo el tiempo, pero es un gran error y no solucionarlo causaría a sus usuarios muchos otros problemas.

Pregunta

¿Cómo haces para verificar tu cambio?

Creo que este es un escenario muy familiar para cualquiera que haya diseñado software, así que estoy seguro de que hay una gran cantidad de enfoques y mejores prácticas para abordar errores como este. Actualmente estamos viendo uno de estos problemas en nuestro proyecto, donde he dedicado un tiempo a determinar el problema, pero no he podido confirmar mis sospechas. Un colega está probando mi solución con la esperanza de que un día corriendo sin interrupciones equivale a está arreglado. Sin embargo, preferiría un enfoque más confiable y me imaginé que hay una gran experiencia aquí en SO.

Publicado el 09/12/2008 a las 15:31
fuente por usuario
En otros idiomas...                            


18 respuestas

votos
12

Los errores que son difíciles de reproducir son los más difíciles de resolver. Lo que necesita para asegurarse de haber encontrado la raíz del problema, incluso si el problema en sí no se puede reproducir con éxito.

Los errores intermitentes más comunes son causados ​​por las condiciones de la raza: al eliminar la raza o asegurarse de que un lado siempre gana, ha eliminado la raíz del problema, incluso si no puede confirmarlo con éxito probando los resultados. Lo único que puedes probar es que la causa no necesita repetirse.

A veces, arreglar lo que se ve como la raíz resuelve un problema, pero no el correcto; no hay forma de evitarlo. La mejor manera de evitar errores intermitentes es ser cuidadoso y metódico con el diseño y la arquitectura del sistema.

Respondida el 09/12/2008 a las 15:40
fuente por usuario

votos
7

Nunca podrá verificar la solución sin identificar la causa raíz y encontrar una forma confiable de reproducir el error.

Para identificar la causa raíz: si su plataforma lo permite, conecte la depuración post mortem al problema.

Por ejemplo, en Windows, obtenga su código para crear un archivo de minivolcado (volcado del núcleo en Unix) cuando encuentre este problema. Luego puede hacer que el cliente (o WinQual, en Windows) le envíe este archivo. Esto debería proporcionarle más información sobre cómo el código ha fallado en el sistema de producción.

Pero sin eso, todavía tendrá que encontrar una forma confiable de reproducir el error. De lo contrario, nunca podrás verificar que esté solucionado.

Incluso con toda esta información, puede terminar arreglando un error que parece, pero no es, el que el cliente está viendo.

Respondida el 09/12/2008 a las 15:40
fuente por usuario

votos
5

Uso lo que llamo "programación defensiva de estilo pesado" : agregue afirmaciones en todos los módulos que parecen estar vinculados por el problema. Lo que quiero decir es agregar MUCHAS afirmaciones , afirmar evidencias, afirmar el estado de los objetos en todos sus miembros, afirmar el estado de "medio ambiente", etc.

Las afirmaciones lo ayudan a identificar el código que NO está vinculado al problema.

La mayoría de las veces encuentro el origen del problema simplemente escribiendo las afirmaciones, ya que te obliga a volver a leer todo el código y a pliegarte bajo las agallas de la aplicación para entenderlo.

Respondida el 09/12/2008 a las 15:47
fuente por usuario

votos
5

Instrumente la compilación con un registro más extenso (posiblemente opcional) y un ahorro de datos que permita una reproducción exacta de los pasos de IU variables que los usuarios tomaron antes de que se produjera el bloqueo.

Si esos datos no le permiten reproducir el problema de manera confiable, entonces ha reducido la clase de error. Es hora de buscar fuentes de comportamiento aleatorio, como variaciones en la configuración del sistema, comparaciones de punteros, datos no inicializados, etc.

Algunas veces, "sabes" (o más bien sientes) que puedes solucionar el problema sin realizar pruebas exhaustivas o andamios de pruebas unitarias, porque realmente entiendes el problema. Sin embargo, si no lo hace, a menudo se reduce a algo así como "lo ejecutamos 100 veces y el error ya no se produce, por lo que lo consideraremos reparado hasta la próxima vez que se informe".

Respondida el 09/12/2008 a las 15:39
fuente por usuario

votos
4

No hay una sola respuesta a este problema. A veces, la solución que ha encontrado le ayuda a descubrir el escenario para reproducir el problema, en cuyo caso puede probar ese escenario antes y después de la solución. A veces, sin embargo, la solución que ha encontrado solo soluciona uno de los problemas pero no todos, o como usted dice que enmascara un problema más profundo. Ojalá pudiera decir "haz esto, funciona todo el tiempo", pero no hay un "esto" que se ajuste a ese escenario.

Respondida el 09/12/2008 a las 15:39
fuente por usuario

votos
2

Usted dice en un comentario que cree que es una condición de carrera. Si crees que sabes qué "característica" del código está generando la condición, puedes escribir una prueba para intentar forzarla.

Aquí hay un código arriesgado en c:

const int NITER = 1000;
int thread_unsafe_count = 0;
int thread_unsafe_tracker = 0;

void* thread_unsafe_plus(void *a){
  int i, local;
  thread_unsafe_tracker++;
  for (i=0; i<NITER; i++){
    local = thread_unsafe_count;
    local++;
    thread_unsafe_count+=local;
  };
}
void* thread_unsafe_minus(void *a){
  int i, local;
  thread_unsafe_tracker--;
  for (i=0; i<NITER; i++){
    local = thread_unsafe_count;
    local--;
    thread_unsafe_count+=local;
  };
}

que puedo probar (en un entorno pthreads) con:

pthread_t th1, th2;
pthread_create(&th1,NULL,&thread_unsafe_plus,NULL);
pthread_create(&th2,NULL,&thread_unsafe_minus,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
if (thread_unsafe_count != 0) {
  printf("Ah ha!\n");
}

En la vida real, es probable que tengas que ajustar tu código sospechoso de alguna manera para ayudar a que la carrera llegue más lejos.

Si funciona, ajusta la cantidad de hilos y otros parámetros para hacer que golpee la mayor parte del tiempo, y ahora tienes una oportunidad.

Respondida el 09/12/2008 a las 18:24
fuente por usuario

votos
2

Primero necesita obtener rastros de pila de sus clientes, de esa manera usted puede hacer algunos análisis forenses.

Luego realice pruebas de fuzz con entrada aleatoria y mantenga estas pruebas en ejecución durante largos períodos, son excelentes para encontrar esos casos irracionales de frontera, que los programadores y evaluadores humanos pueden encontrar a través de casos de uso y comprensión del código.

Respondida el 09/12/2008 a las 15:48
fuente por usuario

votos
1

Me he encontrado con errores en los sistemas que parecen causar errores sistemáticamente, pero al pasar por el código en un depurador el problema desaparece misteriosamente. En todos estos casos, el problema fue el momento.

Cuando el sistema funcionaba normalmente, había algún tipo de conflicto por los recursos o daba el siguiente paso antes de que finalizara el último. Cuando lo puse en el depurador, las cosas se movían lo suficientemente lento como para que el problema desapareciera.

Una vez que descubrí que era un problema de tiempo, fue fácil encontrar una solución. No estoy seguro de si esto es aplicable en su caso, pero cada vez que fallan los errores en el depurador, los problemas son los primeros sospechosos.

Respondida el 09/12/2008 a las 18:32
fuente por usuario

votos
1

Para un error difícil de reproducir, el primer paso suele ser la documentación. En el área del código que está fallando, modifique el código para que sea hiperexplícito: un comando por línea; manejo de excepciones pesadas y diferenciadas; salida prolija, incluso prolija de depuración. De esta forma, incluso si no puede reproducir o corregir el error, puede obtener mucha más información sobre la causa la próxima vez que se vea la falla.

El segundo paso suele ser la aserción de suposiciones y la comprobación de límites. Todo lo que creas saber sobre el código en cuestión, escribe. Afirmaciones y verificaciones. Específicamente, verifique los objetos por nulidad y (si su lenguaje es dinámico) existencia.

En tercer lugar, verifique la cobertura de su unidad de prueba. ¿Sus pruebas unitarias cubren realmente cada horquilla en ejecución? Si no tiene pruebas unitarias, este es probablemente un buen lugar para comenzar.

El problema con los errores irreproducibles es que solo son irreproducibles para el desarrollador. Si sus usuarios finales insisten en reproducirlos, es una herramienta valiosa para aprovechar el crash en el campo.

Respondida el 09/12/2008 a las 18:19
fuente por usuario

votos
1

Escenario específico

Aunque no quiero concentrarme solo en el problema que estoy teniendo, aquí hay algunos detalles del problema actual que enfrentamos y cómo lo he abordado hasta ahora.

El problema se produce cuando el usuario interactúa con la interfaz de usuario (un TabControl para ser exactos) en una fase particular de un proceso. No siempre ocurre y creo que es porque la ventana de tiempo para que el problema sea exhibido es pequeña. Mi sospecha es que la inicialización de un UserControl (estamos en .NET, usando C #) coincide con un evento de cambio de estado desde otra área de la aplicación, lo que lleva a que se elimine una fuente. Mientras tanto, otro control (una etiqueta) intenta dibujar su cadena con esa fuente y, por lo tanto, el bloqueo.

Sin embargo, la confirmación de lo que conduce a la disposición de la fuente ha resultado difícil. La solución actual ha sido clonar la fuente para que la etiqueta del dibujo aún tenga una fuente válida, pero esto realmente enmascara el problema de raíz que es la fuente que se está eliminando en primer lugar. Obviamente, me gustaría rastrear la secuencia completa, pero eso está resultando muy difícil y el tiempo es corto.

Enfoque

Mi enfoque fue primero mirar el seguimiento de la pila de nuestros informes de fallas y examinar el código de Microsoft usando Reflector. Desafortunadamente, esto condujo a una llamada GDI + con poca documentación, que solo devuelve un número del error: .NET lo convierte en un mensaje bastante inútil que indica que algo no es válido. Estupendo.

A partir de ahí, fui a ver qué llamada en nuestro código lleva a este problema. La pila comienza con un bucle de mensaje, no en nuestro código, pero encontré una llamada a Update () en el área general bajo sospecha y, usando instrumentación (rastreos, etc.), pudimos confirmar con un 75% de certeza que esto fue la fuente del mensaje de pintura. Sin embargo, no fue la fuente del error: pedirle a la etiqueta que pinte no es un crimen.

A partir de ahí, miré cada aspecto de la llamada de pintura que se estaba rompiendo (DrawString) para ver lo que podría ser inválido y comencé a descartar cada uno hasta que cayó sobre los artículos desechables. Luego determiné sobre cuáles teníamos control y la fuente era la única. Entonces, eché un vistazo a cómo manejamos la fuente y en qué circunstancias lo eliminamos para identificar posibles causas raíz. Pude proponer una secuencia plausible de eventos que se ajustan a los informes de los usuarios y, por lo tanto, pueden codificar una solución de bajo riesgo.

Por supuesto, se me pasó por la mente que el error estaba en el marco, pero me gustaría asumir que lo arruinamos antes de culpar a Microsoft.

Conclusión

Entonces, así es como me acerqué a un ejemplo particular de este tipo de problema. Como puede ver, es menos que ideal, pero encaja con lo que muchos han dicho.

Respondida el 09/12/2008 a las 16:21
fuente por usuario

votos
1

Estos son horribles y casi siempre resistentes a las "soluciones" que el ingeniero cree que está poniendo, ya que tienen el hábito de volver a morder meses después. Tenga cuidado con las correcciones hechas a errores intermitentes. Prepárese para un poco de trabajo pesado y un registro intensivo ya que esto suena más un problema de prueba que un problema de desarrollo.

Mi propio problema al superar errores como estos era que a menudo estaba demasiado cerca del problema, no retrocedía y miraba la imagen más grande. Intenta que otra persona mire cómo abordas el problema.

Específicamente, mi error tenía que ver con la configuración de los tiempos de espera y varios otros números mágicos que en retrospectiva estaban en el límite y así funcionaban casi todo el tiempo. El truco en mi propio caso fue hacer una gran cantidad de experimentos con configuraciones para poder averiguar qué valores 'romperían' el software.

¿Las fallas ocurren durante períodos de tiempo específicos? Si es así, ¿dónde y cuándo? ¿Son solo ciertas personas las que parecen reproducir el error? ¿Qué conjunto de entradas parece invitar al problema? ¿En qué parte de la aplicación falla? ¿El error parece más o menos intermitente en el campo?

Cuando era un probador de software, mis principales herramientas eran un bolígrafo y un papel para registrar notas de mis acciones anteriores: recordar muchos detalles aparentemente insignificantes es vital. Al observar y recolectar pequeños bits de datos todo el tiempo, el error parecerá ser menos intermitente.

Respondida el 09/12/2008 a las 16:17
fuente por usuario

votos
1

Algunas preguntas que podría hacerse:

  • ¿Cuándo funcionó esta pieza de código sin problemas?
  • Qué se ha hecho desde que dejó de funcionar.

Si el código nunca funcionó, el enfoque sería diferente de forma natural.

Al menos cuando muchos usuarios cambian mucho código todo el tiempo, este es un escenario muy común.

Respondida el 09/12/2008 a las 15:53
fuente por usuario

votos
1

En esta situación, donde nada más funciona, introduzco el registro adicional.

También agrego notificaciones por correo electrónico que me muestran el estado de la aplicación cuando se rompe.

A veces agrego contadores de rendimiento ... Pongo esos datos en una tabla y miro las tendencias.

Incluso si no aparece nada, estás reduciendo las cosas. De una forma u otra, se van a terminar con las teorías útiles.

Respondida el 09/12/2008 a las 15:47
fuente por usuario

votos
1

Esos tipos de errores son muy frustrantes. Extrapolarlo a diferentes máquinas con diferentes tipos de hardware personalizado que podría estar en ellos (como en mi empresa), y boy oh boy se convierte en una pesadilla. Actualmente tengo varios errores como este en este momento en mi trabajo.

Mi regla de oro: no lo soluciono a menos que pueda reproducirlo yo mismo o me presentan un registro que muestra claramente algo incorrecto. De lo contrario, no puedo verificar mi cambio, ni puedo verificar que mi cambio no haya roto nada más. Por supuesto, es solo una regla práctica: hago excepciones.

Creo que tienes razón en preocuparte por el enfoque de tu colega.

Respondida el 09/12/2008 a las 15:44
fuente por usuario

votos
0

Simplemente: pedir al usuario que lo reportó.

Sólo tiene que utilizar uno de los reporteros como un sistema de verificación. Por lo general, la persona que estaba dispuesto a informar de un error es más que encantados de ayudarle a resolver su problema [1]. Sólo le dará a su versión con un arreglo posible y preguntar si el problema se ha ido. En los casos en que el insecto es una regresión, el mismo método puede ser usado para dividir en dos, donde se produjo el problema de dar al usuario con el problema de múltiples versiones para poner a prueba. En otros casos, el usuario también puede ayudar a depurar el problema dándole una versión con más capacidades de depuración.

Esto limitará los efectos negativos de una posible solución a esa persona en vez de adivinar que algo va a reparar el fallo y, posteriormente, al darse cuenta de que ha acaba de lanzar una "corrección de errores" que no tiene ningún efecto o en el peor de los casos un efecto negativo para la estabilidad del sistema.

También puede limitar los posibles efectos negativos de la "corrección de errores", dando la nueva versión a un número limitado de usuarios (por ejemplo para todos los que ocupan el problema) y la liberación de la corrección sólo después de eso.

También los que pueden confirmar que la solución que ha realizado las obras, es fácil añadir pruebas que asegura que su solución se quedará en el código (por lo menos en el nivel de prueba de unidad, si el error es difícil de reproducir en el más alto nivel del sistema ).

Por supuesto, esto requiere que todo lo que se está trabajando es compatible con este tipo de enfoque. Pero si no me sería realmente hacer lo que pueda para que pueda - los usuarios finales están más satisfechos y muchos de los problemas más difíciles tecnología sólo va a desaparecer y las prioridades vienen claro que el desarrollo puede interactuar directamente con los usuarios finales del sistema.

[1] Si alguna vez ha informado de un error, lo más probable es saber que muchas veces la respuesta por parte del equipo de desarrollo / mantenimiento de alguna manera es negativo desde el punto de vista de los usuarios finales o no habrá ninguna respuesta en absoluto - especialmente en situaciones en las que la error no puede ser reproducido por el equipo de desarrollo.

Respondida el 05/09/2014 a las 11:50
fuente por usuario

votos
0

Una vez que entiendas completamente el error (y eso es un gran "una vez"), deberías ser capaz de reproducirlo a voluntad. Cuando se escribe el código de reproducción (prueba automatizada), corrige el error.

¿Cómo llegar al punto donde entiendes el error?

Instrumente el código (log como loco). Trabaje con su control de calidad: son buenos para volver a crear el problema, y ​​debe organizar el kit completo de herramientas de desarrollo en sus máquinas. Use herramientas automatizadas para recursos / memoria sin inicializar. Solo mire fijamente el código. No hay una solución fácil allí.

Respondida el 09/12/2008 a las 19:56
fuente por usuario

votos
0

A menos que haya grandes restricciones de tiempo, no empiezo a probar los cambios hasta que pueda reproducir el problema de manera confiable.

Si realmente tuviera que hacerlo, supongo que podría escribir un caso de prueba que a veces parece desencadenar el problema, y ​​agregarlo a su conjunto de pruebas automatizadas (tiene un conjunto de pruebas automatizado, ¿no?) Y luego hacer su cambio y esperar. ese caso de prueba nunca falla nuevamente, sabiendo que si realmente no arreglas nada al menos ahora tienes más posibilidades de atraparlo. Pero cuando puedes escribir un caso de prueba, casi siempre reduces las cosas hasta el punto en que ya no estás lidiando con una situación (aparentemente) no determinista.

Respondida el 09/12/2008 a las 19:06
fuente por usuario

votos
0

Estos problemas siempre han sido causados ​​por:

  1. Problemas de memoria
  2. Enhebrar problemas

Para resolver el problema, debes:

  • Instrumente su código (Agregar declaraciones de registro)
  • Revisión de código enhebrado
  • Asignación / desreferenciación de memoria de revisión de código

Probablemente, las revisiones de código solo sucedan si es una prioridad, o si tiene una fuerte sospecha sobre qué código comparten los múltiples informes de errores. Si se trata de un problema de enhebrado, verifique la seguridad de su hilo: asegúrese de que las variables accesibles por ambos hilos estén protegidas. Si se trata de un problema de memoria, verifique sus asignaciones y desreferencias, y especialmente desconfíe del código que asigna y devuelve memoria, o código que utiliza la asignación de memoria por parte de otra persona que pueda liberarlo.

Respondida el 09/12/2008 a las 15:47
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more