Uma função auxiliar passa a retornar segundos em vez de milissegundos. Os argumentos e o tipo retornado continuam iguais. Um chamador deixa de solicitar atualização, outro continua ordenando corretamente e uma coluna CSV fica silenciosamente mil vezes menor. Uma lista de referências não informa, sozinha, quais desses resultados ocorreram.
Suponha uma aplicação que lista sessões, decide quando atualizá-las e exporta suas idades. É um exemplo construído para aprendizado; os resultados abaixo vêm da execução do JavaScript com entradas fixas e asserções inalteradas. Baixe o exemplo completo para reproduzi-los.
Defina a mudança antes de listar chamadores
O download mantém duas versões da função em elapsed.mjs, permitindo executar os mesmos consumidores com ambas:
export function elapsedBefore(nowMs, startedMs) {
return nowMs - startedMs;
}
export function elapsedAfter(nowMs, startedMs) {
return (nowMs - startedMs) / 1000;
}
As duas entradas continuam sendo timestamps em milissegundos. O retorno proposto é em segundos, incluindo frações: uma idade de 2_500 milissegundos vira 2.5, não 2. Com nowMs = 60_000 e startedMs = 15_000, o resultado muda de 45_000 para 45.
Isso torna a pergunta precisa: quais consumidores dependem da unidade antiga e quais dependem apenas de uma propriedade preservada pela conversão? Uma verificação de tipos que vê números nas duas versões não responde.
Se as entradas forem desconhecidas, primeiro siga um valor desde sua origem pelo código. Mudar a unidade de saída e mudar a unidade de entrada são edições distintas; este exemplo muda apenas a saída.
Use referências para montar uma lista de candidatos
Busque elapsed no editor ou execute:
rg -n 'elapsed' --glob '*.mjs' .
Abra consumers.mjs. Dois chamadores merecem atenção:
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 o valor com um limite fixo. oldestFirst compara dois valores produzidos pela mesma função. Marque ambos como candidatos e registre o que prometem aos chamadores: atualizar ao atingir trinta segundos e retornar IDs da sessão mais antiga à mais nova sem alterar o array de entrada.
Referências do servidor de linguagem ajudam a navegar quando ele está instalado e configurado. Trate os resultados como pistas. Aliases, funções injetadas, registros e configuração em execução ainda exigem inspeção; a visão de referências não inventaria todos os consumidores que uma implantação pode selecionar.
Siga a função através do registro
app.mjs passa a função para além dos dois consumidores diretos. Seleciona uma fábrica pela configuração e fornece a ela a função com outro nome:
const factory = registry.get(config.exporter);
if (!factory) throw new Error(`Unknown exporter: ${config.exporter}`);
const exporter = factory({ measureAge: elapsed });
Siga register(registry) antes, no mesmo arquivo. Nos modos before e after, chama registerAgeColumn, implementada em 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');
},
}));
}
O cabeçalho CSV promete segundos. Com a função antiga, dividir por 1000 os produz. Com a nova, a divisão acontece duas vezes.
Buscar apenas elapsed não revela a chamada measureAge(...) neste arquivo. Amplie a busca para o alias e a chave de registro após descobri-los:
rg -n 'measureAge|age-column|exporter' --glob '*.mjs' .
A aplicação de teste seleciona explicitamente { exporter: 'age-column' } e confere selectedExporter. Isso conecta o registro ao caminho realmente executado. Encontrar uma fábrica registrada sem conferir a seleção deixaria seu uso indefinido. Em um servidor, o trabalho equivalente pode começar pela rota e configuração que selecionam o caminho da requisição.
Execute as mesmas expectativas nas duas versões
Descompacte e entre em code-change-impact-analysis. Não há dependências externas. A execução registrada usou Node.js 22.22.1; use Node.js 22 ou posterior e execute os comandos separadamente no terminal:
node run.mjs before
node run.mjs after
node run.mjs migrated
Espera-se que after saia com código 1. Não una os comandos com &&, pois isso interromperia antes da versão migrada. Os três modos carregam as mesmas asserções de tests.mjs.
| Versão | Passaram | Falharam | Código de saída |
|---|---|---|---|
before: função em milissegundos, consumidores originais |
7 | 0 | 0 |
after: função em segundos, consumidores originais |
4 | 3 | 1, falha esperada |
migrated: função em segundos, consumidores dependentes de unidade atualizados |
7 | 0 | 0 |
As três falhas em after são as decisões de atualização em 30_000 e 31_000 milissegundos e a saída CSV. O caso logo abaixo do limite continua passando. Os dois testes de ordenação passam, assim como a rejeição de um exportador desconhecido.
Para examinar os valores junto às asserções, execute:
node demo.mjs
Com nowMs = 60_000, a sessão alpha começa em 15_000 e beta em 57_500. As observações impressas incluem:
| Observação | before |
after |
migrated |
|---|---|---|---|
| Atualização aos 30 segundos | true |
false |
true |
| IDs do mais antigo ao mais novo | alpha, beta |
alpha, beta |
alpha, beta |
Idade CSV de alpha |
45 |
0.045 |
45 |
Idade CSV de beta |
2.5 |
0.0025 |
2.5 |
demo.mjs imprime observações; tests.mjs decide se satisfazem o comportamento esperado. O padrão node --test run.mjs executa apenas before, então uma execução padrão verde não valida a proposta.
Separe consumidores quebrados dos testados sem regressão
O resultado é mais útil do que uma contagem de referências:
| Consumidor ou área | Constatação | Evidência |
|---|---|---|
needsRefresh |
Afetado, confirmado | Os testes inalterados do limite de trinta segundos falham com a função em segundos. |
oldestFirst |
Regressão descartada para as entradas testadas | Idades distintas, frações, empates, diferenças de um milissegundo e listas vazias ou unitárias mantêm a ordem; a entrada não muda. |
Exportador age-column selecionado |
Afetado nesta configuração, confirmado | O teste seleciona a fábrica e compara o CSV completo. As duas idades ficam mil vezes menores após a mudança. |
| Outras configurações ou consumidores fora do download | Desconhecido | Não foram fornecidos nem executados. |
Há também uma razão para a ordenação: a comparação usa a diferença entre duas idades. Dividir ambas pelo mesmo fator positivo preserva o sinal para essas entradas. Ela precisa da ordem relativa; o limite de atualização e o exportador precisam de uma unidade específica.
Esse raciocínio ajuda a escolher testes. Não permite afirmar que todos os números possíveis foram verificados. As fixtures usam timestamps inteiros pequenos e finitos em milissegundos. Valores extremos de ponto flutuante, entradas inválidas, agendamento assíncrono de atualização e um leitor CSV real ficam fora da execução. O download contém um exportador; não afirma cobrir um inventário de plugins de produção.
Migre os consumidores dependentes da unidade
Mantenha elapsedAfter. Em consumers.mjs, a função de atualização migrada compara segundos com segundos:
export function needsRefreshSeconds(elapsed, nowMs, lastSyncMs) {
return elapsed(nowMs, lastSyncMs) >= 30;
}
Em age-column.mjs, registerAgeColumnSeconds emite measureAge(...) diretamente, removendo a segunda divisão. app.mjs escolhe essas duas implementações quando o modo é migrated. oldestFirst permanece inalterada.
Os testes continuam exigindo os mesmos resultados visíveis: atualização aos trinta segundos, os mesmos IDs ordenados e segundos no CSV. As sete verificações finais passando mostram que esses resultados foram restaurados para a fixture. Se seu patch também muda resultados esperados, revise as asserções junto à implementação antes de considerar a execução verde equivalente.
Use a IA para sugerir o próximo arquivo
Durante a leitura, uma pergunta opcional pode restringir a busca:
Siga a função passada como
elapsed, incluindo aliases e fábricas registradas. Para cada consumidor candidato, mostre o código que presume uma unidade ou usa apenas ordenação. Identifique a seleção em execução e a entrada de teste que permitem verificar a consequência. Separe chamadores não testados dos resultados executados.
Confira os caminhos propostos no código e execute os comandos.