wandres.dev
SOURCES II · Breakpoints sin línea

Cuando no sabes dónde poner el breakpoint

La taxonomía completa de puntos de parada que no son de línea, el árbol de decisión para elegir el correcto según el síntoma, y por qué esta es la técnica que más tiempo ahorra de toda la guía.

⏱ 18 min

Un breakpoint de línea presupone que sabes en qué línea está el problema. La mayoría de los bugs difíciles se caracterizan exactamente por lo contrario: sabes qué pasa, ves el efecto, y no tienes ni idea de qué código lo produce. La respuesta habitual a esa situación es buscar en el proyecto, leer código, poner trazas y esperar. La respuesta correcta es no buscar el código: poner el breakpoint en el efecto y dejar que el depurador te lleve hasta la causa. Eso es lo que hacen los cuatro tipos de punto de parada de este nivel, y aprenderlos es probablemente el cambio de hábito más rentable que puedes hacer como desarrollador web.

🎯 Al terminar esta lección sabrás
  • Enumerar las cuatro familias de breakpoints sin línea y qué evento captura cada una.
  • Elegir la familia adecuada a partir del síntoma observable, sin conocer el código.
  • Invertir el planteamiento de búsqueda: del código al efecto en lugar del efecto al código.
  • Reconocer los síntomas que no tienen breakpoint asociado y qué hacer con ellos.

La inversión que hay que hacer

El planteamiento habitual para depurar es: identificar qué módulo debería ser responsable, abrirlo, leerlo, poner un breakpoint donde parezca, ejecutar, comprobar si pasa por ahí, y repetir. Es una búsqueda desde la causa hipotética hacia el efecto, y su coste depende de lo bien que conozcas el código. En una base de código ajena, o grande, o con framework, el coste es enorme.

Los breakpoints sin línea invierten la dirección. No preguntan dónde está el código: preguntan qué observable quieres vigilar. Y todos los observables que importan tienen su punto de parada.

Si el efecto es una clase que aparece en un elemento, el punto de parada va en la modificación de atributos de ese elemento.

Si el efecto es un nodo que desaparece, va en la eliminación de ese nodo.

Si el efecto es una petición que no debería salir, va en la URL de esa petición.

Si el efecto es algo que ocurre al pulsar, va en el evento de clic.

Si el efecto es un error que aparece en la consola, va en las excepciones.

En los cinco casos, la ejecución se detiene en el instante exacto en que ocurre el efecto, con la pila de llamadas completa apuntando a quien lo provocó. No has tenido que leer una sola línea de código para encontrarlo.

Diez minutos o una hora: esta es la diferencia

Merece la pena poner números a la afirmación del título, porque la diferencia es lo bastante grande como para que suene exagerada y no lo es. Toma un caso completamente típico: una clase CSS aparece en un elemento y no sabes qué código la pone. Es un bug que cualquiera se ha encontrado. La forma habitual de resolverlo consiste en buscar el nombre de la clase en el proyecto, encontrar entre cinco y cuarenta apariciones repartidas entre plantillas, hojas de estilo, ficheros de utilidades y tests, descartar las que son declaraciones de estilo, quedarte con las tres o cuatro que la asignan desde JavaScript, poner una traza en cada una, recargar, reproducir, ver cuál se dispara, y si ninguna se dispara —porque la asignación real está en una librería, o se construye concatenando cadenas, o viene de un objeto de configuración— empezar de nuevo con otra estrategia. Entre veinte minutos y una hora, y con una probabilidad nada despreciable de acabar sin encontrarlo si el nombre de la clase se construye dinámicamente, que es cada vez más frecuente. La otra forma: botón derecho sobre el nodo en el panel de Elements, breakpoint en modificación de atributos, reproducir. La ejecución se detiene en la línea exacta que asigna la clase, con la pila completa detrás, funcione la asignación como funcione y esté donde esté el código, incluido dentro de una librería minificada. Entre diez segundos y dos minutos. La diferencia no es de habilidad ni de experiencia: es de saber que el segundo camino existe. Y lo que hace este nivel el más valioso de todo el tramo es que esa misma asimetría se repite en las cuatro familias. Quién ha cerrado mi modal, quién ha hecho esta petición, quién ha borrado este nodo, dónde se ha lanzado esta excepción antes de que alguien se la tragara: cuatro preguntas que consumen tardes enteras con métodos de búsqueda y que se responden en menos de dos minutos con la herramienta correcta. Si solo te llevas una cosa de esta guía, que sea este nivel.

Las cuatro familias

