Um assistente de IA sugere remover uma verificação de uma pequena função que lê linhas. “O chamador já fornece números de linha válidos”, diz ele. A função parece simples. Antes de aceitar a mudança, você consulta o histórico Git, e a linha aponta para um commit chamado “Compact comparison spacing”.
Isso oferece outro lugar para investigar. As perguntas úteis são qual entrada a verificação protege, quando esse comportamento surgiu e se os chamadores atuais ainda precisam dele. Um pequeno histórico que você pode reconstruir torna essas perguntas concretas.
Comece pela linha suspeita
Suponha que um visualizador de código-fonte use números de linha começando em um. Sua função atual é assim:
def read_line(lines, line_number):
"""Read a source line using a one-based line number."""
if line_number<1:
raise ValueError("line number must be positive")
if line_number > len(lines):
raise IndexError("line number is past the end")
return lines[line_number - 1]
Para as duas linhas ["alpha", "omega"], a linha 1 deve retornar alpha e a linha 2, omega. Em Python, porém, o índice -1 lê o último elemento. Sem a verificação do limite inferior, o número de linha 0 vira o índice -1 e retorna omega. Um número de linha -1 vira o índice -2 e retorna alpha.
Você pode explicar isso pelo código atual. O histórico responde outra pergunta: quando essa rejeição passou a fazer parte da função e o que a mudança incluiu?
Reconstrua os quatro commits
Baixe git-history-lab.py para uma pasta de trabalho vazia. Você precisa de Git 2.29+ e Python 3.9+; não há dependências de pacotes. Execute no seu terminal:
python3 git-history-lab.py create history-lab --record history-check
cd history-lab
git log --reverse --format='%h %s' HEAD
No Windows, use seu inicializador de Python 3 no lugar de python3, se necessário. O fluxo usa Git e Python, sem APIs de macOS. A execução registrada usou Git 2.50.1 e Python 3.9.6 no macOS; Windows e Linux não foram testados separadamente.
O script cria um novo repositório local, executa as verificações e salva as saídas em history-check. Ele recusa um destino existente, usa um autor fictício só nos próprios commits e não altera sua configuração global do Git. Todos os nomes, datas, mensagens de commit e o cenário do visualizador são fixtures de ensino.
ce4c0cf Add one-based source line lookup
e87088e Reject nonpositive source line numbers
7d41ebc Rename line reader module
27a0dc5 Compact comparison spacing
Cada commit também tem uma tag legível:
| Tag | Mudança |
|---|---|
baseline |
Adiciona line_reader.py; os testes cobrem a primeira linha, a última e uma entrada além do fim |
fix-positive-lines |
Adiciona a verificação do limite inferior e testes para 0, -1 e uma lista de entrada vazia |
rename-module |
Renomeia o arquivo para source_lines.py e atualiza o import do teste |
format-spacing |
Muda line_number < 1 para line_number<1 |
O espaçamento compacto chama atenção de propósito para facilitar o reconhecimento da última edição.
Use blame para encontrar o próximo diff
Inspecione a linha 3:
git blame -L 3,3 HEAD -- source_lines.py
git show --format=fuller format-spacing -- source_lines.py
O blame atribui a linha a 27a0dc57, o commit de espaçamento. Seu patch contém:
- if line_number < 1:
+ if line_number<1:
Agora peça ao Git para ignorar espaços em branco ao rastrear a linha:
git blame -w -L 3,4 HEAD -- source_lines.py
O resultado aponta para e87088e0 e o caminho antigo, line_reader.py. O git blame segue automaticamente renomeações de arquivos inteiros; -w ignora espaços em branco na atribuição. A renomeação em si não causou o resultado enganoso aqui; foi a edição posterior do espaçamento. Consulte a documentação oficial do git-blame.
Para esta edição específica, o verificador também compara árvores sintáticas de Python e executa os testes antes e depois. Os dois concordam. Ignorar espaços em branco é uma forma de investigar a atribuição, não um teste de comportamento: indentação e espaços dentro de strings podem importar em Python.
Depois de inspecionar um commit de formatação conhecido, você também pode excluí-lo explicitamente:
git blame --ignore-rev format-spacing -L 3,3 HEAD -- source_lines.py
Isso também chega à correção neste laboratório. Não exclua um commit só porque o título diz “format”; leia primeiro seu patch. O blame registra a atribuição de linhas, não o raciocínio de um desenvolvedor nem sua responsabilidade por um bug.
Busque o texto que sobrevive à reescrita
A mensagem de erro permaneceu igual durante a renomeação e a edição de espaçamento. Busque sua introdução:
git log --format='%h %s' -p -S 'line number must be positive' HEAD -- '*.py'
Isso retorna a correção e seu patch em line_reader.py. Colocar *.py entre aspas deixa a correspondência de caminhos a cargo do Git e inclui o nome antigo. Este repositório só tem um commit que adiciona ou remove essa mensagem.
-S seleciona mudanças no número de ocorrências de uma string. Ele não seleciona toda edição perto de uma ocorrência. Consulte a explicação de pickaxe do Git.
Experimente uma string que mudou durante a formatação:
git log --format='%h %s' -p -S 'line_number < 1' HEAD -- '*.py'
Agora a saída inclui tanto o commit de espaçamento, que remove essa grafia exata, quanto a correção, que a adiciona. A ocorrência extra é útil: leia o que mudou e escolha um fragmento mais estável, se necessário. A ausência de resultado também precisa de interpretação; o texto pode mudar sem alterar sua quantidade de ocorrências.
Para um arquivo conhecido, este laboratório também funciona com:
git log --follow --format='%h %s' -p -S 'line number must be positive' HEAD -- source_lines.py
--follow acompanha o histórico de renomeações de um arquivo. Não é uma busca geral entre arquivos. Em uma investigação real, confira caminhos antigos ou amplie a seleção de caminhos quando uma busca restrita parar cedo demais.
Siga as linhas no tempo com log -L
Se você já conhece o intervalo relevante, peça seus patches ao longo do histórico:
git log --format='%h %s' -L 3,4:source_lines.py HEAD
Neste repositório, a saída inclui a edição de espaços em branco e a adição original da verificação. Ela atravessa a renomeação pura do arquivo, mas o bloco renomeado sem mudanças não recebe um patch separado. Use o log anterior de quatro commits quando precisar da sequência inteira.
Para a função inteira, execute:
git log --format='%h %s' -L :read_line:source_lines.py HEAD
Isso também chega à expressão de indexação original em baseline. O laboratório declara *.py diff=python em .gitattributes para o Git usar suas regras de cabeçalho de função Python. O reconhecimento de funções depende da linguagem e da configuração do diff; consulte definição de um cabeçalho de trecho personalizado. Uma alternativa numérica para este arquivo é:
git log --format='%h %s' -L 1,7:source_lines.py HEAD
Com log -L, o caminho fica dentro do argumento de intervalo; não acrescente um limitador -- path. O intervalo precisa existir na revisão inicial, e o percurso começa em uma única revisão. Essas restrições estão na documentação oficial do git-log.
Históricos reais podem conter merges, código copiado, funções reescritas ou commits antigos ausentes em um clone superficial. Rastrear uma linha é um ponto de partida para ler patches; isso não recupera uma justificativa que nunca foi registrada.
Teste a explicação nas duas versões
Leia a implementação e os testes juntos na correção encontrada:
git show --format=fuller fix-positive-lines -- line_reader.py test_lines.py
O patch adiciona a verificação do limite inferior e um teste que espera ValueError para 0 e -1. O verificador do laboratório extrai o código e os testes dos commits para pastas temporárias. Ele executa a mesma suíte de regressão contra a implementação inicial e depois os testes próprios de cada commit. Ele não troca seu checkout.
| Observação da execução registrada | O que ela sustenta |
|---|---|
A versão inicial retorna omega para 0 e alpha para -1 |
Essas entradas chegam à indexação negativa do Python |
| A suíte da correção falha na versão inicial para ambas as entradas | As asserções adicionadas detectam o comportamento antigo |
| As versões de correção, renomeação e espaçamento passam cinco testes cada | Esses casos testados se comportam como esperado após a correção |
| A renomeação preserva os bytes do código; o espaçamento preserva a árvore sintática | Essas edições posteriores preservam a implementação desta função |
Os três testes originais da versão inicial também passam. Uma suíte aprovada pode simplesmente deixar de fora a entrada que interessa a você. Os resultados esperados completos descrevem as verificações; seu history-check/transcript.txt contém os comandos e saídas reais, incluindo as duas falhas esperadas.
A mensagem do commit de exemplo descreve a linha zero retornando a última linha; os testes reproduzem esse comportamento. Antes de remover a verificação no seu projeto, veja como os chamadores atuais rejeitam zero e números negativos. O exemplo pressupõe entradas inteiras; outros tipos precisam de validação separada.
Dê à IA uma pergunta verificável
Quando tiver os IDs das revisões, o patch e a saída dos testes, peça ao assistente para relacioná-los:
Explique por que esta verificação de limite inferior existe. Use a correção fornecida e seu commit pai, mostre o resultado das entradas 0 e -1 e identifique as asserções de regressão. Separe o que o diff e os testes executados mostram do que a mensagem do commit afirma. Depois inspecione os chamadores atuais antes de recomendar a remoção. Se esses chamadores ou relatos históricos não estiverem disponíveis, diga qual conclusão continua em aberto.
Uma resposta útil tem três partes: a mudança de comportamento, a motivação relatada e o que continua desconhecido sobre os chamadores atuais. Se ela afirmar que um incidente ocorreu só porque a mensagem diz isso, peça uma afirmação mais restrita. Se propuser apagar a verificação, peça para mostrar como cada caminho de entrada suportado elimina as entradas que falham. Seguir um valor por código desconhecido é um bom próximo passo para investigar os chamadores; revisar um patch escrito por IA mostra como transformar uma explicação proposta em um teste de regressão específico.