Práticas recomendadas (inteligentes) de programação defensiva favorita [fechado]


148

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 de if(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 finalaté 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 catchbloco 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 .


Gostaria de representar aqui os 30% ignorantes de nós que talvez não conheçam as técnicas simples . Alguém tem um link para as técnicas "óbvias" que todos deveriam conhecer como base?
elliot42


Por que você escolheria uma resposta oficial? Isso parece totalmente inábil.
bzlm

Bem, quando se trata de programação, até a inteligência deve ficar em segundo plano em homenagem à "regra do mínimo espanto". Para mim, romper com o espírito de Stack Overflow de marcar a melhor resposta violaria o protocolo Stack Overflow (quebrando a regra). Afora isso: eu gosto de fechamento :-)
Ryan Delucchi

Respostas:


103

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 :

Evite programação defensiva, falhe rapidamente.

Por programação defensiva, quero dizer o hábito de escrever código que tenta compensar alguma falha nos dados, de escrever código que pressupõe que os chamadores possam fornecer dados que não estejam em conformidade com o contrato entre o chamador e a sub-rotina e que a sub-rotina deve, de alguma forma, lidar com com isso.


8
A programação defensiva está tentando lidar com condições ilegais introduzidas por outras partes de um programa. Lidar com entrada incorreta do usuário é algo completamente diferente.
31510 Joe Soul-bringer

5
Errr ... observe que esta definição de programação defensiva nem sequer é próxima da definição implicitamente usada na pergunta.
Sol

15
Nunca evite a programação defensiva. A coisa não é "compensar" as falhas nos dados, mas proteger-se dos dados maliciosos projetados para fazer seu código fazer o que não deveria. Consulte Estouro de Buffer, Injeção SQL. Nada falha mais rápido do que uma página web sob XSS, mas não é bonito
Jorge Córdoba

6
Eu argumentaria que os procedimentos de "falha rápida" são uma forma de programação defensiva.
21909 Ryan Delucchi

6
@ryan está exatamente certo, falhar rápido é um bom conceito defensivo. Se o estado em que você se encontra não for possível, não tente continuar mancando, FAÇA RAPIDAMENTE E RUIDOSAMENTE! Mais importante se você é direcionado por metadados. A programação defensiva não é apenas verificar seus parâmetros ...
Bill K

75

SQL

Quando tenho que excluir dados, escrevo

select *    
--delete    
From mytable    
Where ...

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 *


Eu escrevi isso no meu iPhone, onde não consigo selecionar o texto. Se alguém pudesse marcar meu código como código, seria muito apreciado. Obrigado.
John MacIntyre

6
Eu apenas envolvê-lo em uma transação para que eu possa rolar para trás se eu estragar tudo ...
rmeador

36
COMEÇAR A TRANSAÇÃO | ROLLBACK TRANSACTION
Dalin Seivewright

3
+1 Sim, suponho que isso possa ser substituído por uma transação, mas eu gosto da simplicidade de fazê-lo dessa maneira.
21911 Ryan Delucchi

3
Usar uma transação é melhor; significa que você pode executá-lo dezenas de vezes e ver os efeitos reais , até ter certeza de que acertou e pode confirmar.
quer

48

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:

  • Salve todos os dados persistentes
  • Feche todos os arquivos apropriados
  • Grave mensagens de erro em um arquivo de log
  • Apresentar um erro significativo ao usuário

Eu já vi isso chamado fundo de dia chuvoso.
291 plinth

42

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.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}

2
Ou um lance em uma linguagem moderna fangled.
Tom Hawtin - tackline

2
A declaração tem a vantagem de que seus efeitos podem ser desativados globalmente em tempo de compilação. No entanto, em algumas situações, o arremesso pode ser mais apropriado, se o seu idioma suportar.
Diomidis Spinellis

2
A declaração tem outra vantagem que a torna útil para o código de produção: quando algo dá errado, informa exatamente o que falhou e qual linha do programa o erro ocorreu. Um relatório de erro como esse é maravilhoso!
Mason Wheeler

2
@ Diomidis: Outro ângulo é: Assert tem a desvantagem de que seus efeitos podem ser desativados globalmente em tempo de compilação.
Desiludido

1
jogar é realmente melhor. E então você garante que sua cobertura de teste seja adequada.
Dominic Cronin

41

Ao lidar com os vários estados de uma enumeração (C #):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Então, dentro de alguma rotina ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

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.Failfalhas são horríveis (no modo Debug) para chamar minha atenção para esse fato. Quando adiciono o case 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.)


4
A maioria dos compiladores é inteligente o suficiente para emitir um aviso se você não manipular algumas enumerações em um bloco de caso. Mas definir o padrão como falha ainda é uma boa forma - uma enumeração é apenas um número e, se você receber corrupção de memória, poderá aparecer um valor inválido.
Adam Hawes

