Debugging: regras indispensáveis para resolver até os problemas mais difíceis de encontrar (2004)
(dwheeler.com)- Debugging, de David J. Agans, aborda os fundamentos da depuração para encontrar a causa e corrigir um bug depois que ele é descoberto, oferecendo princípios aos quais vale a pena recorrer repetidamente tanto para desenvolvedores iniciantes e intermediários quanto para os mais experientes
- O livro é composto por 9 regras que conectam entendimento do sistema, reprodução da falha, observação, divisão e conquista, controle de mudanças, trilha de auditoria, revisão de premissas, perspectiva externa e validação da correção a casos práticos
- Embora apareçam tecnologias antigas e exemplos fora da computação, o ponto central não é uma ferramenta específica, mas sim uma forma de pensar para restringir o problema, aplicável à depuração de hardware e software em geral
- Para bugs difíceis, como problemas intermitentes, o conselho de Make it Fail é especialmente útil, embora seja uma pena que o termo Heisenbug não seja tratado diretamente
- Ao contrário de livros que explicam como usar o GDB ou escrever testes, o foco está no panorama geral da depuração, então o uso de ferramentas e testes de regressão precisa ser complementado com outros materiais
O problema que o livro mira
- Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems, de David J. Agans, trata do processo de encontrar a causa e realmente corrigir um bug depois que ele é detectado
- Em vez de se concentrar em uma tecnologia ou ferramenta específica, organiza os princípios de depuração necessários para desenvolvedores de software e hardware de computador
- É especialmente adequado para desenvolvedores iniciantes e intermediários, e também ajuda os mais experientes a recuperar fundamentos que podem ser esquecidos em situações urgentes
- Um de seus pontos fortes é condensar, em princípios e casos, conhecimentos de depuração que normalmente são aprendidos pela experiência
As 9 regras de depuração
-
Entenda o sistema
- Leia o manual, compreenda a estrutura geral e entenda os princípios básicos e o funcionamento detalhado
- Verifique também o que as ferramentas usadas mostram e o que elas escondem
-
Faça falhar
- Execute o problema novamente e volte ao início para estimular diretamente as condições de falha
- Em vez de imitar a falha, provoque a falha real e encontre as condições não controladas que produzem bugs intermitentes
- Registre tudo, não confie demais em estatísticas e aceite que eventos raros podem de fato acontecer
- Não descarte as ferramentas de depuração; use-as para expor o problema
-
Pare de pensar e olhe
- Antes de começar reparos complexos com base em suposições, obtenha primeiro dados observáveis
- Examine a falha e os detalhes, crie instrumentação interna ou adicione instrumentação externa
- Não evite investigar a fundo, mas tenha cuidado com o efeito Heisenberg, em que a própria observação pode alterar o comportamento
- Use hipóteses apenas como ferramenta para restringir a área de busca, não como conclusão
-
Divida e conquiste
- Reduza a área de investigação por aproximação sucessiva (
successive approximation) e determine de que lado o bug está - Use padrões de teste visíveis com clareza e parta de um estado ruim para restringir a causa
- Remova primeiro bugs já conhecidos e ruído para simplificar o que está sendo investigado
- Reduza a área de investigação por aproximação sucessiva (
-
Mude apenas uma coisa por vez
- Isole os fatores centrais e entenda o que está errado antes de corrigir
- Altere os testes também um de cada vez e compare com os casos normais
- Por fim, verifique o que mudou desde a última vez em que tudo funcionou
-
Mantenha uma trilha de auditoria
- Registre em uma trilha de auditoria o que foi feito, em que ordem e quais foram os resultados
- Como até detalhes que parecem triviais podem ser a causa, registre os eventos conectando-os entre si
- A trilha de auditoria do processo de projeto também ajuda nos testes e deve ser documentada
-
Verifique o plugue
- Desconfie das premissas tidas como óbvias e confira tudo novamente desde o começo
- Inclua também nas verificações as próprias ferramentas usadas para encontrar o problema
-
Obtenha uma nova perspectiva
- Quando travar sozinho, busque novos insights por meio de outra pessoa ou de uma forma diferente de explicar o problema
- Só de explicar o problema para um manequim, seus pensamentos podem se organizar
- Aproveite a especialização, ouça quem tem experiência e priorize compartilhar sintomas e pedir ajuda em vez do orgulho
-
Se você não corrigiu, então não está corrigido
- Depois da correção, confirme se o problema realmente foi resolvido e verifique se a sua mudança eliminou a causa real
- Problemas não desaparecem sozinhos; é preciso corrigir tanto a causa quanto o processo
Como os casos dão vida aos princípios
- A lista de regras pode parecer seca à primeira vista, mas as explicações detalhadas e os relatos de caso trazem os princípios para situações reais
- Muitos casos entram em detalhes técnicos, o que pode ser pesado para alguns leitores
- Há exemplos com tecnologias antigas, mas isso não é um grande problema porque o essencial são os princípios, não a tecnologia específica
- Nem todos os casos tratam de computação; há também um exemplo divertido relacionado à fiação de uma casa
- Quem não tiver nenhuma familiaridade com hardware e software de computador pode ter dificuldade para acompanhar muitos dos exemplos
- Depois da explicação das regras, vêm histórias em que várias regras são aplicadas em conjunto, exercícios simples para o leitor, dicas de help desk e comentários de encerramento
Pontos de destaque e limitações
- O princípio “Pare de pensar e olhe” é especialmente importante
- Porque muita gente tenta corrigir problemas na base do palpite antes de reunir dados que confirmem ou refutem a hipótese
- “Se você não corrigiu, então não está corrigido” também é uma regra que marca bastante
- É preciso verificar não apenas se foi corrigido, mas também qual era a causa e por que a correção funcionou
- A discussão sobre “estimular a falha, não imitá-la” não é tão clara quanto outras partes do livro, mas ainda assim vale a pena entendê-la
- Problemas intermitentes costumam ser os mais difíceis de lidar, e o livro oferece conselhos diretos sobre isso em Make it Fail
- Heisenberg é mencionado, mas o termo comum no desenvolvimento de software, Heisenbug, não é tratado
- Heisenbug significa um bug que desaparece ou muda de comportamento quando tentamos observá-lo ou isolá-lo
Diferença em relação a outros materiais
- O livro se diferencia de manuais de ferramentas ou livros de testes por colocar no centro os princípios básicos da depuração
- Debugging with GDB: The GNU Source-Level Debugger, de Richard Stallman e outros, explica principalmente técnicas ou comandos de ferramentas específicas
- Há também materiais com conselhos gerais, como Guide to Faster, Less Frustrating Debugging, de Norman Matloff, mas eles não têm um escopo tão amplo quanto o livro de Agans
- Livros de teste como Software Testing Techniques, de Boris Beizer, focam em escrever testes para encontrar bugs, dedicando relativamente menos espaço a como corrigir os bugs já descobertos
- Depois de encontrar um bug, é preciso adicionar um teste correspondente à suíte de testes de regressão, mas testes e regressão estão fora do escopo deste livro
Materiais complementares e pontos fracos
- O site complementar do livro, debuggingrules.com, traz links para informações relacionadas e um pôster com as 9 regras para baixar e imprimir
- É uma pena que a lista completa de sub-regras, importante para entender as regras, não esteja reunida em uma única página no livro ou no site
- Teria sido ainda mais útil se houvesse conselhos e exemplos mais específicos sobre ferramentas comuns e tipos de problema, como depuradores simbólicos, sondas de lógica digital e ddd sobre gdb
- Também parece haver espaço para um livro separado que expanda as mesmas regras para resolução de problemas gerais fora da computação, embora os exemplos deste livro sejam técnicos demais para leitores não ligados à área
- Os princípios básicos podem parecer óbvios à primeira vista, mas iniciantes precisam aprendê-los e profissionais experientes precisam relembrá-los repetidamente, e este livro é adequado tanto para esse aprendizado quanto para essa revisão
1 comentários
Opiniões do Hacker News
Acho que a tentação de acrescentar uma “correção” ao código quebrado que já existe, tentando fazê-lo funcionar, é a mais nociva
Código quebrado é difícil de consertar porque há pontos demais que podem ser alterados, e é muito mais fácil quebrar um código que funciona
Quando uma sequência inteira de luzes de Natal não acende, trocar as lâmpadas uma por uma falha se houver várias lâmpadas queimadas
Em vez disso, é preciso começar por um exemplo mínimo funcional e ir acrescentando aos poucos até encontrar o ponto em que o erro aparece; na prática, muitas vezes recomeçar do zero economiza tempo
Em um problema difícil de capturar em produção, alguns membros da equipe reescreveram a rotina problemática, e a versão reescrita foi implantada antes que o restante terminasse a depuração
Pelo menos uma vez, nunca chegamos a encontrar o bug original porque não dava para gastar tempo indefinidamente em um problema “corrigido”
A regra número 0 é não entrar em pânico
Prazos e clientes irritados atrapalham o pensamento claro; por isso, um bom gestor confiável precisa proteger os engenheiros dessa pressão para que eles possam se concentrar em resolver o problema
Um bom gerente nos moveu para outra chamada e disse: “ignore o que eles estão dizendo e concentre-se; eu cuido disso”, e depois disso meu respeito por esse gerente aumentou muito
Se não há tempo para fazer direito, por que você acha que haverá tempo para fazer duas vezes?
Ou seja, bloquear as coisas que caem de cima para que os engenheiros possam se concentrar no trabalho principal
É muito melhor poder voltar para uma versão funcional e depurar sem a pressão de uma crise
Para a regra 4, “dividir para conquistar”,
git bisectajuda muitoSe há um commit bom e, depois dele, entre dezenas ou centenas de commits, um commit ruim, em poucos passos dá para estreitar até o commit ou o código problemático
Um exemplo de uso está em https://nickjanetakis.com/blog/using-git-bisect-to-help-find...
Usei esse método durante uma consultoria ao vivo em uma base de código grande e desconhecida para reduzir rapidamente o escopo; sem isso, a área potencialmente quebrada seria ampla demais
git bisect, mantenho a disciplina de que todo commit que entra no branch “real” deve compilar individualmente, passar nos testes conhecidos na época e, até onde se sabe, ser implantávelConsidero isso mais importante do que preservar cada tecla digitada ou manter até o último commit “Fixes.”. Esses princípios tornam a busca binária inútil
Não uso com frequência, mas, quando acerta em cheio mesmo uma única vez nos bugs maiores e mais misteriosos, entrega de uma vez pistas que valeriam dias, então compensa bastante
git bisectÉ a abordagem de comparar um sistema quebrado com um sistema funcional e eliminar sistematicamente as diferenças para encontrar o defeito
Ela se aplica além de software ou hardware, e foi ótimo, no passado, quando havia dois jet skis iguais: dava para consertar um comparando com o outro
git bisectem siEle pode ser usado não só em intervalos de commits, mas também para dividir o espaço do sistema
Por exemplo, se um workflow de 10 etapas quebrou, você pode verificar se a etapa 5 ainda está normal, ou estreitar se é ou não um problema de hardware
Isso é especialmente importante quando a causa do problema talvez não seja um commit de código no repositório que você está bisectando no momento
bisecté excelente, mas é preciso distinguir entre as “regras”, que são a filosofia e a forma de pensar do livro, e as “ferramentas”, que são conselhos práticosQuem começa por “qual ferramenta devo usar?” está em desvantagem em relação a quem começa por “isso não funcionava antes?”
O mundo está cheio de ferramentas, e você enlouquece se tentar guardar todas na cabeça; é melhor adotar a filosofia primeiro
git bisect run. É uma pequena ferramenta realmente incrívelhttps://andrewrepp.com/git_bisect_run
É preciso verificar se você está editando o arquivo certo na máquina certa
Hoje em dia, sempre adiciono temporariamente uma linha que causa um erro fatal, para confirmar que estou no arquivo certo e, dependendo da situação, também na linha certa
É para confirmar que a minha alteração realmente tem efeito
Há regras adicionais
“A culpa é toda minha”: pode até ser um bug do compilador ou uma falha de hardware, mas isso é muito raro; antes de qualquer coisa, deve-se suspeitar das minhas alterações no código
“Quando encontrar um bug, procure também a família e os amigos dele”: é preciso pensar e verificar onde mais o mesmo tipo de coisa pode ter acontecido
“Otimize primeiro para o usuário, em segundo lugar para o programador de manutenção, e por último para o computador”
Há um resumo em https://blog.codinghorror.com/the-first-rule-of-programming-...
Em geral, no processo de reduzir o código que gera o erro para um caso mais simples, você acaba encontrando o bug na sua própria lógica
Uma ou duas vezes, de fato sobrou algo que valia relatar aos desenvolvedores, e geralmente eram bibliotecas com poucas baterias de teste por terem no máximo algumas centenas de usuários
No fim, foi um grande alívio descobrir que não era um erro de código aparentemente impossível, e sim um problema de CPU
Ainda assim, o melhor é fazer mais algumas rodadas de busca binária na cadeia de causa e efeito para ter certeza
O conselho “entenda o sistema: leia o manual, leia tudo em profundidade, conheça o básico, conheça o roadmap, entenda as ferramentas e procure os detalhes” soa um tanto estranho
Parece dizer que, se houver um bug no código, primeiro você deve ler o manual inteiro de 700 páginas da biblioteca que está usando, ler 7 livros relacionados e só olhar para o bug um ou dois meses depois
Fico curioso se existe sequer um programador que siga esse conselho na prática
Atwood e Spolsky criaram o Stack Overflow em 2008, e era uma época em que as pessoas conheciam livros por nomes como “Camel book” e simplesmente tinham conhecimento
0. https://stackoverflow.blog/2021/12/14/podcast-400-an-oral-hi...
“Leia tudo em profundidade” não quer necessariamente dizer “leia primeiro o manual inteiro de 700 páginas da biblioteca que você está usando”
Se houver um problema com
git bisect, em vez de juntar alguns trechos do Stack Overflow, você pode entender o assunto um pouco mais a fundo em https://git-scm.com/docs/git-bisectO erro é pensar que o resultado de meses de trabalho seria apenas corrigir de qualquer jeito um único bug
O objetivo é corrigir de verdade o máximo possível dessa classe de bugs e, antes de tudo, deixar de escrevê-los
A alternativa é cair de paraquedas em um sistema desconhecido, mexer em coisas sem entendê-las até os testes ficarem verdes, abrir um PR e torcer para não ter piorado mais nada; fazer isso rotineiramente é quase um pesadelo
Além disso, se o manual da biblioteca que você usa tem 700 páginas, há uma boa chance de você estar usando a biblioteca errada
Como 10ª etapa, não deveríamos adicionar o bug aos testes de CI para evitar regressões?
É preciso verificar que, antes da correção, o CI falha, e que depois da correção ele passa
Especialmente porque havia commits com mais de 5 anos e, por ser um componente/biblioteca, existiam vários hacks estranhos para IE
Alguns testes demoram ou são complexos de escrever e também exigem manutenção, e é preciso aceitar que uma suíte de testes não verifica todos os casos de borda
Isso pode significar que um bug que chegou à produção pode voltar, mas, se foi um erro simples, talvez ele não tenha mais probabilidade de reaparecer do que centenas de outros possíveis erros
No fim, depende da situação, e escrever testes não é de graça
Já vi inúmeras vezes a causa profunda ser reativada e o mesmo problema voltar, ou ninguém saber que eu havia corrigido algo e todos continuarem usando workarounds como se o bug ainda existisse
Mesmo depois de chegar ao “corrigido!”, deixar um registro simples e uma análise de causa raiz ajuda outras pessoas
Depois que os testes se acumulam por muito tempo, quão rápido o CI consegue rodar, e ainda faz sentido mantê-los indefinidamente?
Se você quiser incutir esse modo de pensar nas crianças, em si mesmo ou em outras pessoas, recomendo pelo menos o seguinte
The Martian by Andy Weir https://en.wikipedia.org/wiki/The_Martian_(Weir_novel)
https://en.wikipedia.org/wiki/Zen_and_the_Art_of_Motorcycle_...
https://en.wikipedia.org/wiki/The_Three-Body_Problem_(novel)
To Engineer Is Human - The Role of Failure in Successful Design By Henry Petroski
https://pressbooks.bccampus.ca/engineeringinsociety/front-ma...
https://en.wikipedia.org/wiki/Surely_You%27re_Joking,_Mr._Fe...!
Gosto especialmente do conceito de “gumption traps”
Se você caiu na armadilha da rigidez de valores, inevitavelmente vai ficar mais lento; então é preciso reduzir o ritmo de propósito, revisitar por onde já passou e verificar se as coisas que você considerava importantes eram realmente importantes
A passagem que diz que não há nada de errado em simplesmente olhar para a máquina por um tempo e que, se você observar como quem observa uma linha de pesca, chegará um momento em que um pequeno fato perguntará cautelosamente se você está interessado, é quase uma orientação para a vida
Acho que os acontecimentos simplesmente ocorrem por grandes saltos lógicos, e na prática é mais uma fantasia coberta por um trocadilho científico bem raso
Escrevi algo parecido alguns anos atrás. Na época, eu não tinha lido o livro original mencionado aqui
https://explog.in/notes/debugging.html
O zine de depuração da Julia Evans também é muito bom: https://wizardzines.com/zines/debugging-guide/
Mesmo depois de depurar com sucesso, o trabalho não acabou
O ponto central de “Three Questions About Each Bug You Find” <http://www.multicians.org/thvv/threeq.html> são três perguntas
Este erro existe em outros lugares? Qual é o próximo bug escondido por trás deste? O que deve ser feito para evitar bugs desse tipo?