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:
- 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.)
- 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.
- Use ferramentas de análise dinâmica, como valgrind, para verificar problemas de memória, vazamentos, etc.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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.
fonte
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.
fonte
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 .
fonte
À 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.
fonte
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