Qual é a melhor maneira de preparar seu design e código para esses erros "desconhecidos desconhecidos" desde o primeiro dia?

8

Estou apenas me perguntando, existe algum método ou técnica prática ou mesmo truques para evitar os bugs "desconhecidos desconhecidos", especialmente os desagradáveis ​​e aleatórios que geralmente surgem nos últimos minutos ou pelo menos para manter essas coisas em um nível mínimo. Eu pergunto isso porque quando você trabalha em uma nova plataforma ou usa certas novas tecnologias pela primeira vez, como você justifica que seu design e código são robustos o suficiente? Ou essas coisas só podem ser aprendidas com o tempo e os erros?

(Eu uso C ++ na maior parte do meu tempo de trabalho)

Obrigado!

CaptainJH
fonte

Respostas:

5

Há quase vinte anos, obtive muitas informações sobre o excelente livro de David Thielen "Sem erros: entrega de código sem erros em C e C ++", que agora está disponível como PDF gratuito .

Ele me ensinou duas grandes idéias ...

Os erros não vêm do nada. Todos nós, programadores, sentamos e os escrevemos em nosso código com nossos próprios dedos.

"Bug" indica que alguma agência externa decidiu infestar seu programa com bugs e que, se você vive uma vida limpa e sacrifica pequenos animais peludos ao pé do computador, eles desaparecem ... Esse conceito é importante porque é colorido sua abordagem para depurar seu código. Se você vê os erros como "bugs", espera que nenhum seja encontrado. (Você espera que a boa fada tenha aparecido, polvilhado pó de duende e os insetos deixados.)

Os bugs não devem ser chamados de bugs, eles devem ser chamados de Massive Fuck-Ups [MFUs] ... As MFUs existem porque os programas são escritos por pessoas e as pessoas cometem erros ... Você escreverá MFUs. Você se sentará e, com total malícia de premeditação, colocará MFUs em seu código. Pense nisso - você sabe que é você quem está colocando os bugs lá. Portanto, se você se sentar para codificar, estará inserindo alguns bugs.

Como é o destino inevitável de todos os programadores escrever bugs, preciso codificar defensivamente, incluindo coisas que irão pular, gritar e acenar bandeiras vermelhas quando detectarem um bug.

Tendo sido escrito no início dos anos 90, os detalhes sobre isso no livro de Thielen são bastante antigos. Por exemplo, no Linux e Mac OS X, você não precisa mais escrever seu próprio wrapper para o novo operador C ++; você pode usar o valgrind para isso.

Mas há algumas coisas que eu faço rotineiramente para C / C ++ / ObjC:

  1. Quando razoavelmente possível, ative a opção "Avisos são erros" do compilador e corrija todos eles. (Eu mantenho um projeto legado em que a correção de todos de uma só vez levaria semanas, então eu apenas conserto um arquivo a cada poucas semanas - e em alguns anos, posso ativar essa opção.)
  2. Use uma ferramenta de análise de código estática, como o PC-Lint da Gimpel ou a muito bacana agora incorporada no Xcode da Apple. A cobertura é ainda melhor, mas o custo é para grandes corporações, não meros mortais.
  3. Use ferramentas de análise dinâmica, como valgrind, para verificar problemas de memória, vazamentos, etc.
  4. Como diz Thielen (e ainda vale a pena ler o capítulo): Afirme o mundo . Obviamente, ninguém além de um idiota chamará sua função com um ponteiro nulo - e isso significa que alguém, em algum lugar, é um idiota que fará exatamente isso. Pode até ser você em três anos quando o que você estava fazendo hoje ficou nebuloso. Portanto, basta adicionar uma declaração no início da função para validar esse argumento do ponteiro - leva três segundos para digitar e desaparece no executável do release.
  5. Em C ++, o RTTI é seu amigo. Novamente, ninguém além de um idiota chamará sua função com um ponteiro para o tipo errado de objeto - o que significa que, inevitavelmente, algum idiota o fará - e o custo para se defender disso é insignificante. No código baseado em C derivado do GObject, você pode fazer o mesmo com as macros defensivas de conversão dinâmica.
  6. Os testes automatizados de unidade e regressão agora são uma parte essencial do meu repertório. Em um projeto, eles são parte integrante do sistema de compilação de lançamento, e a compilação não será concluída a menos que todos sejam aprovados.
  7. Outra parte importante é o código de log nos executáveis ​​de depuração e lançamento que podem ser ativados no tempo de execução por algo como uma variável de ambiente.
  8. Escreva testes defensivos para que os programadores que executam executáveis ​​de depuração não possam ignorá-los se falharem. Mensagens de tempo de execução para o console podem ser ignoradas. O programa que trava com uma declaração não pode ser ignorado.
  9. Ao projetar, forneça APIs públicas e implementações privadas que o código externo não pode obter. Dessa forma, se você precisar refatorar, ninguém confiará em alguma variável mágica do estado interior ou algo assim. Nas aulas de C ++, sou um grande fã de protegido e privado para isso. Eu também acho que as classes proxy são ótimas, mas não as utilizo pessoalmente.

