wandres.dev
COVERAGE Y CSS OVERVIEW · Encontrar el código muerto

Los límites reales de la cobertura y qué se le escapa

Las siete cosas que la cobertura no puede saber, por qué el código dinámico es invisible para ella, y el procedimiento seguro para eliminar código de verdad.

⏱ 17 min

Esta es la lección que evita que el panel de cobertura cause un incidente. La herramienta produce un dato preciso sobre una pregunta muy estrecha, y la distancia entre esa pregunta y la que uno cree estar haciendo es donde se pierden tardes y donde se rompen aplicaciones. Enumerar explícitamente lo que se le escapa convierte una fuente de decisiones peligrosas en una fuente fiable de hipótesis.

🎯 Al terminar esta lección sabrás
  • Enumerar las siete categorías de código que la cobertura no puede evaluar.
  • Explicar por qué el código construido dinámicamente es invisible.
  • Aplicar el procedimiento seguro de eliminación de código.
  • Elegir la herramienta adecuada cuando la cobertura no basta.

Las siete cosas que se le escapan

Uno: el código condicional que no se ejecutó. La categoría más grande. Manejo de errores, ramas de compatibilidad, casos límite, validaciones que no fallaron. Todo eso existe para lo que no pasó, y precisamente por eso aparece sin usar.

Dos: el código de otras rutas y otros roles. Una sesión cubre lo que hiciste. Las pantallas que no visitaste, las funcionalidades de un rol que no tienes, los flujos que no completaste: todo sin usar.

Tres: el código que depende del entorno. Rellenos de compatibilidad para navegadores que no son el tuyo, ramas por sistema operativo, adaptaciones a capacidades ausentes. En tu navegador no se ejecutan, y en el de un usuario sí.

Cuatro: el CSS de estados y de contextos no alcanzados. Ya visto: estados, consultas de medios, preferencias del sistema.

Cinco: las referencias dinámicas. Aquí está el peligro mayor y merece su propia sección.

Seis: el código ejecutado antes de empezar a grabar. Si instrumentas sin recargar, todo el arranque queda fuera.

Siete: lo que ocurre fuera del hilo principal. El código de un worker no aparece en la cobertura de la página.

🛑
Importante

La combinación más peligrosa es la cobertura más una herramienta de purga automática. La cobertura señala lo que no se usó en una sesión, la purga elimina lo que no encuentra referenciado estáticamente, y ninguna de las dos entiende una clase construida concatenando cadenas. El resultado son estilos que desaparecen para estados que solo ocurren en producción, y bugs que aparecen semanas después sin relación aparente con ningún cambio.

Por qué lo dinámico es invisible

Ninguna herramienta que analice el código estáticamente ni ninguna que registre una ejecución puede saber qué nombres construye tu programa en tiempo de ejecución.

// Cuatro patrones que ninguna herramienta de analisis puede seguir
const estado = 'error';

// 1. Clase construida por concatenacion: "alerta-error" no aparece en ningun sitio
elemento.className = 'alerta-' + estado;

// 2. Acceso a una tabla por clave calculada
const manejadores = { guardar, borrar, duplicar };
manejadores[accionDelUsuario]();     // ninguna de las tres parece llamada

// 3. Importacion con ruta construida
const modulo = await import(`./idiomas/${codigoDeIdioma}.js`);

// 4. Selector construido a partir de datos
document.querySelectorAll('[data-tipo="' + tipoRecibido + '"]');

// La version legible para las herramientas: nombres completos y explicitos
const CLASES = { error: 'alerta-error', aviso: 'alerta-aviso', ok: 'alerta-ok' };
elemento.className = CLASES[estado];   // los tres nombres aparecen literalmente

La corrección del último bloque no es estética: hace que los nombres existan literalmente en el código, lo que los vuelve encontrables por una búsqueda de texto, por una herramienta de purga y por cualquier persona que investigue. Es una de las prácticas que más facilita el mantenimiento a largo plazo, y su coste es escribir un mapa.

Cuando la construcción dinámica es inevitable, la salida es declarar explícitamente lo que hay que preservar. Todas las herramientas de purga tienen un mecanismo de lista blanca, y usarlo es obligatorio en cuanto hay nombres construidos.

El procedimiento seguro para eliminar código

Cuando de verdad quieres borrar algo, la cobertura es solo el primer paso de cinco.

Paso 1: la cobertura genera el candidato. Un módulo o una función que no se ejecutó en un recorrido exhaustivo.

Paso 2: búsqueda de texto exhaustiva. El nombre del símbolo, el nombre del fichero, y fragmentos que pudieran aparecer en una construcción dinámica. Incluye plantillas, ficheros de configuración, y contenido que venga de una base de datos si tu aplicación lo tiene.