em c, eu costumava adicionar um invalidAccountType no final. isso é útil às vezes.
Ray Tayek

1
@ Adam - os compiladores emitem um aviso se você recompilar tudo . Se você adicionar novas classes e recompilar apenas parcialmente, poderá não notar algo como o descrito acima, e o caso padrão o salvará. Não falhará silenciosamente.
Eddie

4
A quebra está implícita nos comentários, Slashene. : P
Ryan Lundy

2
Após a depuração, deve ser 'throw new NotSupportedException ()' para o código de produção.
user7116

35

Ao imprimir mensagens de erro com uma sequência (principalmente uma que depende da entrada do usuário), sempre uso aspas simples ''. Por exemplo:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

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:

ERROR: Could not open file

Então, sempre melhor fazer:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Então, pelo menos, o usuário vê isso:

ERROR: Could not open file ''

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".


4
bom, eu já vi esse problema também #
Alex Baranosky 08/10/09

28

Segurança SQL

Antes de escrever qualquer SQL que modifique os dados, envolvo tudo em uma transação revertida:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

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 SELECTinstruções entre o SQL e o ROLLBACK TRANSACTIONpara garantir que tudo esteja correto.

Quando tiver certeza absoluta de que faz o que esperava, altere o ROLLBACKpara COMMITe corra de verdade.


Você me venceu com esse soco! :-)
Howard Pinsley

2
Eu costumava fazer isso o tempo todo, mas isso causa uma sobrecarga extra nos clusters de banco de dados que eu tive que parar.
Zan Lynx

25

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.


5
O que significa exatamente a parte de evitar? Às vezes, introduzo variáveis ​​que só vivem até a próxima linha. Eles servem como um nome para uma expressão, o que torna o código mais legível.
zoul

4
Sim, eu também discordo. Com expressões muito complexas, geralmente é uma boa ideia dividi-las em duas ou, às vezes, mais curtas e simples, usando variáveis ​​temporárias. É menos propenso a erros na manutenção e o compilador otimizará as temperaturas
Cruachan

1
Concordo que há casos excepcionais em que declaro variáveis ​​para maior clareza (e às vezes para criar um destino para depuração). Minha experiência é que a prática geral é errar na direção oposta.
dkretz

Eu concordo com os dois pontos de vista; embora, quando divido expressões em variáveis ​​temporárias, tente fazê-lo em um escopo separado. Lambdas são ótimos para isso, assim como os métodos auxiliares.
Erik Forbes

19

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).


E é claro: use código genérico (uma pequena biblioteca, métodos de extensão em C #, qualquer que seja) para facilitar isso. Então você pode escrever somethin glike param.NotNull("param")em vez deif ( param == null ) throw new ArgumentNullException("param");
peSHIr

2
Ou use programação baseada em contrato, como Spec #!
bzlm

Depende de qual aplicativo é. Espero que as pessoas que escrevem código para aeronaves fly-by-wire e marcapassos não pensem como peSHIr.
MarkJ

6
@ MarkJ: Você realmente não entende, não é? Se bombardear cedo (= durante o desenvolvimento e o teste), nunca deverá bombardear quando estiver em produção. Então, eu realmente espero que eles façam programas como este!
5609 peSHIr

Devo admitir que não gosto muito disso, especialmente para métodos privados e protegidos. Razões: a) você confunde o código com verificações que não se assemelham aos requisitos de negócios b) essas verificações são difíceis de testar para métodos não públicos c) é inútil em muitos casos, por exemplo, uma vez que um valor nulo fará com que o método falhe de qualquer maneira duas linhas depois
Erich Kitzmueller 30/09/10

18

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:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Não permita que nada escape da sua classe que você não precisa!


+1 Em C #, existem coleções somente para isso.
tobsen

1
+1 para Java. Eu uso isso o tempo todo
Fortyrunner

+1 ... Eu faço muito isso (embora eu queira que mais pessoas se lembrem de fazer isso).
Ryan Delucchi

Apenas certifique-se nada mais tem uma instância da variável de lista subjacente, ou isso não vai fazer muito bem ...
GreenieMeanie

5
FYI, Collections.unmodifiableList retorna uma visualização imutável da lista, não uma cópia imutável . Portanto, se a lista original for modificada, a visualização também será!
Zarkonnen

17

em Perl, todo mundo faz

use warnings;

Eu gosto

use warnings FATAL => 'all';

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.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here

Queremos que isso foi um pouco mais anunciado ...
DJG

16

C #:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}

O mesmo em Java e provavelmente na maioria das linguagens OO.
MiniQuark

