Una función auxiliar empieza a devolver segundos en lugar de milisegundos. Sus argumentos y tipo de retorno parecen iguales. Un llamador deja de solicitar actualizaciones, otro sigue ordenando correctamente y una columna CSV pasa silenciosamente a tener valores mil veces menores. Una lista de referencias no basta para saber cuál de esos resultados ocurrió.

Supongamos una pequeña aplicación que lista sesiones, decide cuándo actualizarlas y exporta su antigüedad. Es un ejemplo didáctico construido; los resultados proceden de ejecutar su JavaScript con entradas fijas y aserciones intactas. Descarga el ejemplo completo para reproducirlos.

Delimita el cambio antes de enumerar llamadores

La descarga conserva ambas versiones de una función en elapsed.mjs, para ejecutar los mismos consumidores con cualquiera de ellas:

export function elapsedBefore(nowMs, startedMs) {
  return nowMs - startedMs;
}

export function elapsedAfter(nowMs, startedMs) {
  return (nowMs - startedMs) / 1000;
}

Ambas entradas siguen siendo marcas de tiempo en milisegundos. El retorno propuesto son segundos, incluidas fracciones: 2_500 milisegundos pasan a 2.5, no a 2. Con nowMs = 60_000 y startedMs = 15_000, el resultado pasa de 45_000 a 45.

Esto concreta la pregunta: ¿qué consumidores necesitan la unidad antigua y cuáles solo necesitan una propiedad que conserva la conversión? Una comprobación de tipos que ve un número en ambas versiones no responde.

Si desconoces las entradas, sigue primero un valor desde su origen por el código. Cambiar la unidad de salida y cambiar la de entrada son ediciones distintas; aquí solo cambia la salida.

Usa referencias para elaborar una lista de candidatos

Busca elapsed en el editor o ejecuta:

rg -n 'elapsed' --glob '*.mjs' .

Abre consumers.mjs. Hay dos llamadas que conviene examinar:

export function needsRefresh(elapsed, nowMs, lastSyncMs) {
  return elapsed(nowMs, lastSyncMs) >= 30_000;
}

export function oldestFirst(elapsed, nowMs, sessions) {
  return [...sessions]
    .sort((a, b) => elapsed(nowMs, b.startedMs) - elapsed(nowMs, a.startedMs))
    .map((session) => session.id);
}

needsRefresh compara el valor con un umbral fijo. oldestFirst compara dos valores producidos por la misma función. Marca ambos como candidatos y anota qué prometen: actualizar al alcanzar treinta segundos y devolver los identificadores de sesión de más antiguo a más reciente sin modificar el array de entrada.

Las referencias de un servidor de lenguaje ayudan a navegar por el proyecto disponible cuando está instalado y configurado. Trátalas como pistas. Los alias, funciones inyectadas, registros y configuración de ejecución requieren inspección; una vista de referencias no es un inventario completo de todos los consumidores que puede seleccionar un despliegue.

Sigue la función a través del registro

app.mjs pasa la función a algo distinto de los dos consumidores directos. Elige una factoría mediante configuración y le entrega la función con otro nombre:

const factory = registry.get(config.exporter);
if (!factory) throw new Error(`Unknown exporter: ${config.exporter}`);
const exporter = factory({ measureAge: elapsed });

Sigue register(registry) más arriba en el mismo archivo. En los modos before y after llama a registerAgeColumn, implementada en age-column.mjs:

export function registerAgeColumn(registry) {
  registry.set('age-column', ({ measureAge }) => ({
    kind: 'age-column',
    rows(nowMs, sessions) {
      return [
        'id,age_seconds',
        ...sessions.map(
          (session) =>
            `${session.id},${measureAge(nowMs, session.startedMs) / 1000}`,
        ),
      ].join('\n');
    },
  }));
}

La cabecera CSV promete segundos. Con la función antigua, dividir por 1000 los proporciona. Con la nueva, la división se hace dos veces.

Buscar solo elapsed no muestra la llamada measureAge(...) en este archivo. Amplía la búsqueda al alias y la clave de registro cuando los descubras:

rg -n 'measureAge|age-column|exporter' --glob '*.mjs' .

La aplicación de prueba selecciona explícitamente { exporter: 'age-column' } y comprueba selectedExporter. Eso conecta la entrada del registro con el recorrido realmente ejecutado. Encontrar una factoría registrada sin comprobar su selección dejaría su uso sin resolver. En un servidor, el trabajo equivalente puede empezar por la ruta y configuración que seleccionan el recorrido de una solicitud.

Ejecuta las mismas expectativas contra ambas versiones

