14 pontos por GN⁺ 2025-11-04 | 2 comentários | Compartilhar no WhatsApp
  • O conceito de busca binária (binary search) é usado não só em perguntas de entrevista, mas também em uma ferramenta real de desenvolvimento, o Git
  • Em um ambiente de monorepo de grande porte, quando testes passam a falhar de repente, pode surgir uma situação em que é difícil rastrear a causa apenas pelos logs
  • Um colega definiu um commit bom e um commit ruim e fez uma busca automática com git bisect, encontrando com precisão o commit problemático em que o bug começou
  • Em cada etapa, um script é executado para classificar automaticamente o commit de acordo com o resultado do teste, identificando o primeiro commit que falhou
  • Ao aplicar o princípio da busca binária, git bisect é uma ferramenta poderosa para rastrear rapidamente a causa de bugs em codebases grandes

Algoritmo e caso real

  • O algoritmo de busca binária (binary search) vai além de uma simples pergunta de entrevista e funciona como um princípio central também em ferramentas reais de depuração
  • O git bisect pode ser usado como uma ferramenta que utiliza busca binária para encontrar o “primeiro commit ruim (first bad commit)”
    • Ele funciona com um princípio semelhante ao problema “First Bad Version” do Leetcode

Situação do problema em um ambiente de trabalho real

  • Em um ambiente que usa um monorepo de grande porte, centenas ou milhares de commits podem acontecer em um único dia
  • É difícil rastrear a causa de uma falha de teste apenas pelos logs
  • A causa da falha foi uma mudança de string em um arquivo de configuração usado para obter o token necessário para uma chamada remota, fazendo referência a outra conta e levando o teste a falhar
  • Essa mudança passou nos testes de integração, mas na prática causava problemas, e era difícil descobrir em que ponto, entre tantos commits, isso havia acontecido

Resolvendo o problema com git bisect

  • Um colega de outra equipe identificou rapidamente o commit problemático usando o comando git bisect
    • Depois de definir um commit bom (good) e um commit ruim (bad), a ferramenta faz checkout automático dos commits intermediários, executa os testes e vai afunilando a causa
    • Cada execução de teste levava tempo, mas no fim foi possível encontrar exatamente o commit que introduziu o problema
    • Ao reverter esse commit, todos os testes voltaram a funcionar normalmente

Processo de execução do git bisect

  • Exemplo de histórico de commits
    • Commit 1: commit inicial (normal)
    • Commit 2: refatoração (normal)
    • Commit 3: introdução do bug (erro ocorre)
    • Commit 4~10: mudanças não funcionais (erro permanece)
  • Exemplo de comandos executados
    git bisect start  
    git bisect bad HEAD  
    git bisect good HEAD~9  
    git bisect run ./test_script.sh  
    
  • O script de teste (test_script.sh) retorna 0 em caso de sucesso e um código diferente de zero em caso de falha
  • O git bisect faz checkout automático dos commits intermediários e executa o script de teste,
    identificando o primeiro commit ruim com base no ponto em que o teste começa a falhar
  • Na saída, o commit b982ed9373fe235fe61c74b15faf264bc7142398 é confirmado como o primeiro commit com bug

Conclusão

  • O git bisect é uma ferramenta prática que aplica o princípio da busca binária à navegação pelo histórico do código
  • Mesmo em repositórios grandes ou históricos de mudanças complexos, ele permite rastrear rapidamente o ponto em que o bug foi introduzido
  • Combinado com automação de testes, ele viabiliza uma depuração estável mesmo em codebases grandes

2 comentários

 
kandk 2025-11-04

