Pular para o conteúdo
Tech

Você já ouviu falar ou se deparou com um Heisenbug?

TL;DR Clique aqui pra ver o resumo

Tema: Tech

8 min de leitura 0% lido
Você já ouviu falar ou se deparou com um Heisenbug?
heisenbug

O nome remete ao físico teórico Heisenberg e não ao Walter White mas... eu não resisti em fazer o meme, foi a primeira coisa que me veio à mente.

O termo vem do princípio da incerteza que o físico formulou: no ato de medir a posição de uma partícula, isso acaba inevitavelmente perturbando seu momentum. Não entendeu ainda? Porque eu também não entendi na primeira leitura, mas o que isso quer dizer é o seguinte: no mundo quântico, algumas coisas parecem borradas ou imprevisíveis, e quanto mais se tenta medir, como por exemplo descobrir onde a partícula está, menos se consegue saber sobre outra propriedade. E o mais louco é que quanto mais perto se tenta chegar, mais se perde o controle. (Sorry, física quântica não é meu forte kkkkk.)

A analogia trazida pro mundo dos softwares segue o mesmo princípio e pode te deixar algumas noites sem dormir: o bug só aparece exatamente quando você tenta olhar pra ele.


O que é um Heisenbug

É um bug que muda de comportamento ou às vezes passa a impressão de ter sumido completamente no momento em que você tenta reproduzi-lo. Você vai abrir o debugger, vai seguir o passo a passo e... nada. Vai rodar perfeitamente. Aí você fecha tudo, manda de novo pra produção e... TCHARAM, tá lá o maldito bug dando crash de novo.

O termo "Heisenbug" foi cunhado por Jim Gray em 1985 no paper "Why Do Computers Stop and What Can Be Done About It?". Vou deixar o link do paper original em PDF AQUI.

Gray estava tentando entender falhas em sistemas de grande porte e acabou batizando esse tipo de problema com um nome que ficou bem famoso, inclusive quando fui pesquisar eu vi que este paper ainda é leitura recomendada nas universidades até hoje. Ou seja, desde 1985 nos deparando sempre com os mesmos bugs kkkkk.


Por que eles acontecem

Os Heisenbugs têm causas concretas. O que os torna difíceis de achar não é mistério: o próprio ato de depurar acaba mudando as condições que os fazem existir. Aí a coisa começa a fazer sentido. Algumas das causas mais comuns:

Race conditions duas threads competindo pelo mesmo recurso sem coordenação. A janela onde o bug aparece pode ser de microssegundos. Se você adiciona um breakpoint, a thread pausa, a janela fecha e o bug some.

Variáveis não inicializadas em C e C++, por exemplo, uma variável sem inicialização pode carregar o lixo que estava naquele endereço de memória. Ao depurar, o compilador costuma zerar essa memória por conveniência, mas em release isso não acontece. Resultado: o bug aparece em produção, mas no ambiente de desenvolvimento ele nunca aparece, simplesmente porque esse pequeno detalhe pode alterar tudo !

Esse tipo de bug aparece em mais cenários, mas eu quero focar no exemplo que aconteceu por aqui durante a semana e foi o que me levou a descobrir esse assunto. Antes, um exemplo clássico em JavaScript:

``javascript function processarFila(fila) { for (var i = 0; i < fila.length; i++) { setTimeout(() => { console.log(fila[i].nome) // undefined em produção }, 0) } } ``

O var não tem escopo de bloco. O loop termina antes de qualquer callback executar, e quando eles executam, i já está em fila.length — índice fora do array. O resultado é undefined.

Na depuração, você abre o DevTools. O breakpoint pausa a execução dentro do loop. Naquele instante, i tem o valor certo. O closure captura o valor certo. O bug some.

Trocar var por let resolve esse caso específico porque let tem escopo de bloco e cada iteração cria uma cópia separada de i. Vale notar que mesmo com let, uma variante diferente do bug ainda pode acontecer se o array for modificado entre o início do loop e a execução dos callbacks.


O Heisenbug que encontrei por aqui

No dashboard do meu projeto pessoal, que é o painel admin onde faço todo o controle do blog, me deparei com um exemplo desse bug.

O teste GET /api/invites: membro (não-owner) → 403 passava durante todo o desenvolvimento. O endpoint usava requireOwner(), uma função puramente síncrona que checava o e-mail da sessão contra a variável de ambiente. Sem Prisma, sem banco, sem mock necessário.

``typescript // Antes export async function requireOwner() { const session = await getSession() if (session.user.email !== env.OWNER_EMAIL) return errorResponse('FORBIDDEN', ..., 403) return null } ``

Então veio a implementação do requireOwnerOrCollaborators(): mesmo check de owner, mas com um fallback que consultava o banco para checar se o membro tinha a permissão collaborators. Em produção funcionava. O bug só aparecia no CI.

``typescript // Depois export async function requireOwnerOrCollaborators() { const session = await getSession() if (session.user.email === env.OWNER_EMAIL) return null const user = await prisma.user.findUnique(...) // sem mock no teste if (!parseMemberPermissions(user?.permissions).sections.collaborators) return errorResponse('FORBIDDEN', ..., 403) return null } ``

Localmente, havia um banco real via Docker. O Prisma resolvia a query, o mock não precisava cobrir esse caminho, tudo passava. No CI, o Prisma era mockado via proxy global e prisma.user retornava undefined para chamadas não configuradas explicitamente. Aí vinha o TypeError, não o 403. O teste registrava falha por exceção inesperada, não por asserção.

Funcionava em todo lugar onde alguém olhava. Quebrava onde ninguém estava.


Como caçar estes Heisenbugs

Observe sem perturbar. Prefira logs estruturados e assíncronos no lugar de breakpoints. Ferramentas como Sentry e OpenTelemetry, junto com logs de produção, podem ser mais confiáveis do que o debugger para esse tipo de bug isso justamente porque alteram menos o ambiente.

Reproduza exatamente o mesmo ambiente. Não só o código: use o Docker com os mesmos parâmetros, mesmas variáveis de ambiente, mesma versão do runtime. Muitos desses bugs nascem na diferença entre ambientes.

Cace o não-determinismo. Race conditions, Date.now() ou Math.random() sem seed, ordenação de Maps e Sets, I/O assíncrono sem await e qualquer coisa que introduza uma ordem de execução imprevisível é suspeita. Se você não consegue descrever exatamente a sequência em que as coisas acontecem, o bug pode estar exatamente aí.

Fique esperto com o comportamento do log. Se o bug sumir quando você adicionar um console.log, aproveite essa informação porque ela significa que o bug é sensível a timing ou ordem de execução. Você está perto dele.

Revise seus mocks com desconfiança. Mocks que não cobrem todos os caminhos de código são uma das fontes mais comuns desse tipo de bug. Localmente, o código nunca chega no caminho não mockado. No CI, chega e aí estoura um TypeError do nada. Então já sabe: se o teste passa local mas falha no CI, o primeiro suspeito é o mock.


A taxonomia de Gray : Mais sobre seu paper :

No paper de 1985, Gray cunhou o termo Heisenbug e classificou os bugs em dois tipos principais. Com o tempo, a comunidade de desenvolvimento expandiu essa taxonomia com mais dois nomes que ficaram populares. Os quatro juntos ficaram assim:

Bohrbug : determinístico. Sempre falha nas mesmas condições, sempre do mesmo jeito. Fácil de reproduzir, fácil de corrigir. O nome homenageia o modelo atômico de Bohr: simples, previsível, bem-comportado.

Heisenbug : muda ao ser observado. Race conditions, timing, otimizações de compilador, efeitos colaterais de instrumentação.

Mandelbug : (cunhado pela comunidade, posterior ao paper) a causa raiz fica num sistema tão complexo e interdependente que o comportamento parece caótico. O nome vem dos fractais de Mandelbrot: quanto mais você aproxima, mais estrutura aparece, mas nunca simplifica. Sistemas de SO, drivers, interações entre subsistemas legados.

Schrödingerbug (cunhado pela comunidade, posterior ao paper) existe no código mas nunca foi ativado. Como o gato de Schrödinger, está simultaneamente vivo e morto enquanto ninguém abre a caixa. Quando alguém finalmente passa pelo caminho de código pela primeira vez, o bug colapsa para um estado determinístico e geralmente leva algo junto.


Eu quis apresentar esse tema porque foi algo novo que tomou meu tempo esta semana. Conforme fui pesquisando e investigando vi que davam esse nome pra esse tipo de problema e a curiosidade fez o resto. Este post acabou sendo um resumo apenas do assunto, existem mais cenários onde podemos nos deparar com esse problema e mais formas de encontrá-los.

A ideia aqui é fomentar a curiosidade. Deixei o link do paper aqui mesmo dentro do post é só clicar para ser redirecionado para o PDF do artigo.

É um tema que ainda estou pesquisando e me aprofundando, aprendi algo que possivelmente nenhuma documentação teria me ensinado da mesma forma, deu trabalho? Pra um %$aralho, mas esse eu consegui resolver.

Se você encontrou qualquer coisa aqui que te faça comentar, que seja uma correção ou uma dica ou simplesmente porque gostou do assunto , ta ai fique à vontade.

Estou sempre à procura de aprender e melhorar, e o seu feedback é bem-vindo. See you, Space Dev!

Fontes: gepeto pra imagens e google

>_ comentários 0
0 / 1000

> comentários são moderados antes de serem publicados.

> carregando comentários...

Admin