¿Qué proceso usas normalmente cuando intentas depurar un problema / problema / error con tu software? [cerrado]

15

La mayoría de las personas parecen tratar la depuración como un arte, en lugar de una ciencia. Para aquellos aquí que lo tratan como una ciencia, en lugar de un arte, ¿qué proceso (s) usas normalmente cuando te enfrentas a un nuevo problema / error / problema?

blueberryfields
fuente

Respuestas:

13

En términos muy generales, lo que hago es:

  1. Intenta aislar el problema. Piense en lo que cambió cuando apareció el error por primera vez. ¿En qué trabajas? ¿Qué parte del código estabas cambiando? El 99% de mis errores se resuelven de esta manera. Suele ser algo tonto.

  2. Si tengo una idea de dónde está el problema, eche un vistazo al código que parece ser la causa. Léelo Léelo en voz alta incluso. Pregúnteme: "¿Qué estoy tratando de lograr?". Para algunos tipos de problemas: ¿podría tener algunos efectos secundarios o podría verse afectado por el código en otro lugar de una manera que no había pensado?

  3. Intente de varias maneras analizar qué sale mal, dónde y cuándo (ver más abajo).

  4. Si todavía no tengo idea, verifico si una versión anterior de mi fuente tiene el mismo problema, trato de encontrar cuándo apareció por primera vez en mi línea de tiempo de desarrollo. Para hacer esto, debe trabajar con un buen sistema de control de versiones, como git (git tiene una característica llamada bisect exactamente para este tipo de depuración).

  5. Si aún no tienes idea, tómate un descanso ... en realidad a menudo ayuda.

  6. Regrese al tablero de dibujo: revise cómo se supone que debe funcionar su programa y si eso realmente tiene sentido.

Realmente depende del tipo de problema, pero suponiendo que tenga una idea general de dónde podría estar el problema, entonces:

  • Si sospecho que el problema está en alguna parte del código / cambio reciente, primero trato de eliminar / comentar / cambiar o lo que sea para que el error desaparezca al simplificar el código, y luego recuperar el código problemático y tomar un bien miralo.

  • Ejecute un depurador con puntos de interrupción (si es posible) y eche un vistazo a cómo se ven mis datos tratando de encontrar cuándo comienza a actuar mal, para tener una mejor idea de dónde van las cosas mal.

sinelaw
fuente
1
+1 por tomar un descanso. Los problemas más difíciles solo se vuelven más difíciles cuando estás frustrado y en tu sexta hora los depura. Saber cuándo tomar un descanso es una de las habilidades de depuración más útiles que he obtenido.
Brad Gardner
Impresionante respuesta. No puedo hacer nada mejor.
EricBoersma
1
Al igual que mi enfoque, pero se le olvidó la parte en la que se pregunta a un colega para dar una rápida mirada por encima y se nota al instante el error de ortografía ...
ChrisAnnODell
1
Excelente respuesta Solo quiero agregar que una onza de prevención vale una libra de cura. Una gran parte de mi proceso de depuración es que mientras estoy codificando en primer lugar, solo hago pequeños cambios incrementales y compilo, pruebo y me comprometo localmente entre cada uno. De esa manera, si aparece un error repentinamente, la lista de posibles sospechosos es muy pequeña y fácil de ver con un bzr qdiffcomando.
Karl Bielefeldt
8

Intento utilizar el desarrollo basado en pruebas ( TDD ). Escribo una prueba que replica el error, luego trato de pasar la prueba. A veces, el acto de escribir la prueba ayuda a encontrar el error.

Esto me mantiene alejado del depurador la mayor parte del tiempo y proporciona pruebas de regresión para evitar reintroducir el error.

Algunos enlaces:

TrueWill
fuente
44
Creo que esta respuesta es muy incompleta. No entiendo tantos votos a favor.
Alex
1
Solo recibe tantos votos positivos porque incluye el acrónimo mágico: TDD.
Bjarke Freund-Hansen
@Alex: agregué algunos enlaces. El "Encontrar un error, escribir una prueba" tiene un ejemplo. Puedo ampliar esto, pero realmente es así de simple.
TrueWill
7