Isso é bom no Objective-C, onde é possível enviar mensagens para zero ("chamar métodos de um objeto nulo", com uma certa licença). A chamada [nil isEqualToString: @ "Moo"] retorna false.
zoul

Eu discordo do exemplo de c #. Uma solução melhor é usar "if (myString ==" someValue ")". Também não há exceção de referência nula, e certamente mais legível.
Dan C.

13
este exemplo está apenas permitindo que você oculte uma situação potencialmente perigosa em seu programa. Se você não esperava que fosse nulo, QUER lançar uma exceção e, se esperava que fosse nulo, deve lidar com isso como tal. Esta é uma má prática.
rmeador

2
Em C #, eu sempre uso apenas string.Equals (<string1>, <string2>). Não importa se um deles é nulo.
Darcy Casselman

15

Na verificação de c # da string.IsNullOrEmpty antes de executar qualquer operação na string, como length, indexOf, mid etc

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[Editar]
Agora também posso fazer o seguinte no .NET 4.0, que verifica adicionalmente se o valor é apenas espaço em branco

string.IsNullOrWhiteSpace(myString)

7
Isso não é ser defensivo, é ignorar o problema. Deveria ser if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
9602 enashnash

14

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.


E - no Windows - para C ++ também! (ativado com lançamento de exceção SEH especial e após captura).
Brian Haak

12

Com o VB.NET, as opções Explicit e Option Strict estão ativadas por padrão para todo o Visual Studio.


Embora eu esteja trabalhando com código mais antigo, por isso não tenho a opção estrita ativada (causa muitos erros de compilação para corrigir), tendo essas duas opções ativadas pode (e teria no meu caso) economizado muito dor.
Kibbee

1
A propósito, para quem converte código antigo que não usou a Option Strict para usá-lo, observe que no caso de itens que foram convertidos automaticamente em String, eles não estão usando ToString (); eles estão usando um elenco para string. Nos meus primeiros dias do .NET, eu os alterava para usar ToString (), e isso quebraria as coisas, especialmente com enumerações.
Ryan Lundy

10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

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.


1
Melhor usar um modelo. É escopo e sobrecarregável.

Eu estava prestes a dizer isso. Use um modelo em vez de uma macro. Dessa forma, se você precisar percorrer o código, poderá.
9788 Steve Rowe

Aprendi o que era mais fácil de depurar da maneira mais difícil na escola. Esse é um erro que não cometi desde então. :)
Greg D

3
Ou use Ponteiros Inteligentes ... Ou, evite novo / exclua o máximo que puder.
Arafangion

1
@ Arafangion: apenas evite delete. O uso newé bom, desde que o novo objeto seja de propriedade de um ponteiro inteligente.
Alexandre C.

10

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:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

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.


No .NET, Debug.Assert faz a mesma coisa. Sempre que você tem um lugar onde você pensa "Esta referência não pode ser nulo aqui, certo?", Você pode colocar um Debug.Assert lá, de modo que se pode ser nulo, você pode corrigir o erro ou modificar suas suposições.
Ryan Lundy 29/01

1
+1, métodos públicos devem jogar em falhas de contrato, privates deve afirmar
user7116

9

Não encontrei a readonlypalavra - chave até encontrar o ReSharper, mas agora a uso instintivamente, especialmente para as classes de serviço.

readonly var prodSVC = new ProductService();

Eu uso a palavra-chave 'final' equivalente do Java em todos os campos que posso. Realmente evita que você faça movimentos de cabeça para baixo, como não definir um campo / escrever opções confusas que podem definir campos várias vezes. É menos provável que eu marque variáveis ​​/ parâmetros locais, mas acho que não poderia machucar.
Outlaw Programmer

Faz C # não permite que você marque as variáveis locais como somente leitura, de modo que nem sequer tem a opção ...
Jason Punyon

Não sei o que significa apenas leitura aqui, mas a final de Java não é suficiente para mim. Significa apenas que o ponteiro para um objeto não é alterado, mas não há como impedir alterações no próprio objeto.
Slartibartfast

Em C #, uma estrutura somente leitura impediria que ela fosse alterada.
Samuel

9

Em Java, quando algo está acontecendo e não sei por que, às vezes usarei o Log4J assim:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

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.


Hmm, isso é meio legal, mas estou preocupado que causaria alguma mistura de código funcional e de tratamento de erros. Embora o mecanismo try-catch pode estar no lado desajeitado, ele força um para fazer a execução exceção reroute de um bloco (tentativa) para outro (captura), que é onde toda a manipulação o erro é
Ryan Delucchi

1
Isso não é usado para manipulação de erros, mas apenas para diagnósticos . Isso permite que você veja o caminho pelo qual seu código entrou em uma situação inesperada sem interromper o fluxo do código. Eu uso isso quando o próprio método pode lidar com a situação inesperada, mas ainda não deve acontecer.
Eddie

