O que é um schrödinbug?


52

Esta página wiki diz:

Um schrödinbug é um bug que se manifesta somente depois que alguém lê o código-fonte ou usa o programa de maneira incomum percebe que ele nunca deveria ter funcionado em primeiro lugar; nesse momento, o programa para de funcionar imediatamente para todos até que seja corrigido. O arquivo do jargão acrescenta: "Embora ... isso pareça impossível, acontece; alguns programas abrigam schrödinbugs latentes há anos".

O que está sendo falado é muito vago.

Alguém pode fornecer um exemplo de como é um schrödinbug (como em uma situação ficcional / da vida real)?


15
Observe que a citação é contada de brincadeira.

11
Eu acho que você entenderia melhor shrodinbug se você sabia sobre o gato de Shrodinger: en.wikipedia.org/wiki/Shrodingers_cat
Eimantas

11
@Eimantas Na verdade, estou agora mais confuso, mas que é um artigo interessante :)

Respostas:


82

Na minha experiência, o padrão é este:

  • O sistema funciona, geralmente por anos
  • Um erro é relatado
  • O desenvolvedor investiga o erro e encontra um pouco de código que parece estar completamente defeituoso e declara que "nunca poderia ter funcionado"
  • O bug é corrigido e a legenda do código que nunca poderia ter funcionado (mas funcionou por anos) cresce

Sejamos lógicos aqui. Código que nunca poderia ter funcionado ... nunca poderia ter funcionado . Se ele fez o trabalho, em seguida, a declaração é falsa.

Então, eu vou dizer que um bug exatamente como descrito (que é observar o código defeituoso para de funcionar) é claramente absurdo.

Na realidade, o que aconteceu é uma das duas coisas:

1) O desenvolvedor não entendeu completamente o código . Nesse caso, o código geralmente é uma bagunça e, em algum lugar, possui uma sensibilidade maior, mas não óbvia, a alguma condição externa (digamos, uma versão ou configuração específica do SO que governa como algumas funções funcionam de alguma maneira menor, mas significativa). Essa condição externa é alterada (digamos, por uma atualização ou alteração do servidor que se acredita não relacionada) e, ao fazer isso, causa a quebra do código.

O desenvolvedor então analisa o código e, não entendendo o contexto histórico ou tendo tempo para rastrear todas as dependências e cenários possíveis, declarou que nunca poderia ter funcionado e reescrito.

Nessa situação, o que se deve entender aqui é que a ideia de que "nunca poderia ter funcionado" é comprovadamente falsa (porque funcionou).

Isso não quer dizer que reescrever é uma coisa ruim - muitas vezes não é, embora seja bom saber exatamente o que estava errado muitas vezes demorado e reescrever a seção de código geralmente é mais rápida e permite que você tenha certeza de que corrigiu as coisas.

2) Na verdade, nunca funcionou, apenas ninguém jamais notou . Isso é surpreendentemente comum, principalmente em grandes sistemas. Nesse caso, alguém começa e começa a olhar as coisas da maneira que ninguém fazia antes, ou um processo de negócios muda, trazendo um caso de vantagem anteriormente menor para o processo principal e algo que nunca funcionou (ou funcionou em alguns, mas não em todos) o horário) é encontrado e relatado.

O desenvolvedor analisa e declara "nunca poderia ter funcionado", mas os usuários dizem "absurdo, estamos usando há anos" e eles estão certos, mas algo que consideram irrelevantes (e geralmente não mencionam até o O desenvolvedor encontra a condição exata em que diz "ah, sim, fazemos isso agora e não antes") mudou.

Aqui o desenvolvedor está certo - ele nunca poderia ter funcionado e nunca funcionou.

Mas, em ambos os casos, uma das duas coisas é verdadeira:

  • A afirmação "nunca poderia ter funcionado" é verdadeira e nunca funcionou - as pessoas pensaram que funcionou.
  • Funcionou e a afirmação "nunca poderia ter funcionado" é falsa e resulta de uma (geralmente razoável) falta de compreensão do código e de suas dependências

11
Acontece comigo com tanta frequência
genesis

2
Grande insight sobre o realismo dessas situações #
226 StuperUser

11
Eu acho que geralmente é o resultado de um momento "WTF". Eu tive isso uma vez. Reli alguns códigos que escrevi e percebi que um bug que foi notado recentemente deveria ter quebrado todo o aplicativo. Na verdade, após uma inspeção mais aprofundada, outro componente que escrevi foi tão bom que compensou os erros.
Thaddee Tyl