É por causa de problemas assim que usamos TBD (trunk-based development).

 
GN⁺ 2025-11-04
Comentários do Hacker News
  • Quando trabalhei em um codebase enorme, sem cobertura de testes e com abstrações péssimas, git bisect era praticamente a única ferramenta realmente útil
    O código era tão complexo que era impossível rastrear bugs de forma lógica, então era muito mais fácil descobrir em qual commit o problema apareceu
    Mas, em codebases de alta qualidade, eu não precisava tanto de bisect. Dava para testar cada componente de forma independente, e a observabilidade também era boa, então ficava claro onde olhar

    • Não acho que git bisect seja algo dispensável. Ele não serve só para encontrar o bug, mas também para entender por que aquele bug surgiu
      Se o projeto tem mensagens de commit bem escritas, o bisect permite recuperar o contexto dos commits antigos e refletir isso no commit que corrige o bug. Esse ciclo fortalece a própria cultura de commits
    • Já encontrei um bug com uma string estranha em um programa OSS. Era código em C, e a causa era uma variável não inicializada
      Era impossível rastrear isso manualmente, mas escrevi um script de bisect, deixei rodando por uns 30 minutos e ele encontrou exatamente o commit problemático
    • O git bisect foi introduzido originalmente para encontrar regressões do kernel Linux
      Mesmo em casos impossíveis de testar, como drivers de hardware, usuários comuns passaram a conseguir fazer bisect no kernel por conta própria e identificar o commit com problema
      Antes era preciso pedir ajuda aos desenvolvedores por e-mail, mas agora o próprio usuário consegue ir afunilando o problema
    • Se o objetivo é só corrigir o bug, talvez o bisect não seja necessário. Mas às vezes é importante saber desde quando o bug existe
      Por exemplo, para rastrear o alcance de dados processados incorretamente, ou para decidir se “isso é bug ou funcionalidade”
    • Às vezes também é preciso saber quando o bug foi corrigido
      Por exemplo, quando um cliente está enfrentando o problema em uma versão de 6 anos atrás, dá para verificar se atualizar para uma versão de 4 anos atrás já resolve
      Ou, quando o código passou por um grande refactor, também dá para entender se a correção foi intencional ou aconteceu por acaso
  • git bisect é excelente quando funciona bem, mas não consegue encontrar todo tipo de bug
    Alguns bugs não apresentam sintomas no momento em que são introduzidos, e só aparecem depois por causa de outra mudança
    Nesses casos, a premissa do bisect — de que o bug aparece apenas uma vez entre um commit bom e um ruim — deixa de valer
    Commits impossíveis de testar podem ser marcados com skip, mas, se esse for o commit problemático, o resultado fica ambíguo

  • Recentemente usei git bisect pela primeira vez de forma séria, e foi quase mágico
    Havia duas funções com o mesmo nome, e durante um trabalho de formatação de código o import da função correta foi removido, causando o problema
    Revisei o código várias vezes, mas não fazia ideia da causa até identificar o commit problemático com bisect

  • Normalmente eu já sei em que arquivo ou função o bug está, então não uso bisect com tanta frequência
    Em vez disso, acompanho o histórico de mudanças de uma função específica com o comando git log -L :func_name:path/to/file.c
    É preciso configurar o .gitattributes

    • Teve quem perguntasse sobre essa configuração do .gitattributes. A reação foi de curiosidade sobre o que exatamente é necessário
    • Também teve gente dizendo que usa bisect todo dia. O fluxo de trabalho é completamente diferente
    • Em casos como C++, com funções polimórficas, o git log -L é fraco. É difícil rastrear uma versão específica entre funções sobrecarregadas com o mesmo nome
    • Sem .gitattributes, outra opção é usar git log -S para encontrar commits que contenham uma determinada string
  • Vale a pena conhecer o exit code 125 em scripts de teste
    Em casos em que não dá para determinar o resultado do teste, como falha de build, retornar 125 faz o bisect pular aquele commit
    Organizei isso em um post no meu blog

    • Em repositórios em que commits de merge representam pontos aprovados no CI, usar git bisect --first-parent pode ser útil
      Assim fica rápido descobrir “qual PR introduziu o bug”, e depois basta rodar um bisect mais detalhado naquela branch
  • O bisect brilha quando aparece um teste flaky
    Quando, por causa de uma race condition, é preciso rodar o teste centenas de milhares de vezes para ter confiança, deixar um script de bisect rodando em background torna a solução viável na prática

    • Nesse caso, parece que aplicar busca binária bayesiana poderia reduzir bastante o número de execuções de teste
  • Recentemente encontrei a causa de um bug com bisect em um projeto de player de música feito com Svelte (lets-make-sweet-music.com)
    Não havia testes nem logs de erro, e as atualizações do dependabot tinham aumentado o número de commits, dificultando o rastreamento
    Graças ao bisect, encontrei o commit problemático, e a causa era que o arquivo que substituí não implementava a funcionalidade de múltiplos bindings de eventos
    Manter os commits pequenos ajuda a afunilar rapidamente a causa do problema encontrado com bisect

  • Alguém disse que “ensinar busca binária em entrevista é forçado”, e o git bisect é um ótimo exemplo prático desse conceito
    Mas não é necessário implementá-la manualmente. Na maioria das linguagens, isso já existe na biblioteca padrão

    • Curiosamente, há uma história de que a busca binária foi proposta pela primeira vez nos anos 1940, mas uma implementação sem bugs só apareceu nos anos 1960
      Ao calcular o índice do meio com (low + high) / 2, pode haver overflow
    • Pessoalmente, acho bom que todo desenvolvedor implemente busca binária pelo menos uma vez em uma linguagem com inteiros de precisão arbitrária (como Python)
      É um dos melhores exercícios para treinar o raciocínio baseado em invariantes
  • Além do bisect, o Git também tem ótimas ferramentas de exploração de código, como log -L, log -S e blame
    Já escrevi um post no blog sobre esse tema