Se você tivesse que escolher suas técnicas (inteligentes) favoritas para codificação defensiva, quais seriam? Embora minhas linguagens atuais sejam Java e Objective-C (com experiência em C ++), sinta-se à vontade para responder em qualquer idioma. A ênfase aqui seria em técnicas defensivas inteligentes que não sejam aquelas que mais de 70% de nós aqui já conhecem. Então agora é hora de cavar fundo na sua mochila de truques.
Em outras palavras, tente pensar em outro exemplo que não seja interessante :
if(5 == x)
em vez deif(x == 5)
: para evitar tarefas não intencionais
Aqui estão alguns exemplos de algumas das melhores práticas de programação defensiva intrigantes (exemplos específicos de linguagem estão em Java):
- Bloqueie suas variáveis até saber que precisa alterá-las
Ou seja, você pode declarar todas as variáveis final
até saber que precisará alterá-las, e nesse ponto poderá remover o final
. Um fato geralmente desconhecido é que isso também é válido para parâmetros de método:
public void foo(final int arg) { /* Stuff Here */ }
- Quando algo ruim acontecer, deixe um rastro de evidências para trás
Há várias coisas que você pode fazer quando há uma exceção: obviamente, registrando-a e realizando alguma limpeza, seriam algumas. Mas você também pode deixar um rastro de evidência (por exemplo, definir variáveis para valores sentinela como "UNABLE TO LOAD FILE" ou 99999 seria útil no depurador, caso você tenha passado por um catch
bloco de exceção ).
- Quando se trata de consistência: o diabo está nos detalhes
Seja o mais consistente com as outras bibliotecas que você está usando. Por exemplo, em Java, se você estiver criando um método que extrai um intervalo de valores, torne o limite inferior inclusivo e o limite superior exclusivo . Isso o tornará consistente com métodos como o String.substring(start, end)
que opera da mesma maneira. Você encontrará todos esses tipos de métodos no Sun JDK se comportando dessa maneira, uma vez que realiza várias operações, incluindo a iteração de elementos consistentes com as matrizes, onde os índices variam de Zero ( inclusive ) até o comprimento da matriz ( exclusivo ).
Então, quais são suas práticas defensivas favoritas?
Atualização: Se você ainda não o fez, sinta-se à vontade para entrar em contato. Estou dando a chance de mais respostas antes de escolher a resposta oficial .
fonte
Respostas:
Em c ++, uma vez eu gostei de redefinir o novo, de modo que ele fornecesse memória extra para detectar erros no post de vedação.
Atualmente, prefiro evitar a programação defensiva em favor do desenvolvimento orientado a testes . Se você detectar erros de forma rápida e externa, não precisará confundir seu código com manobras defensivas, seu código estará SECO e você terá menos erros dos quais precisa se defender.
Como o WikiKnowledge escreveu :
fonte
SQL
Quando tenho que excluir dados, escrevo
Quando executá-lo, saberei se esqueci ou estraguei a cláusula where. Eu tenho segurança. Se estiver tudo bem, realço tudo depois dos tokens de comentário '-' e o executo.
Editar: se estiver excluindo muitos dados, usarei count (*) em vez de apenas *
fonte
Aloque um pedaço razoável de memória quando o aplicativo for iniciado - acho que Steve McConnell se referiu a isso como um pára-quedas de memória no Code Complete.
Isso pode ser usado no caso de algo sério dar errado e você precisar terminar.
A alocação antecipada dessa memória fornece uma rede de segurança, pois você pode liberá-la e usar a memória disponível para fazer o seguinte:
fonte
Em toda declaração de switch que não possui um caso padrão, eu adiciono um caso que interrompe o programa com uma mensagem de erro.
fonte
Ao lidar com os vários estados de uma enumeração (C #):
Então, dentro de alguma rotina ...
Em algum momento, adicionarei outro tipo de conta a essa enumeração. E quando o fizer, esquecerei de corrigir esta declaração de opção. Portanto, as
Debug.Fail
falhas são horríveis (no modo Debug) para chamar minha atenção para esse fato. Quando adiciono ocase AccountType.MyNewAccountType:
, a horrível falha para ... até adicionar outro tipo de conta e esquecer de atualizar os casos aqui.(Sim, o polimorfismo provavelmente é melhor aqui, mas este é apenas um exemplo em cima da minha cabeça.)
fonte
Ao imprimir mensagens de erro com uma sequência (principalmente uma que depende da entrada do usuário), sempre uso aspas simples
''
. Por exemplo:Essa falta de aspas
%s
é muito ruim, porque digamos que o nome do arquivo é uma string vazia ou apenas espaço em branco ou algo assim. A mensagem impressa seria obviamente:Então, sempre melhor fazer:
Então, pelo menos, o usuário vê isso:
Acho que isso faz uma enorme diferença em termos de qualidade dos relatórios de erros enviados pelos usuários finais. Se houver uma mensagem de erro engraçada como essa em vez de algo genérico, é muito mais provável que a copie / cole em vez de apenas escrever "não abriria meus arquivos".
fonte
Segurança SQL
Antes de escrever qualquer SQL que modifique os dados, envolvo tudo em uma transação revertida:
Isso impede que você execute uma exclusão / atualização incorreta permanentemente. Além disso, você pode executar a coisa toda e verificar contagens razoáveis de registros ou adicionar
SELECT
instruções entre o SQL e oROLLBACK TRANSACTION
para garantir que tudo esteja correto.Quando tiver certeza absoluta de que faz o que esperava, altere o
ROLLBACK
paraCOMMIT
e corra de verdade.fonte
Para todos os idiomas:
Reduza o escopo das variáveis ao mínimo possível. Evite variáveis que são fornecidas apenas para transportá-las para a próxima instrução. Variáveis que não existem são variáveis que você não precisa entender e não pode ser responsabilizado. Use Lambdas sempre que possível, pelo mesmo motivo.
fonte
Em caso de dúvida, bombardeie o aplicativo!
Verifique cada parâmetro no início de cada método (seja você mesmo codificando explicitamente ou usando a programação baseada em contrato, não importa aqui) e bombardeie com a exceção correta e / ou uma mensagem de erro significativa, se houver alguma pré-condição para o código. não conheceu.
Todos sabemos sobre essas pré-condições implícitas quando escrevemos o código , mas, se não forem verificadas explicitamente, estaremos criando labirintos quando algo der errado mais tarde e pilhas de dezenas de chamadas de método separam a ocorrência do sintoma e a localização real onde uma pré-condição não é atendida (= onde realmente está o problema / bug).
fonte
param.NotNull("param")
em vez deif ( param == null ) throw new ArgumentNullException("param");
Em Java, especialmente com coleções, faça uso da API; portanto, se seu método retornar o tipo List (por exemplo), tente o seguinte:
Não permita que nada escape da sua classe que você não precisa!
fonte
em Perl, todo mundo faz
Eu gosto
Isso faz com que o código morra por qualquer aviso do compilador / tempo de execução. Isso é útil principalmente na captura de seqüências não inicializadas.
fonte
C #:
fonte
Na verificação de c # da string.IsNullOrEmpty antes de executar qualquer operação na string, como length, indexOf, mid etc
[Editar]
Agora também posso fazer o seguinte no .NET 4.0, que verifica adicionalmente se o valor é apenas espaço em branco
fonte
if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */
.Em Java e C #, forneça a cada thread um nome significativo. Isso inclui threads do conjunto de threads. Isso torna os despejos de pilha muito mais significativos. É preciso um pouco mais de esforço para atribuir um nome significativo aos threads do pool de threads, mas se um pool de threads tiver um problema em um aplicativo de execução demorada, eu posso causar um despejo de pilha (você conhece o SendSignal.exe , certo? ), pegue os logs e, sem ter que interromper um sistema em execução, posso dizer quais threads são ... o que for. Impasse, vazando, crescendo, qualquer que seja o problema.
fonte
Com o VB.NET, as opções Explicit e Option Strict estão ativadas por padrão para todo o Visual Studio.
fonte
C ++
substitua todas as suas chamadas ' delete pPtr ' e ' delete [] pPtr ' por SAFE_DELETE (pPtr) e SAFE_DELETE_ARRAY (pPtr)
Agora, por engano, se você usar o ponteiro 'pPtr' depois de excluí-lo, receberá o erro 'violação de acesso'. É muito mais fácil consertar do que corrupções aleatórias na memória.
fonte
delete
. O usonew
é bom, desde que o novo objeto seja de propriedade de um ponteiro inteligente.Com o Java, pode ser útil usar a palavra-chave assert, mesmo se você executar o código de produção com as afirmações desativadas:
Mesmo com as afirmações desativadas, pelo menos o código documenta o fato de que param deve ser definido em algum lugar. Observe que esta é uma função auxiliar privada e não é membro de uma API pública. Esse método só pode ser chamado por você; portanto, não há problema em fazer algumas suposições sobre como ele será usado. Para métodos públicos, provavelmente é melhor lançar uma exceção real para entrada inválida.
fonte
Não encontrei a
readonly
palavra - chave até encontrar o ReSharper, mas agora a uso instintivamente, especialmente para as classes de serviço.fonte
Em Java, quando algo está acontecendo e não sei por que, às vezes usarei o Log4J assim:
dessa maneira, recebo um rastreamento de pilha que mostra como entrei na situação inesperada, digamos um bloqueio que nunca foi desbloqueado, algo nulo que não pode ser nulo e assim por diante. Obviamente, se uma exceção real é lançada, não preciso fazer isso. É quando eu preciso ver o que está acontecendo no código de produção sem realmente atrapalhar mais nada. Eu não quero jogar uma exceção e eu não pegar um. Eu só quero um rastreamento de pilha registrado com uma mensagem apropriada para sinalizar para o que está acontecendo.
fonte
Se você estiver usando o Visual C ++, utilize a palavra-chave override sempre que substituir o método de uma classe base. Dessa forma, se alguém alterar a assinatura da classe base, ocorrerá um erro do compilador em vez de o método errado ser chamado silenciosamente. Isso teria me poupado algumas vezes se tivesse existido anteriormente.
Exemplo:
fonte
Aprendi em Java quase nunca esperar indefinidamente que um bloqueio seja desbloqueado, a menos que eu realmente espere que isso demore indefinidamente. Se realisticamente, o bloqueio deve ser desbloqueado em segundos, esperarei apenas por um determinado período de tempo. Se a trava não for desbloqueada, eu reclamo e despejo a pilha nos logs e, dependendo do que é melhor para a estabilidade do sistema, continue como se a trava estivesse desbloqueada ou como se a trava nunca tivesse sido desbloqueada.
Isso ajudou a isolar algumas condições de corrida e condições de pseudo-conflito que eram misteriosas antes de eu começar a fazer isso.
fonte
C #
sealed
muito nas aulas para evitar a introdução de dependências onde não as queria. A permissão da herança deve ser feita explicitamente e não por acidente.fonte
Ao emitir uma mensagem de erro, tente pelo menos fornecer as mesmas informações que o programa tinha ao tomar a decisão de emitir um erro.
"Permissão negada" informa que houve um problema de permissão, mas você não tem idéia do porquê ou onde o problema ocorreu. "Não é possível gravar o log de transações / meu / arquivo: sistema de arquivos somente leitura" pelo menos permite que você saiba a base na qual a decisão foi tomada, mesmo se estiver errada - especialmente se estiver errada: nome de arquivo errado? abriu errado? outro erro inesperado? - e informa onde você estava quando teve o problema.
fonte
Em C #, use a
as
palavra-chave para transmitir.lançará uma exceção se obj não for uma string
deixará um as null se obj não for uma string
Você ainda precisa considerar o nulo, mas isso geralmente é mais direto do que procurar exceções de conversão. Às vezes, você deseja um comportamento do tipo "transmitir ou explodir"; nesse caso, a
(string)obj
sintaxe é preferida.No meu próprio código, acho que uso a
as
sintaxe cerca de 75% das vezes e a(cast)
sintaxe cerca de 25%.fonte
is/instanceof
vem à mente.Esteja preparado para qualquer entrada, e qualquer entrada que seja inesperada, faça o dump para logs. (Dentro da razão. Se você estiver lendo senhas do usuário, não as despeje em logs! E não registre milhares desses tipos de mensagens em logs por segundo. Razão sobre o conteúdo, a probabilidade e a frequência antes de registrá-lo .)
Não estou falando apenas sobre validação de entrada do usuário. Por exemplo, se você estiver lendo solicitações HTTP que espera conter XML, esteja preparado para outros formatos de dados. Fiquei surpreso ao ver respostas HTML em que esperava apenas XML - até que eu olhei e vi que minha solicitação estava passando por um proxy transparente que eu desconhecia e que o cliente alegava ignorância - e que o proxy expirou tentando concluir o solicitação. Assim, o proxy retornou uma página de erro HTML ao meu cliente, confundindo o heck-out do cliente que esperava apenas dados XML.
Assim, mesmo quando você pensa que controla as duas extremidades do cabo, você pode obter formatos de dados inesperados sem envolver nenhuma vilania. Esteja preparado, codifique na defensiva e forneça saída de diagnóstico em caso de entrada inesperada.
fonte
Eu tento usar a abordagem Design por contrato. Pode ser emulado em tempo de execução por qualquer idioma. Cada idioma suporta "afirmar", mas é fácil e conveniente escrever uma implementação melhor que permita gerenciar o erro de uma maneira mais útil.
Nos 25 erros de programação mais perigosos, a "Validação incorreta de entrada" é o erro mais perigoso na seção "Interação insegura entre componentes".
Adicionar declarações de pré-condição no início dos métodos é uma boa maneira de garantir que os parâmetros sejam consistentes. No final dos métodos, escrevo pós-condições , que verificam se a saída é o que se pretende ser.
Para implementar invariantes , escrevo um método em qualquer classe que verifique a "consistência da classe", que deve ser chamada automaticamente por macro de pré-condição e pós-condição.
Estou avaliando a Biblioteca de contratos de código .
fonte
Java
A java api não tem conceito de objetos imutáveis, o que é ruim! Final pode ajudá-lo nesse caso. Marque todas as classes imutáveis com final e prepare-a de acordo .
Às vezes, é útil usar variáveis finais locais para garantir que elas nunca alterem seu valor. Achei isso útil em construções de loop feias, mas necessárias. É muito fácil reutilizar acidentalmente uma variável, mesmo que seja uma constante.
Use cópias de defesa em seus jogadores. A menos que você retorne um tipo primitivo ou um objeto imutável, certifique-se de copiar o objeto para não violar o encapsulamento.
Nunca use clone, use um construtor de cópias .
Aprenda o contrato entre iguais e hashCode. Isso é violado com tanta frequência. O problema é que isso não afeta seu código em 99% dos casos. As pessoas substituem iguais, mas não se importam com o hashCode. Há casos em que seu código pode quebrar ou se comportar de forma estranha, por exemplo, usar objetos mutáveis como chaves em um mapa.
fonte
Eu esqueci de escrever
echo
em PHP muitas vezes:Levaria uma eternidade para tentar descobrir por que -> baz () não estava retornando nada quando, na verdade, eu simplesmente não estava ecoando! : -S Então criei uma
EchoMe
classe que poderia ser agrupada em torno de qualquer valor que devesse ser ecoado:E então, para o ambiente de desenvolvimento, usei um EchoMe para embrulhar as coisas que deveriam ser impressas:
Usando essa técnica, o primeiro exemplo que faltava
echo
agora lançaria uma exceção ...fonte
AnObject.ToString
vez deWriteln(AnObject.ToString)
?C ++
Quando digito novo, devo digitar imediatamente excluir. Especialmente para matrizes.
C #
Verifique se há nulo antes de acessar propriedades, especialmente ao usar o padrão Mediador. Os objetos são passados (e, em seguida, devem ser convertidos usando como, como já foi observado) e depois verificados como nulos. Mesmo se você acha que não será nulo, verifique assim mesmo. Eu fiquei surpreso.
fonte
Use um sistema de registro que permita ajustes dinâmicos no nível do registro em tempo de execução. Freqüentemente, se você precisar interromper um programa para habilitar o log, perderá qualquer estado raro em que o bug ocorreu. É necessário poder ativar mais informações de log sem interromper o processo.
Além disso, 'strace -p [pid]' no linux mostrará que você deseja que o sistema chame um processo (ou thread do linux). Pode parecer estranho no começo, mas depois que você se acostumar com o que as chamadas do sistema geralmente são feitas pelo libc, você achará isso inestimável no diagnóstico de campo.
fonte