Qual pode ser a causa de novos bugs aparecendo em outro lugar quando um bug conhecido é resolvido?

14

Durante uma discussão, um dos meus colegas disse que ele tem algumas dificuldades com seu projeto atual enquanto tenta solucionar bugs. "Quando eu resolvo um bug, outra coisa para de funcionar em outro lugar", disse ele.

Comecei a pensar em como isso poderia acontecer, mas não consigo descobrir.

  • Às vezes, tenho problemas semelhantes quando estou muito cansado / com sono para fazer o trabalho corretamente e ter uma visão geral da parte do código em que estava trabalhando. Aqui, o problema parece durar alguns dias ou semanas e não está relacionado ao foco do meu colega.
  • Também posso imaginar esse problema surgindo em um projeto muito grande, mal gerenciado , em que os colegas de equipe não têm idéia de quem faz o quê e que efeito no trabalho de outros pode ter uma mudança que eles estão fazendo. Também não é o caso aqui: é um projeto bastante pequeno, com apenas um desenvolvedor.
  • Também pode ser um problema com a base de código antiga, mal mantida e nunca documentada , onde os únicos desenvolvedores que podem realmente imaginar as conseqüências de uma mudança deixaram a empresa anos atrás. Aqui, o projeto acabou de começar e o desenvolvedor não usa a base de código de ninguém.

Então, qual pode ser a causa desse problema em uma base de código nova e pequena, escrita por um único desenvolvedor que permanece focado em seu trabalho ?

O que pode ajudar?

  • Testes de unidade (não existem)?
  • Arquitetura adequada (tenho certeza de que a base de código não tem arquitetura alguma e foi escrita sem nenhum pensamento preliminar), exigindo toda a refatoração?
  • Parear programação?
  • Algo mais?
Arseni Mourzenko
fonte
14
Ah, o bom e velho padrão de design "ondas em cascata de falha". :-)
Brian Knoblauch
1
Eu comparo isso a uma bolha em uma folha de contato. Empurre para baixo, ele aparece em outro lugar. O melhor a minha codificação fica, menos eu vejo isso
johnc
2
Em uma nota lateral, eu tinha exatamente isso em um sistema incorporado. Eu adicionei uma chamada de função para corrigir um problema. Essa chamada de função foi demais para a pilha (o microcontrolador não tinha detecção de fluxo de pilha) e, portanto, escreveu algumas coisas aleatórias em outro lugar na memória, o que obviamente quebrou algo em algum lugar completamente diferente. Portanto, isso pode acontecer em uma pequena base de código com apenas um desenvolvedor e boa arquitetura.
risingDarkness
... e isso foi um pesadelo para depurar.
risingDarkness

Respostas:

38

Não tem muito a ver com foco, tamanho do projeto, documentação ou outros problemas do processo. Problemas como esse geralmente são resultado de acoplamento excessivo no design, o que dificulta o isolamento de alterações.

Karl Bielefeldt
fonte
15
isto combinado com pouca ou nenhuma testes de regressão
Ryathal
3
É verdade, @Ryathal, embora o teste de regressão não impeça esses tipos de bugs, apenas informe-os mais cedo.
Karl Bielefeldt
Se você souber sobre eles em breve (por exemplo, minutos após a criação dos bugs), poderá desfazer suas alterações e fingir efetivamente que elas nunca aconteceram.
bdsl
14

Uma das causas pode ser o acoplamento estreito entre os componentes do seu software: se não houver interfaces simples e bem definidas entre os componentes, mesmo uma pequena alteração em uma parte do código poderá introduzir efeitos colaterais inesperados em outras partes do software. código.

Como exemplo, ultimamente eu estava trabalhando em uma classe que implementa um componente da GUI no meu aplicativo. Por semanas, novos bugs foram relatados, eu os corrigi e novos bugs apareceram em outro lugar. Percebi que essa classe havia crescido muito, estava fazendo muitas coisas e muitos métodos dependiam de outros métodos serem chamados na sequência correta para funcionar corretamente.

Em vez de corrigir os três últimos bugs, fiz uma refatoração forte: divida o componente em uma classe principal mais as classes MVC (três classes adicionais). Dessa maneira, tive que dividir o código em partes menores e mais simples e definir interfaces mais claras. Após a refatoração, todos os erros foram resolvidos e nenhum novo erro foi relatado.

Giorgio
fonte
7

É fácil para um bug mascarar outro. Suponha que o bug "A" faça com que a função errada seja chamada para manipular a entrada. Quando o bug "A" é corrigido, de repente a função correta é chamada, que nunca foi testada.

ddyer
fonte
5

Bem, a causa imediata são dois erros que fazem um certo, ou pelo menos fazem um não-obviamente-errado. Uma parte do código está compensando o comportamento incorreto da outra parte. Ou, se a primeira parte não estiver "errada" como tal, há algum acordo não escrito entre as duas partes que está sendo violado quando o código é alterado.