Hay una serie de definiciones para la palabra ciencia, pero parece que posiblemente se esté refiriendo a lo que se podría denominar con mayor precisión el " método científico ". El método científico podría resumirse como la observación de algunos fenómenos (presumiblemente un error o un comportamiento inesperado del programa), la formulación de una hipótesis o hipótesis para explicar el comportamiento, y el experimento más probable para probarlo (escribir una prueba que reproduzca el problema de manera confiable).

Los tipos de errores (fenómenos) que pueden ocurrir son prácticamente infinitos y algunos no requieren necesariamente un proceso bien definido. Por ejemplo, a veces observa un error e instantáneamente sabe qué lo causó simplemente porque está muy familiarizado con el código. Otras veces, sabe que, dada alguna entrada (acción, serie de pasos, etc.), se produce un resultado incorrecto (bloqueo, salida incorrecta, etc.). Para esos casos, a menudo no requiere mucho pensamiento "científico". Un poco de pensamiento puede ayudar a reducir el espacio de búsqueda, pero un método común es simplemente recorrer el código en un depurador y ver dónde las cosas salieron mal.

Sin embargo, las situaciones que considero más interesantes y posiblemente dignas de un proceso científico son donde se le entrega un resultado final y se le pide que explique cómo sucedió. Un ejemplo obvio de esto es un volcado por caída. Puede cargar el volcado de memoria y observar el estado del sistema y su trabajo es explicar cómo llegó en ese estado. El volcado de bloqueo (o núcleo) puede mostrar una excepción, punto muerto, error interno o algún estado "indeseable" según lo definido por el usuario (por ejemplo, lentitud). Para estas situaciones, generalmente sigo los siguientes pasos:

  • Observación estrecha : estudie la información que rodea directamente el problema específico, si corresponde. Las cosas obvias aquí son la pila de llamadas, las variables locales si puede verlas, las líneas de código que rodean el problema. Este tipo de estudio de ubicación específica no siempre es aplicable. Por ejemplo, estudiar un sistema "lento" puede no tener una ubicación de inicio obvia como esta, pero una situación de bloqueo o error interno probablemente tendrá un punto de interés inmediato y obvio. Un paso específico aquí podría ser utilizar herramientas como windbg (ejecute! Analyse -v en un volcado de memoria cargado y mire lo que le dice).

  • Observación amplia : estudie otras partes del sistema. Examine el estado de todos los subprocesos en el sistema, mire cualquier información global (número de usuarios / operaciones / artículos, transacciones / procesos / widgets activos, etc.), información del sistema (SO), etc. Si el usuario proporcionó detalles externos , piense en aquellos en conjunto con lo que ha observado. Por ejemplo, si le dijeron que el problema ocurre todos los martes por la tarde, pregúntese qué podría significar eso.

  • Hipotetizar: Esta es la parte realmente divertida (y no estoy siendo gracioso acerca de que sea divertido). A menudo requiere una gran cantidad de pensamiento lógico a la inversa. Puede ser muy divertido pensar en cómo el sistema entró en el estado actual. Sospecho que esta es la parte que mucha gente piensa que es un arte. Y supongo que podría ser si el programador simplemente comienza a arrojarle cosas al azar para ver qué queda. Pero con experiencia, este puede ser un proceso bastante bien definido. Si piensa muy lógicamente en este punto, a menudo es posible definir posibles conjuntos de caminos que condujeron al estado dado. Sé que estamos en el estado S5. Para que eso suceda, S4a o S4b debían ocurrir y quizás S3 antes que S4a, etc. Más a menudo que no, puede haber múltiples elementos que podrían conducir a un estado dado. A veces puede ser útil escribir en un bloc de notas un diagrama de flujo o estado simple o una serie de pasos relacionados con el tiempo. Los procesos reales aquí variarán mucho dependiendo de la situación, pero un pensamiento serio (y un reexamen en los pasos anteriores) en este momento a menudo proporcionará una o más respuestas plausibles. También tenga en cuenta que una parte extremadamente importante de este paso es eliminar las cosas que son imposibles. Eliminar lo imposible puede ayudar a recortar el espacio de la solución (recuerde lo que dijo Sherlock Holmes sobre lo que queda después de eliminar lo imposible). También tenga en cuenta que una parte extremadamente importante de este paso es eliminar las cosas que son imposibles. Eliminar lo imposible puede ayudar a recortar el espacio de la solución (recuerde lo que dijo Sherlock Holmes sobre lo que queda después de eliminar lo imposible). También tenga en cuenta que una parte extremadamente importante de este paso es eliminar las cosas que son imposibles. Eliminar lo imposible puede ayudar a recortar el espacio de la solución (recuerde lo que dijo Sherlock Holmes sobre lo que queda después de eliminar lo imposible).

  • Experimento : en esta etapa, intente reproducir el problema en función de las hipótesis derivadas en el paso anterior. Si pensaste seriamente en el paso anterior, esto debería ser muy sencillo. A veces "engaño" y modifico la base del código para ayudar a una prueba dada. Por ejemplo, recientemente estaba investigando un accidente que concluí que era por una condición de carrera. Para verificarlo, simplemente coloco un Sleep (500) entre un par de líneas de código para permitir que otro hilo haga sus cosas malas en el momento "correcto". No sé si esto está permitido en la ciencia "real", pero es perfectamente razonable en el código que posee.