Paso 3: análisis estático. Las herramientas de análisis detectan exportaciones no usadas y variables muertas dentro del proyecto. Es información complementaria y más fiable que la cobertura para código propio.

Paso 4: instrumentar en producción. El paso decisivo y el que casi nadie da. En lugar de borrar, añade un aviso y despliega. Si en dos semanas nadie lo ha ejecutado, ya no es una hipótesis.

// Instrumentacion para confirmar codigo muerto en produccion antes de borrarlo
const marcadorDeUso = (() => {
  const yaAvisados = new Set();
  return function marcarUso(etiqueta, extra = {}) {
    if (yaAvisados.has(etiqueta)) return;   // una vez por sesion basta
    yaAvisados.add(etiqueta);
    const carga = JSON.stringify({
      etiqueta,
      ruta: location.pathname,
      version: window.__VERSION_APP ?? null,
      cuando: Date.now(),
      pila: new Error().stack?.split('\n').slice(1, 4).join(' | '),
      ...extra
    });
    navigator.sendBeacon?.('/telemetria/uso-de-codigo', carga);
    if (location.hostname === 'localhost') console.warn('[uso]', etiqueta);
  };
})();

// Se coloca al principio de lo que sospechas muerto
function exportarAFormatoAntiguo(datos) {
  marcadorDeUso('exportarAFormatoAntiguo');
  // ... el codigo sigue funcionando igual
  return datos;
}

// Para modulos enteros, en la primera linea del modulo
marcadorDeUso('modulo:informes-heredados');

window.marcarUso = marcadorDeUso;

Paso 5: borrar, con la posibilidad de revertir clara. Un cambio pequeño y separado, no mezclado con otros, para que revertirlo sea trivial si aparece algo.

Ese procedimiento parece largo y en la práctica cuesta poco: los pasos dos y tres son minutos, y el cuatro es esperar. Lo que evita es la clase de incidente que ocurre tres semanas después de un borrado y que nadie relaciona con él.

Cuándo la cobertura no es la herramienta

Tres preguntas que la gente intenta responder con este panel y para las que hay herramientas mejores.

“¿Qué código de mi proyecto no se usa nunca?” El análisis estático de exportaciones no usadas responde esto mucho mejor, porque razona sobre el grafo de importaciones completo en lugar de sobre una ejecución.

“¿Qué parte de mi código está cubierta por pruebas?” Es una pregunta distinta con su propia herramienta, integrada en el ejecutor de pruebas. La cobertura de las DevTools mide una sesión de navegador, no una batería de pruebas.

“¿Qué funcionalidades usan mis usuarios?” Es una pregunta de producto y se responde con analítica de producto, no con cobertura de código. Y suele ser la pregunta que de verdad se quería hacer.

El código muerto no es el problema: el problema es el código que nadie sabe si está vivo

Al final de este nivel merece la pena reencuadrar el objetivo, porque perseguir código muerto suele ser una mala inversión de tiempo y el motivo es que el coste del código muerto es menor de lo que la intuición dice. Unos kilobytes que nunca se ejecutan cuestan un poco de descarga y un poco de análisis, y ese coste es real pero pequeño comparado con casi cualquier otra cosa de un proyecto. El coste que sí es grande, y que no se mide en bytes, es el del código de estado desconocido: aquel que nadie sabe si hace falta, que nadie se atreve a tocar, que hay que mantener compilando, que aparece en cada búsqueda y confunde, que se migra en cada cambio de versión de las dependencias, y que hace que cada refactorización sea más cara. Ese código no aparece en ninguna métrica de rendimiento y es el que de verdad frena a un equipo. La conclusión es que la pregunta útil no es “qué puedo borrar para que pese menos” sino “qué partes de este sistema no sé si están vivas, y cómo lo averiguo”, y esa reformulación cambia la herramienta: no es la cobertura, es la instrumentación en producción. Un marcador de uso desplegado durante unas semanas responde con certeza lo que la cobertura solo insinúa, y responde además una pregunta más rica, porque dice cuántas veces y quién. Con ese dato, las decisiones dejan de ser técnicas y pasan a ser de producto: esta funcionalidad la usan tres clientes al mes, ¿la mantenemos? Esa conversación es mucho más valiosa que la de cuántos kilobytes se ahorran. Y hay un beneficio adicional que se nota al cabo de un año: un equipo que instrumenta el uso de sus funcionalidades acumula un mapa de qué parte de su producto está viva, y ese mapa es lo que permite borrar con confianza en lugar de acumular por miedo, que es la razón real de que los proyectos maduros arrastren tanto peso.