Obviamente, o que você fará para um novo idioma ou tecnologia variará nos detalhes. Mas uma vez que você leva em consideração as noções de que os erros são importantes, você escreveu com seus próprios dedos, e seu código está sob ataque constante de um exército de idiotas, com você na cabeça como general, tenho certeza de que você descobrirá técnicas defensivas adequadas.

Bob Murphy
fonte
14

Bem, se você sabe disso, eles entram na categoria "bugs desconhecidos conhecidos" (ou seja, você sabe que algo dessa natureza "ocorrerá). Qualquer quantidade de testes de unidade não os captura, eles são realmente úteis apenas para casos conhecidos.

A maneira como lidamos com isso é colocar um serviço de log de erros no aplicativo em execução, relatar de volta à base quando ocorrer e lidar com ele quando surgir. Caso contrário, você pode passar anos e ainda não cobrir nada ... em algum momento, basta esperar para ver o que acontece no mundo real e evoluir rapidamente.

No lado do design, você projeta a manutenção como um dos principais fatores.

  • Padrões de aprendizagem que funcionam e padrões a serem evitados. Quanto mais padrões consistentes e coesos você tiver, mais confortável poderá ser que uma classe específica de problema ocorra ou não.
  • Tornar as coisas óbvias. A obscuridade leva à confusão, leva a insetos.
  • Convenções de nomes fortes por todo o caminho. Se você nomear as coisas de maneira consistente e consistente, haverá uma enorme quantidade de benefícios ao tentar mudar ou explicar as coisas ... todas as coisas chamadas Factory farão X.
  • Soletre as coisas completamente. Atualmente, o preenchimento automático não usa acrônimos quando a palavra completa remove a confusão.
  • Separe em camadas ou abstrações ... para que estilos específicos de problemas ocorram em uma camada específica em vez de "em algum lugar"
  • Isole as classes de problemas em camadas e aspectos. Quanto menos uma parte tiver a ver com outra parte do código, melhor em geral. Mesmo que demore um pouco mais para escrever coisas semelhantes duas vezes.

E a chave ... Encontre um mentor que tenha cometido todos os erros anteriormente OU faça uma bagunça em vários até descobrir o que funciona e o que não funciona.

Robin Vessey
fonte
1
Eu gosto de "design para manutenção". Especialmente bom é volta rápida quando os problemas não acabam acontecendo. Isso significa bons testes de unidade abrangentes, uma boa estratégia de implantação e bons processos de rastreamento / controle de qualidade.
22711 Dean Harding
4

Todos os itens acima são bons pontos. Mas há algo não mencionado. Você precisa tornar seus módulos e funções paranóicos. Teste de faixa todos os parâmetros de função. Cuidado com as cordas com princípios ou finais em branco ou muito curtas ou muito longas. Cuidado com os booleanos que são mais verdadeiros do que os verdadeiros. Em linguagens não tipadas como PHP, atente para tipos de variáveis ​​inesperados. Cuidado com NULL.

Esse código paranóico é frequentemente codificado como afirmações que podem ser desabilitadas em uma construção de produção para acelerar as coisas. Mas isso definitivamente evitará os pânico de última hora.

Andy Canfield
fonte
Como um booleano não pode ser verdadeiro nem falso?
Zhehao Mao
@Zhehao Mao: Se o booleano for uma coluna em um banco de dados, poderá ser True, False ou NULL.
Mike Sherrill 'Cat Recall'
Quando eu era soldado, tínhamos um ditado. "Quando todo mundo realmente está atrás de você, a paranóia é apenas bom, som pensar." Algumas autoridades chamam isso de programação defensiva .
Mike Sherrill 'Cat Recall'
Ah eu vejo. Estranheza SQL.
Zhehao Mao 22/07
3

Rob está correto em dizer que os testes de unidade não irá salvá-lo de erros desconhecida, mas os testes de unidade irá ajudar a salvar você de introduzir erros quando você corrigir os erros desconhecidos e você vai economizar acidentalmente re-introduzir erros antigos. O TDD também o obriga a projetar seu software desde o início para ser testável e com um valor contínuo positivo enorme.

mcottle
fonte
Esse aspecto dos testes de unidade parece ser o mais incompreensível: você não prova a correção do seu código com unittests, falsifica a correção das seguintes alterações. Mas toda vez que um bug no código unittested for encontrado, alguém chora 'ver, os testes são inúteis, eles não encontrar este bug'
keppla
Em seguida, você adiciona um teste para reproduzir o defeito. Corrija o defeito e você sempre estará testando esse erro toda vez que executar seu conjunto de testes ...
mcottle
isso é o que eu faria, mas que muitas vezes leva a 'sim, agora é tarde demais, o bug já aconteceu'. O fato de que o erro não é introduzido novamente, muitas vezes, ser supervisionada
keppla
Isso é verdade, mas a essa
227 Robin Vessey
2

Evite status / "efeitos colaterais" sempre que possível. Enquanto os computadores são determinísticos e fornecem a mesma saída para a mesma entrada, a visão geral sobre a entrada é sempre incompleta. Infelizmente, na maioria das vezes não percebemos o quão incompleto é.

Ao falar sobre aplicativos da Web, o banco de dados inteiro, a solicitação atual, a sessão do usuário, as bibliotecas de terceiros instaladas e muito mais fazem parte da entrada. Ao falar sobre encadeamentos, é ainda pior: todo o seu sistema operacional, com todos os outros processos gerenciados pelo mesmo agendador, é 'parte da entrada'.

Os erros são causados ​​por julgar incorretamente a maneira como a entrada é manipulada ou por julgar incorretamente a entrada. Os últimos são, na minha experiência, os mais difíceis: você só pode observá-los "ao vivo", frequentemente, não tem mais informações.

Ao aprender novas tecnologias, infraestruturas etc., é recomendável seguir uma visão geral de quais componentes contribuem para a entrada e, em seguida, tente evitar o maior número possível .

keppla
fonte
+1: Os efeitos colaterais geralmente podem ser evitados aplicando princípios SOLID e criando métodos atômicos. Além disso, o código deve ser coberto por declarações.
Falcon
0

À medida que seu software se torna mais complexo, é inevitável que ocorram alguns erros. A única maneira de evitar isso completamente é desenvolver apenas software trivial - e mesmo assim, você cometerá um erro estúpido de tempos em tempos.

A única coisa prática que você pode fazer é evitar complexidade desnecessária - tornar seu software o mais simples possível, mas não mais simples que isso.

É basicamente disso que se trata todos os princípios e padrões de design mais específicos - tornando as coisas o mais simples possível. O problema é que "simples de que maneira" pode ser subjetivo - você quer dizer o design absolutamente mais simples para os requisitos atuais ou simples de modificar para requisitos futuros. E há princípios para isso também.

Os testes de unidade são um exemplo dessa incerteza. Por um lado, eles são complexidade desnecessária - código que deve ser desenvolvido e mantido, mas que não faz o trabalho. Por outro lado, são uma maneira simples de automatizar os testes, reduzindo a quantidade de testes manuais muito mais difíceis que devem ser realizados.

Não importa quanta teoria de design você aprenda e quanta experiência você adquira, o princípio primordial (e às vezes o único guia que você possui) é buscar a simplicidade.

Steve314
fonte
0

Não há nada de aleatório nos erros de software, as causas principais são de natureza perfeitamente determinística, instruções incorretas para o computador.

Os erros de encadeamento podem ser não determinísticos no comportamento da execução, mas não são aleatórios na causa raiz.

Eles acontecem exatamente pela mesma razão, apenas em momentos aparentemente imprevisíveis no tempo, mas isso não os torna aleatórios apenas aparentemente imprevisíveis, depois que você conhece a causa principal, pode determinar de forma determinística quando eles acontecerão.

Eu disse aparentemente e fiz a distinção por uma razão. Aleatório significa uma coisa, feita, feita, acontecendo ou escolhida sem método ou decisão consciente , que implica que há alguma tomada de decisão independente por parte do computador; não há que ele esteja fazendo exatamente o que foi solicitado a fazer? apenas não disse para fazer a coisa certa em alguns casos muito determinísticos.

A semântica das palavras existe por um motivo: aleatório não significa algo diferente apenas porque alguém a usa incorretamente, sempre significa a mesma coisa. Um termo melhor seria erros de lógica não intencionais ou não óbvios .

Considerar os erros aleatoriamente é quase como aceitar que existe alguma outra força incompreensível no trabalho que não é totalmente compreendida, agindo independentemente da sua entrada no computador, e isso não é muito científico. Quero dizer, os deuses estão com raiva e ferindo sua aplicação por um capricho?


fonte
+1 para um ponto válido, mas -1 para nitpicking. Então, +/- 0. Eu acho que a maioria das pessoas que lêem a questão levou "aleatório" não no sentido literal completamente (o que exatamente faz "aleatório" realmente significa, levado ao seu extremo?), Mas sim como algo média como "eu não entendo como esse comportamento pode ter surgido ou por que o software faz algo errado ".
um CVn
@ Michel - é por isso que eu disse aparentemente e fiz a distinção. Não há extremo para o aleatório , significa uma coisa: Feito, feito, acontecendo ou escolhido sem método ou decisão consciente; a semântica das palavras existe por uma razão; aleatório não significa algo diferente apenas porque alguém o usa incorretamente, sempre significa a mesma coisa. O que eles provavelmente queriam dizer não foi intencional , por causa das razões que declaro na minha explicação na minha resposta.