11
@ Thaddee - Eu já vi isso antes, mas também vi dois bugs nos módulos de código que se chamavam cancelando um ao outro para que realmente funcionasse. Olhe para qualquer um e eles estavam quebrados, mas juntos eles estavam bem.
21711 Jon Hopkins

7
@ Jon Hopkins: Eu também tive um caso de 2 erros cancelando um ao outro, e isso é realmente surpreendente. Encontrei um bug, pronunciei a infame afirmação de que "nunca poderia ter funcionado", procurei mais profundamente para descobrir por que funcionava de qualquer maneira e encontrei outro bug que meio que corrigia o primeiro, na maioria dos casos, pelo menos. Fiquei realmente surpreso com a descoberta e pelo fato de que, com apenas um dos bugs, a consequência teria sido catastrófica!
Alexis Dufrenoy 23/11

54

Como todo mundo menciona código que nunca deveria ter funcionado, darei um exemplo que encontrei, cerca de 8 anos atrás, em um projeto VB3 moribundo que estava sendo convertido em .net. Infelizmente, o projeto teve que ser atualizado até a versão .net estar concluída - e eu era o único lá que entendia remotamente o VB3.

Havia uma função muito importante chamada centenas de vezes para cada cálculo - calculava juros mensais para planos de pensão de longo prazo. Vou reproduzir as partes interessantes.

Function CalculateMonthlyInterest([...], IsYearlyInterestMode As Boolean, [...]) As Double
    [about 30 lines of code]
    If IsYearlyInterestMode Then
        [about 30 lines of code]
        If Not IsYearlyInterestMode Then
            [about 30 lines of code (*)]
        End If
    End If
End Function

A parte marcada com uma estrela tinha o código mais importante; foi a única parte que fez o cálculo real. Claramente, isso nunca deveria ter funcionado, certo?

Foi preciso muita depuração, mas eu finalmente encontrei a causa: IsYearlyInterestModewas Truee Not IsYearlyInterestModetambém era verdadeira. Isso ocorre porque, em algum ponto da linha, alguém o converte em um número inteiro, então, em uma função que deveria configurá-lo como verdadeiro, o incrementava (se fosse 0 False, seria definido como 1, que é VB True, para que eu possa ver a lógica lá) e, em seguida, converta-o para um booleano. E fiquei com uma condição que nunca pode acontecer e ainda acontece o tempo todo.


7
Epílogo: nunca consertei essa função; Acabei de corrigir o site com falha na chamada para enviar 2 como todos os outros.
Configurador

quer dizer que ele é usado quando as pessoas interpretam mal o código?
Pacerier 29/07