2
você também pode usar a nova exceção ("mensagem"). printStackTrace (); Não é necessário jogar ou capturar, mas você ainda recebe um bom rastreamento de pilha no log. Obviamente, isso não deve estar no código de produção, mas pode ser muito útil para depuração.
Jorn

9

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:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}

para Java, esta é a classe Foo {void doSomething () {}} classe Bar {@Override void doSomething () {}} emitirá um aviso se estiver faltando a anotação (para que você não possa substituir silenciosamente um método que não o fez) significa que) e quando a anotação está lá, mas ela não substitui nada.
Jorn

Bom ... eu não sabia sobre este ... muito ruim parece ser Microsoft específico ... mas alguns bons #defines pode ajudar a torná-lo portátil
e.tadeu

8

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.


1
espera com tempos limite usados ​​como este é um sinal de outros problemas piores.
dicroce

2
Em um sistema que precisa ter um tempo de atividade alto, é melhor obter pelo menos as informações de diagnóstico para que você possa encontrar o problema. Às vezes, você prefere sair de um encadeamento ou retornar uma resposta ao chamador quando o sistema estiver em um estado conhecido; portanto, espere um semáforo. Mas você não quer pendurar para sempre
Eddie

8

C #

  • Verifique valores não nulos para parâmetros do tipo de referência no método público.
  • Uso sealedmuito 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.

8

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.


1
Ao escrever o código para uma mensagem de erro, leia a mensagem e pergunte o que você gostaria de saber a seguir e adicione-o. Repita até que não seja razoável. Por exemplo: "Fora do intervalo". O que está fora de alcance? "Foo conta fora de alcance." Qual foi o valor? "Contagem de Foo (42) fora de alcance." Qual é o alcance? "Contagem de Foo (42) fora do intervalo (549 a 666)."
HABO

7

Em C #, use a aspalavra-chave para transmitir.

string a = (string)obj

lançará uma exceção se obj não for uma string

string a = obj as 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)objsintaxe é preferida.

No meu próprio código, acho que uso a assintaxe cerca de 75% das vezes e a (cast)sintaxe cerca de 25%.


Correto, mas agora você precisa verificar se é nulo antes de usar a referência.
Brian Rasmussen

7
Não entendi. Parece uma decisão ruim para mim, preferir o nulo. Você terá problemas em algum lugar durante o tempo de execução, sem nenhuma dica do motivo original.
Kai Huppmann

Sim. Isso só é útil se você realmente desejar nulo quando não for do tipo correto. Útil em alguns casos: no Silverlight, onde a lógica de controle e o design são frequentemente separados, a lógica deseja usar o controle "Para cima" apenas se for um botão. Caso contrário, é como se não existisse (= nulo).
Sander

2
Isso parece tão defensivo quanto as grandes janelas do salão de uma fortaleza. Mas é uma vista adorável!
Pontus Gagge

1
Isso me lembra alguém que não usou exceções porque 'eles interromperam programas'. Se você deseja um determinado comportamento quando um objeto não é uma classe que você espera, acho que codificaria de outra maneira. is/instanceofvem à mente.
Kirschstein

6

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.


6

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 .


6

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.


5

Eu esqueci de escrever echoem PHP muitas vezes:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

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 EchoMeclasse que poderia ser agrupada em torno de qualquer valor que devesse ser ecoado:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

E então, para o ambiente de desenvolvimento, usei um EchoMe para embrulhar as coisas que deveriam ser impressas:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Usando essa técnica, o primeiro exemplo que faltava echoagora lançaria uma exceção ...


O destruidor não deve procurar $ this-> impresso! == true?
291 Karsten

Você deve considerar o uso de um sistema de modelos, a incorporação de php em html é sub-ideal em quase todos os sistemas.
Cruachan

Talvez esteja faltando alguma coisa? Mas parece-me que isso está tentando compensar a codificação: em AnObject.ToStringvez de Writeln(AnObject.ToString)?
Desiludido

Sim, mas o erro é muito mais fácil de fazer em PHP
muito php

4

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.


Eu gosto do seu primeiro ponto. Faço coisas semelhantes quando, por exemplo, escrevendo um método que retorna uma coleção de algo. Crio a coleção na primeira linha e escrevo imediatamente a declaração de retorno. Tudo o que resta é preencher como a coleção é preenchida.
Outlaw Programmer

5
No C ++, quando você digita new, deve atribuir imediatamente esse ponteiro a um AutoPtr ou a um contador contado por referência. C ++ possui destruidores e modelos; use-os com sabedoria para lidar com a exclusão automaticamente.
Andrew

4

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.

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.