Breakpoints de evento. Detienen la ejecución cuando se dispara un evento del tipo elegido, en el manejador correspondiente. Cubren toda la interacción del usuario, los eventos del ciclo de vida del documento, los temporizadores, las animaciones y las peticiones. Se eligen por categoría y por tipo en un árbol de casillas.

Breakpoints de DOM. Detienen la ejecución cuando un nodo concreto cambia: cuando se modifica su subárbol, cuando cambian sus atributos, o cuando se elimina. Se ponen desde el menú contextual del nodo en el panel de Elements.

Breakpoints de red. Detienen la ejecución cuando sale una petición cuya URL contiene cierta subcadena, o cualquier petición. Se ponen escribiendo el fragmento de URL en su subpanel.

Breakpoints de excepción. Detienen la ejecución cuando se lanza una excepción, opcionalmente incluyendo las que están dentro de un try. Son dos casillas.

El árbol de decisión

flowchart TB
s[Se el efecto pero no el codigo] --> a{Que observas}
a -->|Cambia una clase o un atributo| d1[Breakpoint de DOM en atributos]
a -->|Aparece o desaparece un nodo| d2[Breakpoint de DOM en subarbol o eliminacion]
a -->|Ocurre al pulsar o teclear| e1[Breakpoint de evento de raton o teclado]
a -->|Ocurre al cargar la pagina| e2[Breakpoint de evento de carga o primera sentencia]
a -->|Ocurre despues de un rato| e3[Breakpoint de evento de temporizador]
a -->|Sale una peticion que no toca| n1[Breakpoint de XHR o fetch por URL]
a -->|Aparece un error en consola| x1[Pausa en excepciones no capturadas]
a -->|Algo falla en silencio| x2[Pausa en excepciones capturadas]
a -->|Cambia el scroll o la URL| w1[Envolver el metodo con trace]
style s fill:#cba6f7,color:#11111b
style d1 fill:#a6e3a1,color:#11111b
style d2 fill:#a6e3a1,color:#11111b
style e1 fill:#89b4fa,color:#11111b
style e2 fill:#89b4fa,color:#11111b
style e3 fill:#89b4fa,color:#11111b
style n1 fill:#f9e2af,color:#11111b
style x1 fill:#f38ba8,color:#11111b
style x2 fill:#f38ba8,color:#11111b
style w1 fill:#fab387,color:#11111b

La rama de la derecha del árbol cubre los casos que no tienen breakpoint propio, y su respuesta es la técnica de envolver un método con una traza, ya vista en el nivel de la consola. Los observables que caen ahí son los cambios de scroll, los cambios de URL mediante la API de historial, las escrituras en almacenamiento, y las llamadas a métodos concretos de una librería.

Lo que hay que asumir para que funcione

Hay una resistencia psicológica a esta forma de trabajar que conviene nombrar, porque es lo que hace que la gente no la adopte aunque la conozca: te detiene en código que no es tuyo. Un breakpoint de modificación de atributos en un componente de React te para dentro del reconciliador, no en tu componente. Un breakpoint de clic te para en el manejador delegado del framework. La primera reacción es “esto no me sirve, esto es código de la librería”.

Es exactamente al revés, y hay dos razones.

La primera es que la pila de llamadas contiene tu código. El marco superior es de la librería, y tres o cuatro marcos más abajo está tu función, con sus argumentos y su estado. Un vistazo a la pila lleva ahí.

La segunda es que con la lista de ignorados bien configurada, ni siquiera te detiene ahí: el depurador salta los marcos ignorados y te presenta directamente el primer marco de tu código. Esa configuración, que tiene lección propia más adelante, es lo que convierte esta técnica de incómoda en fluida.

Los síntomas que no tienen breakpoint

Para cerrar el mapa, conviene saber qué queda fuera.

Los cambios de estilo computado que no pasan por un atributo. Si una regla CSS empieza a aplicarse porque cambió el tamaño de la ventana o el estado de una media query, no hay ningún código que ejecutar y por tanto nada donde detenerse. Ese es un problema de CSS y se diagnostica en Elements.

Las animaciones y transiciones del motor. Una vez lanzadas, corren en el compositor sin JavaScript. El panel de animaciones es la herramienta.

Lo que ocurre dentro de un worker cuyo código no tienes. Se puede depurar seleccionando su contexto, pero los breakpoints de DOM no aplican porque no hay DOM.

El código nativo del navegador. No hay forma de detenerse dentro de la implementación de querySelector.

Para todo lo demás, hay una familia. Las cuatro lecciones siguientes las recorren una por una, con los casos reales de cada una.