Descomprime la descarga y entra en code-change-impact-analysis. No tiene dependencias externas. La ejecución registrada usó Node.js 22.22.1; usa Node.js 22 o posterior y ejecuta estos comandos por separado:

node run.mjs before
node run.mjs after
node run.mjs migrated

Se espera que after termine con código 1. No unas los comandos con &&, porque se detendrían antes de la versión migrada. Los tres modos cargan las mismas aserciones de tests.mjs.

Versión Pasan Fallan Código de salida
before: función en milisegundos, consumidores originales 7 0 0
after: función en segundos, consumidores originales 4 3 1, fallo esperado
migrated: función en segundos, consumidores dependientes de la unidad actualizados 7 0 0

Las tres comprobaciones que fallan en after son las decisiones de actualización a 30_000 y 31_000 milisegundos y la salida CSV. La comprobación justo por debajo del umbral sigue pasando. Pasan ambas pruebas de ordenación y el rechazo de un nombre de exportador desconocido.

Para examinar los valores junto a las aserciones, ejecuta:

node demo.mjs

Con nowMs = 60_000, la sesión alpha empieza en 15_000 y beta en 57_500. Entre las observaciones impresas están:

Observación before after migrated
Actualizar con antigüedad de 30 segundos true false true
Identificadores de más antiguo a más reciente alpha, beta alpha, beta alpha, beta
Antigüedad CSV de alpha 45 0.045 45
Antigüedad CSV de beta 2.5 0.0025 2.5

demo.mjs imprime observaciones; tests.mjs decide si cumplen el comportamiento esperado. Por defecto, node --test run.mjs solo ejecuta before, por lo que una ejecución verde por defecto no validaría el cambio propuesto.

Distingue un consumidor roto de otro que comprobaste y descartaste

El resultado es más útil que contar referencias:

Consumidor o área Hallazgo Qué lo respalda
needsRefresh Afectado confirmado Las pruebas intactas del umbral de treinta segundos fallan con la función en segundos.
oldestFirst Regresión descartada para las entradas probadas Antigüedades mixtas, fracciones de segundo, empates, diferencias de un milisegundo y listas vacías o unitarias mantienen el orden; la entrada no cambia.
Exportador age-column seleccionado Afectado confirmado con esta configuración La prueba selecciona esa factoría y compara toda la cadena CSV. Ambos valores son mil veces menores después del cambio.
Otras configuraciones de despliegue o consumidores fuera de la descarga Desconocido No se han proporcionado ni ejecutado.

La ordenación tiene una explicación: compara la diferencia entre dos antigüedades. Dividir ambas por el mismo factor positivo conserva el signo para estas entradas. Necesita orden relativo; el umbral de actualización y el exportador necesitan una unidad concreta.

Este razonamiento ayuda a elegir pruebas. No permite afirmar que se comprobó cualquier entrada numérica posible. Los fixtures usan marcas de tiempo pequeñas, finitas y enteras en milisegundos. Quedan fuera los valores extremos de coma flotante, entradas no válidas, programación asíncrona de actualizaciones y un lector CSV real posterior. La descarga contiene un exportador y no pretende inventariar los plugins de producción.

Migra los consumidores que dependen de la unidad

Conserva elapsedAfter. En consumers.mjs, la función de actualización migrada compara segundos con segundos:

export function needsRefreshSeconds(elapsed, nowMs, lastSyncMs) {
  return elapsed(nowMs, lastSyncMs) >= 30;
}

En age-column.mjs, registerAgeColumnSeconds emite measureAge(...) directamente y elimina la segunda división. app.mjs selecciona esas dos implementaciones cuando el modo es migrated. oldestFirst permanece intacta.

Las pruebas siguen exigiendo los mismos resultados visibles: actualización a los treinta segundos, mismos identificadores ordenados y segundos en CSV. Las siete comprobaciones finales que pasan muestran que se restauraron para este fixture. Si tu parche también cambia resultados esperados, revisa las aserciones junto a la implementación antes de considerar equivalente su ejecución verde.

Usa la IA para sugerir el siguiente archivo que examinar

Durante la lectura, una pregunta opcional puede acotar la búsqueda:

Sigue la función pasada como elapsed, incluidos alias y factorías registradas. Para cada consumidor candidato, muestra el código que presupone una unidad o solo utiliza ordenación. Identifica qué selección de ejecución y entrada de prueba permitirían comprobar la consecuencia. Separa los llamadores sin probar de los resultados ejecutados.

Comprueba los recorridos propuestos en el código y ejecuta los comandos.