Pergunta

Possíveis Duplicados:
Programação defensivas

Nós tivemos um grande debate desta manhã sobre o assunto de programação defensivas.Tivemos uma revisão de código onde um ponteiro foi passado e não foi verificado se foi válido.

Algumas pessoas achavam que só um cheque de ponteiro nulo foi necessário.Questionado se poderia ser verificada em um nível mais alto, em vez de a cada método é transmitido, e que verificar nulo foi muito limitada, verifique se o objeto no outro extremo, a ponto de não atender a certos requisitos.

Eu entendo e concordo que a seleção para null é melhor do que nada, mas parece-me que apenas verificando nulo fornece uma falsa sensação de segurança, já que ele é limitado em escopo.Se você quer garantir que o ponteiro é utilizável, verifique mais do que nulo.

Quais são suas experiências sobre o assunto?Como você escrever defesas no seu código para os parâmetros que são passados para subordinada métodos?

Foi útil?

Solução

No Código de Completar 2, no capítulo sobre tratamento de erros, foi introduzida a idéia de barricadas.Em essência, uma barricada é o código que rigorosamente valida todas entrada vindo para ele.O código dentro da barricada pode-se assumir que qualquer entrada inválida já foi tratada, e que os insumos recebidos são boas.Dentro da barricada, o código só precisa se preocupar com dados inválidos passada para ele por outros de código dentro da barricada.Afirmando condições e criteriosos testes de unidade pode aumentar a sua confiança no barricado código.Desta forma, você programa muito defensivamente na barricada, mas menos dentro da barricada.Outra maneira de pensar sobre isso é que na barricada, você sempre lidar com os erros corretamente e dentro da barricada, simplesmente, afirmar as condições na sua compilação de depuração.

Como longe como a utilização de matérias ponteiros vai, normalmente o melhor que você pode fazer é afirmar que o ponteiro não é nulo.Se você sabe o que é suposto ser, em que a memória, em seguida, você pode garantir que os conteúdos são consistentes, de alguma forma.Isso levanta a questão de por que a memória não está embrulhado em um objeto que pode verificar a sua consistência própria.

Então, por que você está usando um ponteiro bruto neste caso?Seria melhor usar uma referência ou um ponteiro inteligente?O ponteiro contêm dados numéricos, e se assim for, seria melhor para envolvê-lo em um objeto que conseguiu o ciclo de vida do ponteiro?

A resposta a estas perguntas podem ajudar você a encontrar uma forma de ser mais defensivo, em que você vai acabar com um design que é mais fácil de defender.

Outras dicas

A melhor maneira de estar na defensiva não é para verificar ponteiros nulos em tempo de execução, mas para evitar o uso de ponteiros, que pode ser nula para começar

Se o objeto que está sendo transmitido não deve ser nula, use uma referência!Ou passá-lo por valor!Ou usar um ponteiro inteligente de algum tipo.

A melhor maneira de fazer defensiva de programação é para pegar seus erros em tempo de compilação.Se ele é considerado um erro para um objeto a ser nulo ou aponte para lixo, em seguida, você deve fazer as coisas erros de compilação.

Em última análise, você não tem nenhuma maneira de saber se um ponteiro aponta para um objeto válido.Assim, em vez de verificação para um específicas de canto (que é muito menos comum do que o realmente perigosas, ponteiros apontando para objetos inválidos), fazer com que o erro impossível usando um tipo de dados que garante a validade.

Eu não consigo pensar em outro idioma principal que permite a você pegar como muitos erros em tempo de compilação como no C++.utilizar essa capacidade.

Não há nenhuma maneira de verificar se um ponteiro é válido.

Em todos os graves, depende de quantos erros você gostaria de ter infligido sobre você.

A verificação de um ponteiro nulo é definitivamente algo que eu consideraria necessário, mas não suficiente.Há uma abundância de outros princípios sólidos, você pode usar começando com pontos de entrada de seu código (por exemplo, validação de entrada de = faz que o ponteiro aponte para algo de útil) e pontos de saída (por exemplo, você pensou que o ponteiro apontado para algo de útil, mas isso aconteceu para fazer com que o código para lançar uma exceção).

Em suma, se você assumir que todo mundo chamando seu código vai fazer o seu melhor para arruinar a sua vida, você provavelmente vai encontrar um monte de piores culpados.