Por exemplo, suponha que as funções A e B usem uma convenção baseada em zero para alguma quantidade, para que funcionem juntas corretamente, mas C use uma, você pode "consertar" A para trabalhar com C e descobrir um problema com B.

O problema mais profundo é a falta de verificação independente da correção das partes individuais. Os testes de unidade são projetados para resolver isso. Eles também atuam como uma especificação das entradas apropriadas. Por exemplo, um bom conjunto de testes deixaria claro que as funções A e B esperavam entrada baseada em 0 e baseada em C 1.

A obtenção correta das especificações também pode ser feita de outras maneiras, desde documentos oficiais a bons comentários no código, dependendo das necessidades do projeto. A chave é entender o que cada componente espera e o que promete, para que você possa encontrar inconsistências.

Uma boa arquitetura ajuda no problema de entender o código, facilitando isso. A programação em pares é útil para evitar erros em primeiro lugar ou para encontrá-los mais rapidamente.

Espero que isto ajude.

Mike B
fonte
5

Acoplamento apertado, falta de teste, esses são provavelmente os culpados mais comuns. Basicamente, o problema comum são apenas normas e procedimentos de má qualidade. Outro é apenas um código incorreto que consegue ter sorte por um tempo com o comportamento correto. Considere o memcpybug de Linus Torvalds, ao alterar sua implementação, expondo (não causado) erros em clientes usados memcpyem locais onde deveriam ter sido usados memmovecom origem e destino sobrepostos.


fonte
4

Parece que esses "novos" erros não são realmente "novos". Eles simplesmente não eram um problema, até que o outro código que estava quebrado foi corrigido. Em outras palavras, seu colega não percebe que ele realmente tinha dois erros o tempo todo. Se o código que não está provando ser quebrado não estivesse quebrado, ele não teria falhado, uma vez que o outro pedaço de código fosse realmente corrigido.

Em ambos os casos, um melhor regime de teste automatizado pode ser útil. Parece que seu colega precisa fazer um teste de unidade da base de código atual. No futuro, o teste de regressão verificará se o código existente continua funcionando.

Ramhound
fonte
0

Melhore a amplitude do seu regime de teste automatizado. SEMPRE execute o conjunto completo de testes antes de confirmar as alterações no código. Dessa forma, você detectará o efeito pernicioso de suas alterações.

Stephen Gross
fonte
0

Acabei de encontrar isso quando um teste estava incorreto. O teste verificou um determinado estado de permissão que estava correto. Atualizei o código e executei o teste de permissão. Funcionou. Então eu fiz todos os testes. Todos os outros testes que usaram o recurso verificado falharam. Corrigi o teste e a verificação de permissão, mas havia um pouco de pânico no começo.

Especificações inconsistentes também acontecem. Então é quase garantido que a correção de um bug criará outro (emocionante quando essa parte específica da especificação não é exercida até mais tarde no projeto).

ccoakley
fonte
0

Imagine que você tem um produto completo. Em seguida, você adiciona algo novo, tudo parece bem, mas você quebrou outra coisa, que depende de algum código que você altera para fazer o novo recurso funcionar. Mesmo se você não alterar nenhum código, basta adicionar funcionalidade à existente, isso pode quebrar outra coisa.

Então, basicamente você quase se respondeu:

  • acoplamento solto
  • falta de testes

Aprenda a adaptar o princípio TDD (pelo menos para novos recursos) e tente testar todos os estados possíveis que possam ocorrer.

A programação em pares é ótima, mas nem sempre "disponível" (tempo, dinheiro, ambos ..). Porém, as revisões de código (por seus colegas, por exemplo) uma vez por dia / semana / conjunto de confirmações também ajudarão bastante, principalmente quando a revisão incluir o conjunto de testes. (Acho difícil não escrever bugs nos conjuntos de testes ... às vezes preciso testar o teste internamente (verificação de sanidade) :)).

Dalibor Filus
fonte
0

Digamos que o desenvolvedor A escreveu algum código com um bug. O código não exatamente o que deveria fazer, mas algo um pouco diferente. O desenvolvedor B escreveu um código que se baseava no código de A fazendo exatamente o que é especificado e o código de B não funciona. B investiga, encontra o comportamento incorreto no código de A e o corrige.

Enquanto isso, o código do desenvolvedor C funcionava corretamente porque ele contava com o comportamento incorreto do código de A. O código de A agora está correto. E o código de C para de funcionar. O que significa que, quando você corrige o código, precisa verificar com muito cuidado quem usa esse código e como o comportamento deles será alterado com o código fixo.

Eu tive outra situação: algum código se comportou mal e impediu um recurso completamente de funcionar em alguma situação X. Então, mudei o mau comportamento e fiz o recurso funcionar. O infeliz efeito colateral foi que todo o recurso teve problemas significativos na situação X e falhou em todo o lugar - isso era completamente desconhecido para qualquer pessoa, porque a situação nunca havia surgido antes. Bem, isso é difícil.

gnasher729
fonte