11
@ Pacerier: Mais frequentemente quando o código está tão bagunçado que só funciona corretamente por acidente. No meu exemplo, nenhum desenvolvedor pretendeu IsYearlyInterestModeavaliar como verdadeiro e não verdadeiro; o desenvolvedor original que acrescentou algumas linhas (incluindo um dos ifs não realmente compreender como ele funciona - ele só passou a trabalhar para que ele era bom o suficiente.
configurador

16

Não conheça um exemplo do mundo real, mas para simplificá-lo com uma situação de exemplo:

  • Um bug não é percebido por um tempo, porque o aplicativo não executa o código sob condições que causam falha.
  • Alguém percebe isso fazendo algo fora do uso normal (ou inspecionando a fonte).
  • Agora que o bug foi detectado, o aplicativo falhou até condições normais também, até que o bug fosse corrigido.

Isso pode acontecer porque o bug corrompe algum estado do aplicativo que causa falhas nas condições anteriormente normais.


4
Uma explicação é que houve falhas aleatórias no software, que ninguém foi capaz de vincular mentalmente. Portanto, esses erros foram considerados de causa natural (como falhas aleatórias de hardware). Depois que o código-fonte é lido, as pessoas agora podem relacionar todos os erros aleatórios anteriores a essa causa e perceberão que ele nunca deveria ter funcionado em primeiro lugar.
Rwong 29/07

4
Uma segunda explicação é que há uma parte no software que é implementada com um padrão de cadeia de responsabilidade. Cada manipulador é escrito de uma maneira robusta, apesar de um manipulador ter um bug crítico. Agora, o primeiro manipulador sempre falha, mas por causa do segundo manipulador (que tem sobreposições de responsabilidade) tenta realizar a mesma tarefa, a operação geral parece ter sido bem-sucedida. Se houver alguma alteração no segundo módulo, como alteração na área de responsabilidade, isso causaria uma falha geral, embora o bug real esteja em um local diferente.
Rwong 29/07

13

Um exemplo da vida real. Não consigo mostrar código, mas a maioria das pessoas se relaciona com isso.

Temos uma grande biblioteca interna de funções utilitárias onde trabalho. Um dia, estou procurando uma função para fazer uma coisa específica e acho que Frobnicate()tente usá-la. Uh-oh: acontece que Frobnicate()sempre retorna um código de erro.

Ao investigar a implementação, encontro alguns erros lógicos básicos Frobnicate()que fazem com que ela sempre falhe. No controle de origem, posso ver que a função não foi modificada desde que foi escrita, o que significa que a função nunca funcionou como pretendido. Por que ninguém percebeu isso? Pesquiso o restante do alistamento de origem e percebo que todos os chamadores existentes Frobnicate()estão ignorando o valor de retorno (e, portanto, contêm bugs sutis próprios). Se eu alterar essas funções para verificar o valor de retorno como deveria, elas também começarão a falhar.

Este é um caso comum da condição # 2 que Jon Hopkins mencionou em sua resposta, e é deprimente comum em grandes bibliotecas internas.


... o que faz um bom motivo para evitar a gravação de biblioteca interna sempre que uma externa for utilizável. Ele será mais testado e, portanto, terá muito menos surpresas desagradáveis ​​(as bibliotecas de código aberto são preferíveis, porque você pode corrigi-las se o fizerem).
Jan Hudec

Sim, mas se os programadores ignoram os códigos de retorno, isso não é culpa da biblioteca. (By the way, quando foi a última vez que você verificou o retcode de printf()?)
JensG

É exatamente por isso que as exceções verificadas foram inventadas.
Kevin Krumwiede

10

Aqui está um Schrödinbug real que eu vi em algum código do sistema. Um daemon raiz precisa se comunicar com um módulo do kernel. Portanto, o código do kernel cria alguns descritores de arquivo:

int pipeFDs[1];

Em seguida, configura a comunicação através de um canal que será anexado a um canal nomeado:

int pipeResult = pipe(pipeFDs);

Isso não deve funcionar. pipe()grava dois descritores de arquivo na matriz, mas há apenas espaço para um. Mas há cerca de sete anos, fez trabalho; a matriz passou antes de algum espaço não utilizado na memória que foi cooptado para ser um descritor de arquivo.

Então, um dia, tive que portar o código para uma nova arquitetura. Parou de funcionar e o bug que nunca deveria ter funcionado foi descoberto.


5

Um corolário do Schrödinbug é o Heisenbug - que descreve um bug que desaparece (ou aparece ocasionalmente) ao tentar investigar e / ou corrigi-lo.

Heisenbugs são pequenos trapaceiros míticos e inteligentes que correm e se escondem quando um depurador é carregado, mas que saem da madeira quando você para de assistir.

Na realidade, estes geralmente são causados ​​por um ou outro dos seguintes:

  • o impacto dessa otimização, na qual o código compilado -DDEBUGé otimizado para um nível diferente da versão
  • diferenças sutis de tempo devido a interrupções ou barramentos de comunicação do mundo real, que são sutilmente diferentes das cargas simuladas "perfeitas" simuladas

Ambos destacam a importância de testar o código de liberação no equipamento de liberação, bem como o teste de unidade / módulo / sistema usando emuladores.


Por que eu não percebi a resposta de S.Lote e o comentário de delnan antes de publicar isso?
Andrew

Tenho pouca experiência, mas encontrei algumas delas. Eu estava trabalhando em um ambiente Android NDK. Quando o depurador encontrou um ponto de interrupção, ele apenas interrompeu os encadeamentos Java, não os C ++, possibilitando algumas chamadas porque os elementos foram inicializados no C ++. Se deixado sem depurador, o código Java seria mais rápido que o C ++ e tentaria usar valores que ainda não foram inicializados.
MLProgrammer-CiM

Descobri um Heisenbug em nosso uso a API do banco de dados Django há alguns meses: Quando DEBUG = True, o nome dos "parâmetros" arg para uma consulta SQL bruta muda. Nós tinha sido usá-lo como uma palavra-chave arg para maior clareza devido à duração da consulta, que quebrou completamente quando era hora de empurrar para o site beta, ondeDEBUG = False
Izkata

2

Eu já vi alguns Schödinbugs e sempre pelo mesmo motivo:

A política da empresa exigia que todos deveriam usar um programa.
Ninguém realmente o usou (principalmente porque não havia treinamento para isso).
Mas eles não podiam dizer isso à gerência. Então todo mundo tinha que dizer "Eu uso este programa há 2 anos e nunca encontrei esse bug até hoje".
O programa nunca realmente funcionou, exceto por uma minoria de usuários (incluindo os desenvolvedores que o escreveram).

Em um caso, o programa havia sido sujeito a muitos testes, mas não no banco de dados real (que era considerado muito sensível, portanto, uma versão falsa foi usada).


1

Eu tenho um exemplo da minha própria história, isso foi há 25 anos atrás. Eu era criança fazendo programação gráfica rudimentar no Turbo Pascal. O TP tinha uma biblioteca chamada BGI, que incluía algumas funções que permitem copiar uma região da tela em um bloco de memória baseado em ponteiro e depois misturá-lo em outro lugar. Combinado com xor-blitting em uma tela em preto e branco, poderia ser usado para fazer animações simples.

Eu queria dar um passo adiante e fazer sprites. Eu escrevi um programa que desenhava grandes blocos e controles para colori-los, assim como você os reproduzia em pixels, produzindo um programa de desenho simples para criar sprites, que ele poderia copiar para a memória. Havia apenas um problema: para usar esses sprites, eles precisariam ser salvos em um arquivo para que outros programas pudessem lê-los. Mas o TP não tinha como serializar a alocação de memória baseada em ponteiro. Os manuais declararam que não podiam ser gravados em arquivo.

Eu vim com um pedaço de código que, com sucesso, gravou no arquivo. E comecei a escrever um programa de teste que misturava um sprite do meu programa de desenho em segundo plano - no meu caminho para criar um jogo. E funcionou lindamente. No dia seguinte, no entanto, parou de funcionar. Não mostrava nada além de uma bagunça ilegível. Nunca mais funcionou. Criei um novo sprite, e ele funcionou perfeitamente - até que não funcionou, e foi uma bagunça ilegível novamente.

Demorou muito tempo, mas finalmente descobri o que estava acontecendo. O programa de desenho não estava, como pensei, salvando os dados de pixel copiados em arquivo - estava salvando o ponteiro em si. Quando o programa seguinte leu o arquivo, ele acabou com um ponteiro para o mesmo bloco de memória - que ainda continha o que o último programa havia escrito lá (isso era no MS-DOS, o gerenciamento de memória era inexistente). Mas funcionou ... até você reiniciar ou executar qualquer coisa que tivesse reutilizado a mesma área de memória, e você ficou com uma bagunça confusa porque estava colocando um monte de dados totalmente não relacionados no bloco de memória de vídeo.

Ele nunca deveria ter funcionado, nunca deveria ter parecido funcionar (e em qualquer sistema operacional real não teria), mas ainda funcionava e, uma vez quebrado, permanecia quebrado.


0

Isso acontece o tempo todo quando as pessoas usam depuradores.

O ambiente de depuração é diferente do ambiente de produção real - sem depurador -.

A execução com um depurador pode mascarar coisas como estouros de pilha, porque os quadros de pilha do depurador mascaram o erro.


Eu não acho que esteja se referindo à diferença entre o código executado em um depurador e quando compilado.
Jon Hopkins

26
Isso não é um schrödinbug, é um heisenbug .

@ delnan: Está no limite, IMO. Acho que é algo indeterminado, porque existem graus de liberdade desconhecidos. Eu gosto de heisenbug reserva para as coisas que medem uma coisa realmente perturba outro (isto é, condições de corrida, configurações Optimizer, limitações de largura de banda de rede, etc.)
S.Lott

@ S.Lott: A situação que você descreve envolve a observação de mudar as coisas mexendo com os quadros da pilha ou algo parecido. (O pior exemplo que eu já vi foi o depurador executar pacificamente e "corretamente" cargas de valores inválidos de registradores de segmentos no modo de etapa única. O resultado foram algumas rotinas na RTL que foram enviadas apesar de carregar um ponteiro de modo real no modo protegido Como estava apenas sendo copiado e não desreferenciado, comportou-se perfeitamente.)
Loren Pechtel

0

Eu nunca vi um schrodinbug verdadeiro e não acho que eles possam existir - descobrir que isso não vai quebrar as coisas.

Em vez disso, algo mudou que expôs um bug que se esconde há séculos. O que quer que seja alterado ainda é alterado e, portanto, o bug continua aparecendo enquanto, ao mesmo tempo, alguém encontra o bug.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.