Si logra reproducirlo, lo más probable es que haya terminado (todo lo que queda es el simple paso de arreglarlo ... pero eso es para otro día). Asegúrese de verificar la nueva prueba en el sistema de prueba de regresión. Y debo señalar que tenía la intención de que la afirmación anterior sobre arreglarlo fuera simple de ser irónica. Encontrar una solución e implementarla puede requerir mucho trabajo. Es mi opinión que la reparación de un error no es parte del proceso de depuración, sino que es más bien un desarrollo. Y si la solución está involucrada, eso debería requerir cierta cantidad de diseño y revisión.

Mark Wilkins
fuente
La mayoría de los errores que he visto no han sido reproducibles de manera confiable, y, para el subconjunto que lo eran, la mayoría aún requería un trabajo de depuración significativo después de que se reprodujeran, antes de que cualquier trabajo para solucionarlos pudiera comenzar. Incluso si en lugar de decir "logra reproducirlo", dices, "logra reducir la prueba de una unidad que claramente ejercita el error", diría que el trabajo de depuración no ha terminado. Para mí, la depuración termina una vez que tengo una solución, puedo probar que soluciona el problema, y ​​tengo pruebas confiables de que mi solución es lo que realmente soluciona las cosas.
blueberryfields
Estoy de acuerdo en que puede ser bastante trabajo arreglarlo. De hecho, estaba usando el sarcasmo en mis palabras "simple paso de arreglarlo", pero eso no se ve muy bien en el tipo.
4

Intenta reducir el caso de prueba. Cuando es lo suficientemente pequeño, generalmente es más fácil localizar el código correspondiente que está causando el problema.

Es probable que un nuevo check-in esté causando el problema y que la compilación diaria anterior esté bien. En ese caso, su registro de cambios del control de origen debería ayudarlo a decidir a quién capturar.

Además, si está en C / C ++, considere ejecutar valgrind o purify para aislar problemas relacionados con la memoria.

Fanático23
fuente
2