EDITAR para maior clareza:algumas outras respostas estão falando sobre testes de unidade.Eu acredito firmemente que o código de teste é, por vezes, mais valioso do que o código que é teste (dependendo de quem está medindo o valor).O que disse, eu também acho que as unidades de testes também necessária, mas não suficiente para a defensiva de codificação.

Exemplo concreto:considere uma festa de 3 método de pesquisa que está documentado para devolver um conjunto de valores que correspondem à sua solicitação.Infelizmente, o que não era claro na documentação para esse método é que o desenvolvedor original decidimos que seria melhor para retornar um valor nulo, em vez de uma coleção vazia se nada de correspondência de seu pedido.

Então, agora, chamar a sua defensiva e também a unidade testada método de pensamento (que, infelizmente, a falta de uma internos de verificação de ponteiro nulo) e boom!NullPointerException que, sem uma verificação interna, você não tem nenhuma maneira de lidar com:

defensiveMethod(thirdPartySearch("Nothing matches me")); 
// You just passed a null to your own code.

Eu sou um grande fã do "deixa crash" da escola de design.(Isenção de responsabilidade:Eu não trabalho em equipamentos médicos, produtos electrónicos ou de energia nuclear-software relacionado.) Se o seu programa de golpes para cima, o fogo até o depurador e descobrir o porquê.Em contraste, se o seu programa continua a ser executado depois de ilegal parâmetros têm sido detectados até o momento da falha você provavelmente não tem idéia do que deu errado.

Bom código é composto de muitas pequenas funções/métodos, e a adição de uma dúzia de linhas de parâmetro de verificação para cada um desses trechos de código torna-se mais difícil de ler e mais difícil de manter.Mantê-lo simples.

Eu posso ser um pouco extremo, mas eu não gosto de Programação Defensivas, eu acho que é a preguiça, que introduziu o princípio.

Para esse exemplo específico, não há sentido em afirmar que o ponteiro não é nulo.Se você quer um ponteiro nulo, não há melhor maneira de realmente fazer cumprir (e documentar claramente ao mesmo tempo) do que usar uma referência em vez disso.E é a documentação que vai realmente ser imposta pelo compilador e não custa um ziltch em tempo de execução!!

Em geral, eu tendem a não usar 'raw' tipos diretamente.Vamos ilustrar:

void myFunction(std::string const& foo, std::string const& bar);

Quais são os possíveis valores de foo e bar ?Bem, isso é bastante limitado apenas pelo que um std::string pode conter...o que é muito vago.

Por outro lado:

void myFunction(Foo const& foo, Bar const& bar);

é muito melhor!

  • se as pessoas, equivocadamente, inverter a ordem dos argumentos, é detectado pelo compilador
  • cada classe é responsável por verificar que o valor é certa, os usuários não são burdenned.

Eu tenho uma tendência a favor de Tipagem Forte.Se eu tiver uma entrada que deve ser composto somente de caracteres alfabéticos e ser de até 12 caracteres, prefiro criar uma pequena classe de moldagem de um std::string, com um simples validate método usado internamente para verificar as atribuições e passar essa classe de em torno de em vez disso.Desta forma, eu sei que se eu testar a rotina de validação de uma VEZ, eu não precisa se preocupar com todos os caminhos através de qual valor pode chegar até mim > será validada quando ele chega até mim.

Claro, que não me de que o código não deve ser testada.É só que eu sou a favor forte de encapsulamento, e a validação de uma entrada é parte do conhecimento de encapsulamento na minha opinião.

E como não há regra sem excepção...exposto interface é necessariamente inchado com o código de validação, porque você nunca sabe o que pode vir em cima de você.No entanto, com a auto-validação de objetos em sua lista de materiais é bastante transparente em geral.

"Os testes de unidade de verificar o código faz o que deve fazer" > "código de produção, tentando verificar se a sua não está fazendo o que não deve fazer".

Eu mesmo não iria verificar nulo-me, a menos que a sua parte da API publicado.

Isso depende muito;é o método em questão sempre chamado por código externo para o seu grupo, ou é um método interno?

Para métodos internos, você pode testar o suficiente para tornar este um ponto discutível, e se você estiver criando um código onde o objetivo é mais alto desempenho possível, você não pode querer gastar o tempo em verificar os insumos que são muito danado certeza de que está certo.

