Expresiones vigiladas: el panel que convierte el paso a paso en observación
El subpanel de watch, cuándo compensa frente a la consola, y las expresiones que merece la pena tener puestas durante una sesión de depuración.
Recorrer una función paso a paso mirando el panel de scope funciona hasta que la respuesta no está en una variable sino en una expresión sobre varias: la longitud de un array, el resultado de una comparación, una propiedad anidada tres niveles. Escribirla en la consola en cada paso es tedioso y rompe el ritmo. El subpanel de expresiones vigiladas las reevalúa solas en cada parada, en el marco que tengas seleccionado, y eso convierte el paso a paso en una observación continua de exactamente lo que te importa.
- Añadir expresiones vigiladas y entender en qué contexto se evalúan.
- Decidir entre una expresión vigiladas y una consulta puntual en la consola.
- Escribir expresiones que resuman estado complejo en un valor legible.
- Evitar las expresiones con efectos laterales y reconocer sus síntomas.
Cómo funcionan
El subpanel de watch tiene un botón de añadir donde se escribe una expresión JavaScript. A partir de ahí, cada vez que la ejecución se detenga —o cada vez que cambies de marco en la pila— todas las expresiones se reevalúan y se muestra su resultado.
El detalle crítico: se evalúan en el marco seleccionado, no en el marco donde estás detenido. Si seleccionas un marco tres niveles más abajo en la pila, las expresiones se recalculan en ese ámbito. Una expresión que referencia una variable local aparecerá como no definida en los marcos donde esa variable no existe, y con su valor en los que sí.
Ese comportamiento es lo que las hace potentes para la bisección sobre la pila: pones una expresión sobre el valor sospechoso y vas subiendo marcos, viendo cómo cambia el resultado sin escribir nada.
Las expresiones persisten entre sesiones de depuración y entre recargas, así que se acumulan. Conviene limpiarlas cuando cambias de problema, porque una lista con quince expresiones de las cuales doce dan error es ruido.
Igual que las expresiones en vivo de la consola, las vigiladas se evalúan de verdad y sus efectos laterales ocurren. Una expresión con una llamada que muta, o con un getter que hace trabajo, se ejecuta en cada parada. Si al recorrer código el estado cambia de formas que no explicas, revisa esta lista antes que ninguna otra cosa.
Watch o consola
Las dos herramientas se solapan y la elección tiene una regla clara.
Consola para consultas puntuales, exploratorias, de una sola vez. Preguntas del tipo “a ver qué tiene esto” o “cuánto mide aquello”. La consola además permite expandir objetos, ejecutar cosas complejas y encadenar.
Watch para valores que quieres seguir a lo largo de una secuencia. Si vas a mirar lo mismo en cinco pasos consecutivos, es una expresión vigilada. Si lo vas a mirar una vez, es la consola.
Hay un tercer criterio menos obvio: las expresiones vigiladas son mejores cuando lo que quieres es notar el cambio. Un número que aparece siempre en el mismo sitio de la pantalla y que salta de 12 a 0 llama la atención; el mismo valor impreso entre veinte líneas de consola, no.
Expresiones que merecen estar puestas
Las que rinden no son las triviales —una variable local ya está en el panel de scope— sino las que resumen.
// Tamano de una coleccion, que suele ser donde esta el bug
items.length
// Comparacion que decide una rama, evaluada antes de llegar a ella
estado.cargando && !estado.error
// Propiedad anidada que puede ser undefined en cualquier nivel
respuesta?.datos?.usuario?.permisos?.length
// Identidad de un objeto, para detectar si es el mismo o una copia nueva
props.config === configAnterior
// Resumen legible de una estructura grande
JSON.stringify(filtros)
// Cuantos elementos cumplen la condicion problematica
items.filter(i => !i.id).length
// El valor de this, que en callbacks es donde vive la mitad de los bugs
this?.constructor?.name
La quinta de esa lista, la comparación de identidad, es especialmente útil en frameworks: responde a la pregunta de si un objeto es literalmente el mismo entre dos renders, que es lo que decide si un memo funciona y si un efecto se vuelve a ejecutar.
Y la última merece un comentario. En callbacks, métodos extraídos y funciones flecha mal colocadas, this es una fuente constante de errores silenciosos, porque en módulos su valor es undefined en lugar de ser el global, y el error resultante aparece varias líneas después.
El caso de las expresiones que fallan
Una expresión vigilada que no se puede evaluar en el marco actual muestra un error en lugar de un valor. Eso es informativo y no un problema: significa que esa variable no existe ahí.
Lo que sí conviene evitar es una lista llena de errores permanentes, porque el ojo deja de mirarla. Dos tácticas: usar encadenamiento opcional en todo lo que pueda no existir, y borrar las expresiones cuando cambies de zona del código.
// En vez de esto, que falla fuera de la funcion donde existe pedido
pedido.total
// Esto, que muestra undefined en vez de un error
typeof pedido !== 'undefined' ? pedido.total : '-'
Hay una razón por la que las expresiones vigiladas rinden mucho más de lo que su interfaz sugiere, y no tiene que ver con la comodidad de no reescribir en la consola. Escribir una expresión vigilada obliga a decidir qué debería ser cierto. Cuando pones items.length === filtrados.length + descartados.length en la lista de watch, no estás mirando un valor: estás enunciando una invariante de tu programa y pidiéndole al depurador que te avise cuando deje de cumplirse. Y eso convierte el paso a paso, que sin hipótesis es un ejercicio de mirar pasar código, en un experimento con un criterio de fallo definido. La diferencia práctica es la que separa las dos formas de usar el depurador que ya se mencionaron en el nivel anterior: avanzar esperando que algo llame la atención, frente a avanzar comprobando predicciones. La lista de watch es el sitio donde las predicciones se escriben. Hay una técnica concreta que explota esto y que merece la pena adoptar en bugs difíciles: antes de empezar a recorrer, escribe tres o cuatro expresiones que deberían ser verdaderas durante todo el flujo —una condición sobre el tamaño de una colección, una sobre la consistencia de dos estructuras que deberían estar sincronizadas, una sobre un identificador que no debería cambiar— y luego recorre el código mirando solo si alguna se vuelve falsa. El punto donde eso ocurre es, por definición, el punto donde el programa deja de cumplir sus propias reglas, y ese es el bug o su causa inmediata. Es notablemente más rápido que examinar valores uno por uno, y tiene un beneficio secundario que no es menor: las invariantes que escribes durante la depuración son casi siempre buenas aserciones para dejar en el código, así que la sesión termina dejando el proyecto mejor instrumentado de lo que estaba.