La parte más difícil de la depuración es aislar el problema, particularmente cuando el problema está enterrado debajo de varias capas. En la universidad estudié grabación de música y, curiosamente, había una clase de Studio Electronics que se aplica directamente aquí. Voy a usar la depuración de un entorno de estudio como una ilustración del proceso de depuración sistemática.

  1. Prueba tus medidores. Usando un tono de prueba a un voltaje calibrado conocido, el medidor debe leer "U" (ganancia unitaria). Traducción: si sus herramientas están rotas, no puede usarlas para descubrir qué más está mal.
  2. Pruebe cada componente / etapa de ganancia trabajando hacia atrás desde el final. Usando el mismo tono de prueba aplicado a la entrada de la etapa, no debería haber cambios en la salida de la etapa. Traducción: Al aislar cada objeto de la salida hacia atrás, estamos generando confianza en nuestro código hasta que encontremos el lugar donde está en mal estado. Si sus herramientas necesitan algunas capas para señalar el problema, debe saber que las capas intermedias no contribuyen a ello.

El código de depuración realmente no es tan diferente. La depuración es mucho más fácil cuando el código arroja una excepción. Puede rastrear hacia atrás desde el rastro de la pila de esa excepción y establecer puntos de interrupción en las posiciones clave. Por lo general, justo después de establecer una variable, o en la línea que llama al método que arroja la excepción. Puede encontrar que uno o más de los valores no son correctos. Si no está bien (un valor nulo cuando no debería haberlo, o el valor está fuera de rango), entonces es un proceso de descubrir por qué no está bien. Los puntos de ruptura en un IDE son equivalentes a los puntos de prueba electrónicos (diseñados para que la sonda de un medidor verifique el circuito).

Ahora, una vez que haya pasado por esa parte difícil de descubrir dónde está mi verdadero problema, escribiré algunas pruebas unitarias para verificarlo en el futuro.

Berin Loritsch
fuente
2

Con los errores desagradables que lucho por localizar a última hora de la tarde, mi estrategia más efectiva es levantarme y alejarme por unos minutos. Por lo general, nuevas ideas sobre posibles fuentes de error comienzan a fluir después de solo 30 segundos.

Arrendajo
fuente
2

Para un enfoque más práctico:

  1. Si el error está relacionado con una excepción no controlada, observe el seguimiento de la pila. La referencia nula, el índice fuera de límites, etc. y sus propias excepciones definidas son las más comunes, puede asignar este error a un desarrollador junior, probablemente sea fácil y una buena experiencia de aprendizaje.

  2. Si no sucede en todas las máquinas, es probable que sea una forma de problema de carrera / enhebrado. Estos son súper divertidos de rastrear, pon a tu aburrido programador senior en él. Muchos registros, buenos conocimientos y buenas herramientas hacen esto.

  3. Otra gran clase de errores es cuando al equipo de prueba o al cliente (s) no les gusta un comportamiento particular. Por ejemplo, no les gusta que decidas mostrar ID de usuario o que cuando buscas no se complete automáticamente. Estos son errores genuinos, considere tener una mejor administración de productos y desarrolladores con una visión más amplia. Al desarrollador le debería tomar un tiempo relativamente corto "arreglar" esto si construye el sistema con la expansión en mente.

  4. El 80% de todos los otros errores se resuelven teniendo un buen sistema de registro y recopilando suficiente información para resolverlos. Utilice el rastreo integrado con múltiples niveles de sistemas de registro complejos como Log4Net / Log4J

  5. los errores de rendimiento son una categoría propia, la regla del jugador de golf aquí es "¡medir primero, arreglar después!", y te sorprendería ver cuántos desarrolladores solo adivinan dónde está el problema y entran para solucionarlo solo para ver luego una mera disminución del 3-4% en el tiempo de respuesta.

Bogdan Gavril MSFT
fuente
Si pudiera hacer +1 en cada uno de esos 5 individualmente, ¡lo haría!
jmort253
1

Tengo dos enfoques de flujo:

  1. Divida el problema dado en partes más pequeñas y luego conquiste cada parte más pequeña siguiendo el Divide and ConquerParadigma.
  2. Cada vez que tengo dudas con respecto a cualquier valor, simplemente imprimo los valores de las variables para ver exactamente qué entra y sale de la variable.

Este enfoque me ha ayudado la mayoría de las veces.

Rachel
fuente