Externamente visível métodos - se você tiver alguma - você deve sempre verifique seus dados.Sempre.

A partir de depuração ponto de vista, é mais importante que o seu código é fail-fast.O anterior, o código de falha, o mais fácil de encontrar o ponto de falha.

Para métodos internos, que geralmente vara afirma para esses tipos de verificações.Que não se erros apanhados em testes de unidade (você tem boa cobertura do teste, certo?) ou pelo menos em testes de integração que estão em execução com afirmações sobre.

verificação de ponteiro nulo é apenas a metade da história, você deve também atribuir um valor nulo para todos os não atribuídos ponteiro.
o maior responsável API irá fazer o mesmo.
a verificação de um ponteiro null é muito barato em ciclos de CPU, a necessidade de um aplicativo de bater uma vez entregue pode custar a você e sua empresa no dinheiro e reputação.

você pode pular o ponteiro null verifica se o código está em uma interface privada você tem controle completo e/ou verificar nulo, a execução de um teste de unidade ou alguns compilação de depuração de teste (e.g.assert)

Existem algumas coisas no trabalho aqui nesta pergunta que eu gostaria de abordar:

  1. Diretrizes de codificação deve especificar que você quer lidar com uma referência ou um valor diretamente em vez de usar ponteiros.Por definição, os ponteiros são tipos de valor que apenas segurar um endereço na memória -- validade de um ponteiro é uma plataforma específica e significa muitas coisas (intervalo de memória endereçável, plataforma, etc.)
  2. Se você encontrar-se nunca precisar de um ponteiro, por qualquer motivo (como, por gerados dinamicamente e objetos polimórficos) considere o uso de ponteiros inteligentes.Ponteiros inteligentes dar-lhe muitas vantagens com a semântica de "normal" ponteiros.
  3. Se um tipo, por exemplo, tem um "inválido" estado, em seguida, o próprio tipo deve fornecer para isso.Mais especificamente, você pode implementar o NullObject padrão que especifica como um "mal definidos" ou "onu inicializada" objeto se comporta (talvez por gerar exceções ou fornecimento não-op funções de membro).

Você pode criar um ponteiro inteligente que faz o NullObject padrão parecido com este:

template <class Type, class NullTypeDefault>
struct possibly_null_ptr {
  possibly_null_ptr() : p(new NullTypeDefault) {}
  possibly_null_ptr(Type* p_) : p(p_) {}
  Type * operator->() { return p.get(); }
  ~possibly_null_ptr() {}
  private:
    shared_ptr<Type> p;
    friend template<class T, class N> Type & operator*(possibly_null_ptr<T,N>&);
};

template <class Type, class NullTypeDefault>
Type & operator*(possibly_null_ptr<Type,NullTypeDefault> & p) {
  return *p.p;
}

Em seguida, use o possibly_null_ptr<> modelo no casos em que você suporte possivelmente nulo ponteiros para tipos que têm um padrão derivado de "nulo comportamento".Isto torna explícita a concepção de que não é um comportamento aceitável para "objetos nulos", e isso faz com que sua prática defensiva documentada no código -- e mais concreto -- do que uma diretriz geral ou prática.

O ponteiro só deve ser usado se você precisa fazer algo com o ponteiro.Como o ponteiro de aritmética para transversa de alguma estrutura de dados.Em seguida, se possível, que deve ser encapsulado em uma classe.

SE o ponteiro é passado para a função de fazer algo com o objeto para o qual ele aponta, em seguida, passar uma referência em vez disso.

Um método para programação defensivas é a afirmação de que quase tudo que você pode.No início do projeto, ele é chato, mas mais tarde ele é um bom complemento para o teste de unidade.

Um número de respostas abordar a questão de como escrever defesas no seu código, mas não muito foi dito sobre "como defensiva que você deve ser?".Isso é algo que você tem que avaliar com base na criticidade de seus componentes de software.

Estamos fazendo um voo de software e os impactos de um erro de software de gama a partir de um aborrecimento menor a perda da aeronave, da tripulação.Nós categorizar diferentes peças de software com base no seu potencial de impactos adversos que afeta padrões de codificação, teste, etc.Você precisa avaliar como o software vai ser utilizado e os impactos de erros e definir o nível de resistência que você quer (e pode).O DO-178B padrão chama isso de "Design de Nível de Garantia".

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top