debugging - Los tipos más difíciles de errores para rastrear?
language-agnostic testing (30)
¿Cuáles son algunos de los errores más desagradables y difíciles que ha tenido que rastrear y corregir y por qué?
Cuando hablamos, soy sinceramente curioso y profundo en el proceso. Entonces, como dicen, a la miseria le gusta la compañía.
Bichos web cosméticos que incluyen estilos en varias configuraciones de O / S del navegador, por ejemplo, una página se ve bien en Windows y Mac en Firefox y IE, pero en la Mac en Safari algo se arruina. Estos son molestos a veces porque requieren tanta atención al detalle y hacer cambios para arreglar Safari puede romper algo en Firefox o IE por lo que uno tiene que andar con cuidado y darse cuenta de que el estilo puede ser una serie de ataques para arreglar página tras página. Diría que esas son las más desagradables que a veces no se solucionan, ya que no se consideran importantes.
CAMINO en los días, la memoria gotea. Afortunadamente, hay muchas herramientas para encontrarlos, en estos días.
Condiciones de carrera y puntos muertos. Hago muchos procesos multiproceso y eso es lo más difícil de tratar.
Cualquier error basado en las condiciones de tiempo. A menudo, esto ocurre cuando se trabaja con comunicación entre hilos, un sistema externo, lectura de una red, lectura de un archivo o comunicación con cualquier servidor o dispositivo externo.
Cuando los objetos se almacenan en caché y sus implementaciones de igual y código hash se implementan tan mal que el valor del código hash no es único y el igual devuelve verdadero cuando no es igual.
Daños en la memoria bajo carga debido a un hardware defectuoso.
Desbordamientos de búfer (en código nativo)
Dificultad de seguimiento:
- errores uno por uno
- errores de condición de frontera
El año pasado pasé un par de meses rastreando un problema que terminó siendo un error en un sistema descendente. El líder del equipo del sistema ofensivo siguió afirmando que debe ser algo divertido en nuestro procesamiento a pesar de que pasamos los datos tal como nos lo pidieron. Si la iniciativa hubiera sido un poco más cooperativa, podríamos haberlo solucionado antes.
El más difícil fue en realidad un error con el que estaba ayudando a un amigo. Estaba escribiendo C en MS Visual Studio 2005 y olvidó incluir time.h. Además, pidió tiempo sin el argumento requerido, por lo general NULO. Este tiempo declarado implícitamente como: int time (); Esto corrompió la pila, y de una manera completamente impredecible. Era una gran cantidad de código, y no pensamos en mirar la llamada de tiempo () durante bastante tiempo .
Enhebrar errores, especialmente las condiciones de carrera. Cuando no puedes detener el sistema (porque el error desaparece), las cosas se ponen difíciles rápidamente.
Errores que no están en su código per se, sino en el módulo de un proveedor del que depende. Particularmente cuando el vendedor no responde y se ve obligado a hackear una solución temporal. ¡Muy frustrante!
Errores que ocurren cuando se compila en modo de lanzamiento pero no en modo de depuración.
Estábamos desarrollando una base de datos para contener palabras y definiciones en otro idioma. Resultó que este lenguaje se había agregado recientemente al estándar Unicode y no se convirtió en SQL Server 2005 (aunque se agregó alrededor de 2005). Esto tuvo un efecto muy frustrante cuando se trataba de colación.
Las palabras y las definiciones entraron muy bien, pude ver todo en Management Studio. Pero siempre que intentamos encontrar la definición para una palabra dada, nuestras consultas no arrojaron nada. Después de 8 horas de depuración, estaba a punto de pensar que había perdido la capacidad de escribir una consulta SELECT simple.
Es decir, hasta que noté que las letras inglesas coincidían con otras letras inglesas con cualquier cantidad de letras extranjeras introducidas. Por ejemplo, EnglishWord coincidiría con E! N @ gl ## $ ish $ & Word . (Con! @ # $% ^ & * Representando letras extranjeras).
Cuando una intercalación no conoce un determinado carácter, no puede ordenarlos. Si no puede ordenarlos, no puede decir si dos cadenas coinciden o no (una sorpresa para mí). Tan frustrante y todo un día en el desagüe por una configuración de colación estúpida.
Lo más frustrante para mí han sido los errores del compilador, donde el código es correcto, pero he encontrado un caso de esquina no documentado o algo donde el compilador está equivocado. Empiezo asumiendo que cometí un error y luego paso días intentando encontrarlo.
Editar: El otro aspecto más frustrante fue el momento en que obtuve el caso de prueba ligeramente incorrecto, por lo que mi código era correcto pero la prueba no. Eso tomó días para encontrarlo.
En general, creo que los peores errores que he tenido han sido los que no son mi culpa.
Los errores concurrentes son bastante difíciles de rastrear, porque reproducirlos puede ser muy difícil cuando aún no sabes cuál es el error. Es por eso que, cada vez que ve un rastro de pila inexplicable en los registros, debe buscar el motivo de esa excepción hasta que lo encuentre. Incluso si ocurre solo una vez en un millón, eso no lo hace sin importancia.
Como no puede confiar en las pruebas para reproducir el error, debe usar el razonamiento deductivo para descubrir el error. Eso, a su vez, requiere una comprensión profunda de cómo funciona el sistema (por ejemplo, cómo funciona el modelo de memoria de Java y cuáles son las posibles fuentes de errores de concurrencia).
Aquí hay un ejemplo de un error de concurrencia en Guice 1.0 que localicé hace unos días. Puedes probar tus habilidades para encontrar errores tratando de descubrir cuál es el error que causa esa excepción. El error no es demasiado difícil de encontrar, encontré su causa en unos 15-30 minutos (la respuesta está here ).
java.lang.NullPointerException
at com.google.inject.InjectorImpl.injectMembers(InjectorImpl.java:673)
at com.google.inject.InjectorImpl$8.call(InjectorImpl.java:682)
at com.google.inject.InjectorImpl$8.call(InjectorImpl.java:681)
at com.google.inject.InjectorImpl.callInContext(InjectorImpl.java:747)
at com.google.inject.InjectorImpl.injectMembers(InjectorImpl.java:680)
at ...
El hardware de PS Faulty podría causar errores aún más desagradables que la concurrencia, ya que puede tomar mucho tiempo antes de que pueda concluir con seguridad que no hay errores en el código. Afortunadamente, los errores de hardware son más raros que los errores de software.
Los errores más difíciles de rastrear y corregir son aquellos que combinan todos los casos difíciles:
- informado por un tercero, pero no puede reproducirlo en sus propias condiciones de prueba;
- el error ocurre rara vez e imprevisiblemente (p. ej., porque es causado por una condición de raza);
- el error está en un sistema integrado y no se puede adjuntar un depurador;
- cuando intentas obtener información de registro, el error desaparece;
- el error está en el código de un tercero, como una biblioteca ...
- ... a la que no tiene el código fuente, por lo que debe trabajar solo con el desensamblaje;
- y el error está en la interfaz entre múltiples sistemas de hardware (por ejemplo, errores de protocolo de red o errores de contención del bus).
Estuve trabajando en un error con todas estas características esta semana. Era necesario realizar una ingeniería inversa de la biblioteca para descubrir qué era; luego genera hipótesis sobre qué dos dispositivos estaban corriendo; luego haga versiones especialmente instrumentadas del programa diseñadas para provocar la hipotética condición de carrera; luego, una vez que se confirmó una de las hipótesis, fue posible sincronizar el tiempo de los eventos para que la biblioteca ganara la carrera el 100% del tiempo.
Los más difíciles con los que me encuentro habitualmente son los que no aparecen en ninguna traza de registro. ¡Nunca debes comer en silencio una excepción! El problema es que comer una excepción a menudo mueve su código a un estado no válido, donde falla más tarde en otro hilo y de una manera completamente no relacionada.
Dicho esto, el más difícil con el que me encontré fue un programa C en una llamada a función en el que la firma de la llamada no coincidía exactamente con la firma llamada (una era larga, la otra una int). No hubo errores en tiempo de compilación o enlace y la mayoría de las pruebas pasaron, pero la pila estaba desactivada por sizeof (int), por lo que las variables posteriores en la pila tendrían aleatoriamente valores incorrectos, pero la mayoría de las veces funcionaría bien ( los valores que siguen a ese parámetro malo generalmente se pasan como cero).
Esa fue una PERRA para rastrear.
Multithreading, pérdidas de memoria, cualquier cosa que requiera burlas extensas, interfaz con software de terceros.
Para sistemas integrados:
Comportamiento inusual informado por los clientes en el campo, pero que no podemos reproducir.
Después de eso, los errores que se deben a una serie extraña o la concurrencia de eventos. Estos son al menos reproducibles, pero obviamente pueden tomar mucho tiempo, y mucha experimentación, para que sucedan.
Probablemente no sea el más difícil, pero son extremadamente comunes y no triviales:
- Errores relacionados con el estado mutable. Es difícil mantener invariantes en una estructura de datos si tiene muchos campos mutables. Y tiene dependencia de orden de operación: intercambie dos líneas y ocurra algo malo. Uno de mis errores más recientes difíciles de encontrar fue cuando descubrí que el desarrollador anterior del sistema que mantenía utilizaba datos mutables para las claves hashtables; en algunas condiciones raras, daba lugar a bucles infinitos.
- Orden de errores de inicialización. Puede ser obvio cuando se encuentra, pero no es así cuando se codifica.
Problemas de memoria, particularmente en sistemas más antiguos. Tenemos algún software heredado de 16 bits C que debe permanecer de 16 bits por el momento. Los bloques de memoria de 64 KB son un verdadero problema para trabajar, y constantemente agregamos estática o lógica de código que nos empuja más allá de los límites del grupo de 64K.
Para empeorar las cosas, los errores de memoria generalmente no hacen que el programa falle, sino que ocasionan que ciertas funciones se rompan esporádicamente (y no siempre las mismas características). La depuración no es una opción: el depurador no tiene las mismas restricciones de memoria, por lo que los programas siempre funcionan bien en el modo de depuración ... además, no podemos agregar declaraciones printf en línea para probar, ya que eso aumenta el uso de memoria aún más.
Como resultado, a veces podemos pasar DÍAS intentando encontrar un solo bloque de código para reescribir, u horas moviendo caracteres estáticos a los archivos. Afortunadamente, el sistema se está moviendo lentamente fuera de línea.
Problemas dependientes de la máquina.
Actualmente estoy intentando depurar por qué una aplicación tiene una excepción no controlada en un bloque try {} catch {} (sí, no se maneja dentro de un try / catch) que solo se manifiesta en ciertas compilaciones de SO / máquina, y no en otras.
La misma versión de software, los mismos medios de instalación, el mismo código fuente, funciona en algunas excepciones no controladas en lo que debería ser una parte del código muy bien manejada en otras.
Gak.
Una de las más frustrantes para mí fue cuando el algoritmo estaba equivocado en la especificación del software.
Uno de los errores más difíciles que tuve que encontrar fue un error de corrupción de memoria que solo ocurría después de que el programa había estado funcionando durante horas. Debido al tiempo que llevó corromper los datos, asumimos el hardware y probamos primero dos o tres computadoras más.
El error tardaría horas en aparecer, y cuando aparecía, por lo general solo se notaba bastante tiempo después de que el programa se había estropeado tanto que comenzaba a portarse mal. Estrechar la base de código hasta donde estaba ocurriendo el error era muy difícil porque los bloqueos debidos a la memoria corrupta nunca ocurrieron en la función que corrompió la memoria, y tardó tanto tiempo para que el error se manifestara.
El error resultó ser un error de uno a uno en un fragmento de código que rara vez se llamaba para manejar una línea de datos que tenía algo mal (codificación de caracteres no válidos de la memoria).
Al final, el depurador demostró ser inútil porque los bloqueos nunca ocurrieron en el árbol de llamadas para la función ofensiva. Una secuencia bien secuenciada de fprintf (stderr, ...) llama al código y el hecho de arrojar el resultado a un archivo fue lo que finalmente nos permitió identificar cuál era el problema.
Variables sin inicializar. (¿O tienen idiomas modernos eliminados con esto?)
Un heisenbug (llamado así por el Principio de Incertidumbre de Heisenberg) es un error informático que desaparece o altera sus características cuando se intenta estudiarlo.
Hubo un proyecto que construyó un simulador de ingeniería química utilizando un clúster beowulf. Dio la casualidad de que las tarjetas de red no transmitirían una secuencia particular de bytes. Si un paquete contiene esa cadena, el paquete se perderá. Resolvieron el problema reemplazando el hardware; encontrarlo en primer lugar fue mucho más difícil.
Un amigo mío tenía este error. Accidentalmente puso un argumento de función en un programa C entre corchetes en lugar de paréntesis como este: foo[5]
lugar de foo(5)
. El compilador estaba muy contento, porque el nombre de la función es un puntero y no hay nada ilegal en indexar un puntero.
- Errores que ocurren en un servidor y no en otro, y usted no tiene acceso al servidor infractor para depurarlo.
- Errores que tienen que ver con enhebrar.