Diff: o cálculo de diferenças onipresente
Do gerenciamento de versões do Git à revisão de código, de mudanças de configuração à sincronização de dados — Diff é uma operação fundamental no desenvolvimento de software.
| Cenário |
Tipo de Diff |
Ferramenta |
| Mudanças de código |
Diff por linha |
Git |
| Comparação de configuração |
Diff por chave-valor |
Ferramenta de comparação online |
| Dados JSON |
Diff estruturado |
json-diff |
| Documentos legais |
Diff semântico |
Ferramenta especializada |
| Revisão de tradução |
Diff por caractere |
Comparação online |
I. Princípios do Algoritmo Myers Diff
Ideia central
O algoritmo Myers busca o script de edição mais curto (SES) que converte o texto fonte no texto destino — o menor número de operações de inserção/remoção.
Fonte: ABCABBA
Destino: CBABAC
Script de edição:
- A (remover A)
B (manter B)
- C (remover C)
C (manter C → inserir C antes)
A (manter A)
B (manter B)
- B (remover B)
A (manter A)
+ C (inserir C)
Diagrama do algoritmo
0 1 2 3 4 5 6 7
┌───┬───┬───┬───┬───┬───┬───┬───┐
0 │ → │ ↘ │ │ │ │ │ │ │ A
├───┼───┼───┼───┼───┼───┼───┼───┤
1 │ ↓ │ │ ↘ │ │ → │ ↘ │ │ │ B
├───┼───┼───┼───┼───┼───┼───┼───┤
2 │ │ ↓ │ │ ↘ │ ↓ │ │ ↘ │ │ C
├───┼───┼───┼───┼───┼───┼───┼───┤
3 │ │ │ ↓ │ │ │ ↓ │ │ ↘ │ A
├───┼───┼───┼───┼───┼───┼───┼───┤
4 │ │ │ │ ↓ │ ↘ │ │ ↓ │ │ B
├───┼───┼───┼───┼───┼───┼───┼───┤
5 │ │ │ │ │ ↓ │ │ │ ↓ │ B
├───┼───┼───┼───┼───┼───┼───┼───┤
6 │ │ │ │ │ │ ↓ │ ↘ │ │ A
└───┴───┴───┴───┴───┴───┴───┴───┘
C B A B A C B A
→ = direita (remover) ↓ = baixo (inserir) ↘ = diagonal (manter)
Complexidade de tempo
| Caso |
Complexidade |
Descrição |
| Ótimo |
O(ND) |
N=comprimento do texto, D=número de diferenças |
| Pior |
O((N+M)D) |
Textos completamente diferentes |
| Típico |
O(N+D²) |
Poucas diferenças |
II. Uso da ferramenta de comparação de texto do Tools Ku
Passos de operação
- Abra a Ferramenta de comparação de texto
- Cole o texto original no lado esquerdo
- Cole o texto modificado no lado direito
- Veja os resultados das diferenças:
- Vermelho: linhas removidas
- Verde: linhas adicionadas
- Amarelo: linhas modificadas
- Suporta destaque de diferenças por caractere, preciso até cada mudança de caractere
Modos de comparação
| Modo |
Granularidade |
Caso de uso |
| Por linha |
Comparação por linha |
Mudanças de código |
| Por palavra |
Comparação por palavra |
Revisão de documentos |
| Por caractere |
Comparação por caractere |
Correção precisa |
III. Técnicas Especiais de JSON Diff
Usando a ferramenta JSON Diff
- Abra a Ferramenta JSON Diff
- Cole dois dados JSON
- Veja as diferenças estruturadas:
- Chaves adicionadas/removidas/modificadas
- Mudanças de valores (incluindo mudanças de tipo)
- Caminhos aninhados
JSON Diff vs Diff de texto
// Original
{ "name": "张三", "age": 25, "tags": ["a", "b"] }
// Modificado
{ "name": "张三", "age": 26, "tags": ["a", "c"], "email": "z@test.com" }
| Método de comparação |
Resultado |
| Diff de texto |
Mostra diferenças por linha, difícil de entender |
| JSON Diff |
age: 25→26, tags[1]: "b"→"c", +email: "z@test.com" |
--- a/config.json
+++ b/config.json
@@ -1,5 +1,6 @@
{
"name": "my-project",
- "version": "1.0.0",
+ "version": "2.0.0",
"description": "A tool",
- "private": true
+ "private": true,
+ "license": "MIT"
}
| Símbolo |
Significado |
--- |
Arquivo original |
+++ |
Arquivo modificado |
@@ -a,b +c,d @@ |
Posição da diferença (original da linha a, b linhas; modificado da linha c, d linhas) |
- |
Linha removida |
+ |
Linha adicionada |
(espaço) |
Linha de contexto |
V. Geração e Aplicação de Patches
Gerar patch
import * as Diff from 'diff';
const oldText = 'Hello World\nGoodbye World';
const newText = 'Hello World\nGoodbye Cruel World';
// Gerar patch
const patch = Diff.createPatch('file.txt', oldText, newText, 'old', 'new');
console.log(patch);
Saída:
--- old/file.txt
+++ new/file.txt
@@ -1,2 +1,2 @@
Hello World
-Goodbye World
+Goodbye Cruel World
Aplicar patch
// Aplicar para frente
const result = Diff.applyPatch(oldText, patch);
// Aplicar reversamente (reverter)
const reversed = Diff.applyPatch(newText, Diff.reversePatch(patch));
Mesclagem de três vias
const base = 'Texto original';
const branchA = 'Modificação de A';
const branchB = 'Modificação de B';
const merged = Diff.mergeDiff3(base, branchA, branchB);
// Mesclagem automática, marca conflitos com <<<<<<< / ======= / >>>>>>>
VI. Cenários de Aplicação Prática
Detecção de mudanças em arquivos de configuração
const oldConfig = JSON.parse(fs.readFileSync('config.old.json', 'utf-8'));
const newConfig = JSON.parse(fs.readFileSync('config.new.json', 'utf-8'));
const diff = Diff.diffJson(oldConfig, newConfig);
diff.forEach(part => {
if (part.added) console.log('Adicionado:', JSON.stringify(part.value, null, 2));
if (part.removed) console.log('Removido:', JSON.stringify(part.value, null, 2));
});
Revisão de arquivos de tradução
// Comparar a consistência de chaves de tradução chinês-inglês
const zhKeys = Object.keys(flatten(zhTranslation));
const enKeys = Object.keys(flatten(enTranslation));
const missingInEn = zhKeys.filter(k => !enKeys.includes(k));
const missingInZh = enKeys.filter(k => !zhKeys.includes(k));
Testes de regressão de respostas de API
const expected = await fs.readFile('fixtures/expected.json', 'utf-8');
const actual = await fetch('/api/data').then(r => r.json());
const diff = Diff.diffJson(JSON.parse(expected), actual);
if (diff.some(d => d.added || d.removed)) {
throw new Error('A resposta da API não corresponde ao esperado');
}
VII. Otimização de Desempenho
| Estratégia de otimização |
Efeito |
Aplicável a |
| Limitar linhas de contexto |
Reduz o tamanho da saída |
Arquivos grandes |
| Pré-processamento com hash |
Pula linhas idênticas |
Arquivos muito grandes |
| Comparação por blocos |
Processamento paralelo |
Textos muito longos |
| Nível de linha antes do nível de caractere |
Reduz a carga de cálculo |
Arquivos de código |
// Limitar linhas de contexto
const patch = Diff.createPatch('file', old, new, '', '', { context: 3 });