- 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
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
É por causa de problemas assim que usamos TBD (trunk-based development).
Comentários do Hacker News
Quando trabalhei em um codebase enorme, sem cobertura de testes e com abstrações péssimas,
git bisectera praticamente a única ferramenta realmente útilO 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
git bisectseja algo dispensável. Ele não serve só para encontrar o bug, mas também para entender por que aquele bug surgiuSe 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
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
git bisectfoi introduzido originalmente para encontrar regressões do kernel LinuxMesmo 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
Por exemplo, para rastrear o alcance de dados processados incorretamente, ou para decidir se “isso é bug ou funcionalidade”
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 bugAlguns 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íguoRecentemente usei
git bisectpela primeira vez de forma séria, e foi quase mágicoHavia 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.gitattributes. A reação foi de curiosidade sobre o que exatamente é necessáriogit log -Lé fraco. É difícil rastrear uma versão específica entre funções sobrecarregadas com o mesmo nome.gitattributes, outra opção é usargit log -Spara encontrar commits que contenham uma determinada stringVale 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
git bisect --first-parentpode ser útilAssim 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
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
dependabottinham aumentado o número de commits, dificultando o rastreamentoGraç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 conceitoMas não é necessário implementá-la manualmente. Na maioria das linguagens, isso já existe na biblioteca padrão
Ao calcular o índice do meio com
(low + high) / 2, pode haver overflowÉ 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 -SeblameJá escrevi um post no blog sobre esse tema