Debugging en JavaScript: encontrar la causa de un bug | Nicolás Garzón
Debugging
JavaScript
Copiar function calculateTotal ( items ) {
return items. reduce (
( total, item ) => total + item. price,
0 ,
) ;
} Texto
Copiar El total aparece como 01020.JavaScript
Copiar [
{ price : "10" } ,
{ price : "20" } ,
] El operador + concatena cuando uno de los operandos es string.
Corregir solo la presentación:
JavaScript
Copiar Number ( total) ; puede ocultar que los datos entraron con un tipo incorrecto.
Un buen fix actúa en la frontera donde se originó el dato inválido.
Texto
Copiar 1. Definir el comportamiento esperado.
2. Reproducir el problema.
3. Reducir el caso.
4. Observar el estado real.
5. Formular una hipótesis.
6. Probar una sola variable.
7. Corregir la causa.
8. Verificar casos relacionados.
9. Evitar que vuelva a ocurrir.“No funciona” no describe un problema investigable.
Texto
Copiar Al enviar una cantidad de 0, el formulario muestra 1 y crea una orden con una unidad.
Entrada.
Acción.
Resultado actual.
Resultado esperado.
Entorno donde ocurre.
Un bug reproducible permite comparar intentos.
Datos exactos.
Orden de acciones.
Usuario o permisos relevantes.
Navegador o runtime.
Estado inicial.
Frecuencia.
Si aparece una vez entre cien, intenta identificar qué cambia entre ejecuciones: tiempo, orden, red, cache, estado compartido o datos.
JavaScript
Copiar const products = await loadProducts ( ) ;
const visible = applyPermissions ( products) ;
const sorted = sortProducts ( visible) ;
renderProducts ( sorted) ; Pregunta en qué punto aparece el valor incorrecto.
JavaScript
Copiar console. log ( { products } ) ;
console. log ( { visible } ) ;
console. log ( { sorted } ) ; Después elimina partes no necesarias hasta conservar el fallo mínimo.
Un caso pequeño reduce hipótesis y facilita escribir una prueba.
Texto
Copiar TypeError: Cannot read properties of undefined (reading 'name')
at renderProduct (product-card.js:18)
at renderProducts (products.js:42)La primera línea explica el tipo de operación. La traza muestra el camino de llamadas.
No leas únicamente la línea superior. Pregunta:
¿Qué valor era undefined?
¿Quién llamó a la función?
¿Qué datos recibió?
¿La línea visible es el origen o solo el lugar donde explotó?
Un breakpoint pausa la ejecución antes de una línea y permite inspeccionar el estado en ese momento.
Ver variables locales.
Recorrer el Call Stack.
Evaluar expresiones.
Avanzar paso a paso.
Observar cambios en objetos.
Es mejor que llenar el código de logs cuando necesitas entender una secuencia.
JavaScript
Copiar function calculateTotal ( items ) {
debugger ;
return items. reduce (
( total, item ) => total + item. price,
0 ,
) ;
} Con DevTools abierto, la ejecución se pausa en esa sentencia.
Retírala antes de integrar el cambio. Un debugger olvidado puede detener la aplicación de quien tenga herramientas abiertas.
Pausa siempre al alcanzar una línea.
Pausa solo cuando se cumple una expresión.
Texto
Copiar item.id === "product-42"Es útil dentro de bucles donde solo falla un elemento.
DevTools puede pausar cuando se lanza una excepción:
Pausar en excepciones capturadas ayuda a encontrar código que está escondiendo errores mediante catch.
Registra un valor sin modificar el archivo ni detener la ejecución.
Es útil para observar una ruta frecuente con menos interrupción.
En el navegador puedes pausar ante ciertos eventos, cambios del DOM o solicitudes. Son herramientas del entorno, no características del lenguaje, pero ayudan a encontrar quién inició una acción.
Ejecuta la línea actual sin entrar visualmente en cada función llamada.
Entra en la función para observar su ejecución.
Termina la función actual y vuelve a quien la llamó.
Úsalos según la pregunta. Si sabes que una utilidad funciona, haz step over. Si sospechas de su contrato, entra.
Cuando la ejecución está pausada, revisa:
Variables locales.
Parámetros.
Closures.
this.
Ámbitos superiores.
Frame actual.
Funciones que llevaron hasta allí.
No mires solo el valor incorrecto. Busca el primer frame donde dejó de ser correcto.
Puedes mantener expresiones visibles mientras avanzas.
JavaScript
Copiar item. price
Number ( item. price)
total
items. lengthUna watch expression responde una pregunta específica. Evita agregar objetos enormes sin saber qué propiedad investigas.
console pertenece al entorno, no al núcleo de ECMAScript. Navegadores y Node.js ofrecen implementaciones similares, pero algunos métodos y formatos varían.
JavaScript
Copiar console. log ( value) ;
console. table ( products) ;
console. dir ( element) ;
console. trace ( "Reached here" ) ;
console. group ( "Checkout" ) ;
console. groupEnd ( ) ;
console. time ( "load-products" ) ;
console. timeEnd ( "load-products" ) ;
console. count ( "render" ) ;
console. assert ( total >= 0 , "Negative total" ) ; Los logs deben responder preguntas. Un objeto sin etiqueta repetido veinte veces suele crear más ruido.
JavaScript
Copiar console. log ( "checkout input" , {
orderId : order. id,
itemCount : order. items. length,
} ) ; No registres secretos ni datos personales innecesarios.
Las herramientas pueden mostrar objetos por referencia. Si expandes un objeto después de que cambió, podrías observar su estado actual y no exactamente el estado que tenía al llamar a console.log.
Para valores pequeños, registra propiedades concretas:
JavaScript
Copiar console. log ( {
status : order. status,
total : order. total,
} ) ; O crea una copia apropiada para diagnóstico, sabiendo que una copia superficial no congela estructuras anidadas.
Aplicaciones modernas suelen ejecutar código transformado, agrupado o minificado.
Un source map relaciona ese código generado con los archivos originales.
Texto
Copiar código original
↕
source map
↕
código ejecutadoCuando el bundler y el servidor los ofrecen correctamente, DevTools puede mostrar TypeScript o JavaScript fuente y mapear breakpoints y stack traces.
No publiques source maps sensibles sin decidir su estrategia. Pueden facilitar diagnóstico, pero también revelar código fuente al público según cómo se sirvan.
JavaScript
Copiar button. addEventListener ( "click" , async ( ) => {
const order = await loadOrder ( ) ;
renderOrder ( order) ;
} ) ; La ejecución atraviesa eventos, promesas y callbacks.
Herramientas modernas pueden mostrar trazas asíncronas, pero debes distinguir:
Quién programó la operación.
Cuándo se reanudó.
Qué estado cambió durante la espera.
Si una promesa fue esperada o quedó flotando.
El Event Loop y las promesas se estudiarán en el siguiente bloque.
JavaScript
Copiar const original = {
settings : {
theme : "dark" ,
} ,
} ;
const copy = { ... original } ;
copy. settings. theme = "light" ; El bug aparece porque ambas referencias comparten settings.
Busca quién escribe la propiedad.
Coloca un breakpoint en la asignación.
Revisa referencias compartidas.
Comprueba la identidad con ===.
Usa herramientas de detección de cambios cuando corresponda.
Si el bug apareció después de un cambio:
Revisa el diff.
Reduce commits sospechosos.
Prueba una versión anterior.
Aísla configuración y dependencias.
No asumas que la última línea editada es la causa. El cambio puede haber revelado una suposición antigua.
Texto
Copiar Cambiar el parser, agregar timeout, copiar el objeto y actualizar la dependencia al mismo tiempo.Si funciona, no sabrás qué lo corrigió.
Texto
Copiar Hipótesis: price llega como string.
Prueba: inspeccionar typeof price antes de reduce.
Resultado: confirmado o descartado.
El caso que fallaba.
Casos cercanos.
Límites.
Regresiones.
Diferentes entornos relevantes.
Después añade una protección apropiada:
Test.
Validación.
Tipo.
Invariante.
Alerta.
Documentación del contrato.
JavaScript
Copiar const items = [
{ price : "10" } ,
{ price : "20" } ,
] ;
calculateTotal ( items) ; JavaScript
Copiar for ( const item of items) {
console. log ( {
price : item. price,
type : typeof item. price,
} ) ;
} JavaScript
Copiar function parseItem ( value ) {
const price = Number ( value. price) ;
if ( ! Number. isFinite ( price) ) {
throw new ValidationError (
"Item price is invalid" ,
) ;
}
return {
... value,
price,
} ;
} El núcleo recibe números confiables y no necesita corregir tipos durante cada cálculo.
Cambiar código sin reproducir el problema.
Corregir el síntoma en lugar del origen.
Usar únicamente console.log cuando un breakpoint sería más claro.
Ignorar el Call Stack.
Cambiar varias variables al mismo tiempo.
No verificar casos vecinos después del fix.
Dejar debugger o logs sensibles.
Confiar en source maps sin comprobar que se cargaron.
Observar una referencia mutada y creer que era el valor histórico.
Culpar a una dependencia antes de reducir el caso propio.
Debugging es un proceso de observación e hipótesis.
Define entrada, resultado actual y resultado esperado.
Reduce el problema hasta encontrar el primer punto incorrecto.
Breakpoints permiten inspeccionar estado y flujo sin modificar cada línea.
El Call Stack muestra cómo llegaste al fallo.
Los logs deben responder preguntas concretas.
Source maps conectan código ejecutado con código fuente.
Corrige la causa y añade una barrera para evitar la regresión.
¿Por qué convertir total a Number al final puede ser un fix insuficiente?
Respuesta Porque corrige el síntoma después de que datos con tipos incorrectos ya atravesaron el sistema. La causa está en la frontera que aceptó precios como strings; otros cálculos pueden seguir fallando.
Strict mode explica qué comportamientos inseguros o ambiguos cambia JavaScript al ejecutar código estricto.