Pergunta

Eu tenho sido um engenheiro de software profissional para cerca de um ano, tendo se formado com um grau CS. Conheço sobre afirmações por um tempo em C ++ e C, mas não tinha idéia que existia em C # e .NET em tudo até recentemente.

O nosso código de produção não contém afirma qualquer e minha pergunta é esta ...

Devo começar a usar Afirma no nosso código de produção? E em caso afirmativo, quando é o seu uso mais adequado? Faria mais sentido fazer

Debug.Assert(val != null);

ou

if ( val == null )
    throw new exception();
Foi útil?

Solução

Na depuração Microsoft .NET 2.0 Aplicações John Robbins tem uma grande seção sobre afirmações. Seus principais pontos são:

  1. Assert liberalmente. Você nunca pode ter muitas afirmações.
  2. As afirmações não substituem exceções. Exceções cobrir as coisas que seu código exige; afirmações cobrir as coisas que ele assume.
  3. A afirmação bem escrito pode dizer não apenas o que aconteceu e onde (como uma exceção), mas por que.
  4. Uma mensagem de exceção pode muitas vezes ser enigmática, exigindo-lhe para trás de trabalho através do código para recriar o contexto que causou o erro. Uma afirmação pode preservar o estado do programa no momento da ocorrência do erro.
  5. As afirmações dupla como documentação, dizendo outros desenvolvedores que implícitas suposições seu código depende.
  6. A caixa de diálogo que aparece quando uma afirmação falhar permite anexar um depurador para o processo, para que possa picar ao redor da pilha como se você tivesse colocado um ponto de interrupção lá.

PS: Se você gostou Code Complete, eu recomendo seguir-se com este livro. Comprei-o para aprender sobre o uso de arquivos WINDBG e despejo, mas a primeira metade é embalado com dicas para erros ajudam a evitar em primeiro lugar.

Outras dicas

Coloque Debug.Assert() em todo o código onde você quer ter checagens para garantir invariantes. Quando você compilar uma compilação de lançamento (ou seja, nenhum DEBUG constante do compilador), as chamadas para Debug.Assert() serão removidos para que eles não afetará o desempenho.

Você ainda deve lançar exceções antes de chamar Debug.Assert(). O assert apenas garante que tudo é como esperado, enquanto você ainda está em desenvolvimento.

A partir código completo

8 Programação Defensiva

8.2 Afirmações

Uma afirmação é o código que é usado durante o desenvolvimento, geralmente uma rotina ou macro-que permite que um programa para verificar se como ele é executado. quando um afirmação é verdadeira, isso significa que tudo está funcionando como esperado. Quando é falsa, o que significa que foi detectado um erro inesperado na código. Por exemplo, se o sistema assume que um cliente-informação arquivo nunca terá mais de 50.000 registros, o poder programa conter uma afirmação de que o número de registos é lessthan ou igual a 50.000. Enquanto o número de registros é menor ou igual a 50.000, a afirmação vai ficar em silêncio. Se ele encontrar mais de 50.000 registros, no entanto, será bem alto “assert” que há uma erro no programa.

As afirmações são especialmente úteis em grande, programas complicados e em programas de alta confiabilidade. Eles permitem aos programadores mais rapidamente expulsar os pressupostos de interface incompatíveis, erros que se arrastam em quando código é modificado, e assim por diante.

Uma afirmação geralmente leva dois argumentos: a expressão booleana que descreve a suposição de que é suposto ser verdadeiro e uma mensagem para exibir se não é.

(...)

Normalmente, você não quer que os usuários vejam mensagens de declaração em código de produção; afirmações são principalmente para uso durante o desenvolvimento e manutenção. Afirmações são normalmente compilada para o código em tempo de desenvolvimento e compilado fora do código para a produção. Durante desenvolvimento, afirmações expulsar premissas contraditórias, condições inesperadas, maus valores passados ??para rotinas, e assim por diante. Durante a produção, eles são compilados fora do código para que o afirmações fazer o desempenho do sistema não degrade.

FWIW ... Acho que os meus métodos públicos tendem a usar o padrão if () { throw; } para garantir que o método está sendo chamado corretamente. Meus métodos privados tendem a usar Debug.Assert().

A idéia é que com os meus métodos privados, eu sou o único sob controle, então se eu começar a chamar um dos meus próprios métodos privados com os parâmetros que estão incorretas, então eu quebrei minha própria em algum lugar suposição - que deveria nunca ter chegado a esse estado. Na produção, estes privada afirma idealmente deve ser um trabalho desnecessário, já que eu deveria estar mantendo meu estado interno válido e consistente. Contraste com parâmetros dados a métodos públicos, que poderiam ser chamados por qualquer pessoa em tempo de execução:. Eu ainda preciso impor restrições de parâmetro lá jogando exceções

Além disso, os meus métodos privados ainda pode lançar exceções se algo não funciona em tempo de execução (erro de rede, erro de acesso de dados, dados ruins recuperados de um serviço de terceiros, etc.). Meu afirma estão lá apenas para se certificar de que eu não ter quebrado minhas próprias premissas internas sobre o estado do objeto.

Use afirma para verificar os pressupostos de desenvolvimento e exceções para verificar suposições ambientais.

Se eu fosse você eu faria:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

ou para evitar repetida verificação de condição

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}

Se você quiser Afirma em seu código de produção (ou seja, compilações Release), você pode usar Trace.Assert vez de Debug.Assert.

Isto, obviamente, adiciona sobrecarga para o executável de produção.

Além disso, se o seu aplicativo está sendo executado no modo de interface do usuário, o diálogo Assertion será exibida por padrão, o que pode ser uma desconcertante pouco para seus usuários.

Você pode substituir esse comportamento, removendo o DefaultTraceListener:. Olhada na documentação para Trace.Listeners em MSDN

Em resumo,

  • Use Debug.Assert liberalmente a ajuda erros de captura nas compilações de depuração.

  • Se você usar Trace.Assert no modo de interface do usuário, você provavelmente vai querer remover o DefaultTraceListener para evitar desconcertante usuários.

  • Se a condição que você está testando é algo que seu aplicativo não pode segurar, você é provavelmente melhor fora de lançar uma exceção, para garantir a execução não continua. Esteja ciente de que um usuário pode optar por ignorar uma afirmação.

afirma são usados ??para pegar programador (seu) de erro, não erro do usuário. Eles devem ser usados ??somente quando não há chance de um usuário poderia fazer com que o assert ao fogo. Se você estiver escrevendo uma API, por exemplo, afirma não deve ser utilizado para verificar se um argumento não é nulo em qualquer método de um usuário API poderia chamar. Mas poderia ser usado em um método particular não expostos como parte de sua API para afirmar que seu código nunca passa um argumento nulo quando não é suposto.

Eu geralmente favorecem exceções sobre afirma quando eu não tenho certeza.

Na maior parte nunca na minha livro. Na grande maioria das ocasiões, se você quiser verificar se está tudo sã então jogue se não é.

O que eu não gosto é o fato de que faz uma compilação de depuração funcionalmente diferente de uma compilação de lançamento. Se um assert depuração falhar mas a funcionalidade funciona na versão então como é que isso faz algum sentido? É ainda melhor quando o asserter há muito tempo deixou a empresa e ninguém sabe que parte do código. Então você tem que matar um pouco do seu tempo explorando a questão para ver se ele é realmente um problema ou não. Se for um problema, então por que não é a pessoa que joga em primeiro lugar?

Para mim, isso sugere usando Debug.Asserts você está adiando o problema para outra pessoa, lidar com o problema sozinho. Se algo é suposto ser o caso e não é então jogue.

Eu acho que existem possivelmente desempenho cenários críticos onde você deseja otimizar afastado seu assevera e eles são úteis lá, no entanto eu ainda estou para encontrar um tal cenário.

Em Breve

Asserts são usados ??para guardas e para verificar Design by restrições contrato, a saber:

  • Asserts deve ser para depuração e não-Produção constrói somente. Afirma normalmente são ignorados pelo compilador Nas compilações.
  • Asserts pode verificar se há bugs / condições inesperadas que estão no controle de seu sistema
  • Asserts não são um mecanismo de validação de primeira linha de regras de entrada do usuário ou de negócios
  • Asserts deve não ser usado para detectar condições ambientais inesperadas (que estão fora do controle do código) por exemplo sem memória, falha de rede, falha de banco de dados, etc. Embora raro, estas condições devem ser esperados (e seu código de aplicativo não pode corrigir problemas como falha de hardware ou esgotamento dos recursos). Normalmente, as exceções serão lançados -. A sua aplicação pode então tomar uma ação corretiva (por exemplo, repetir uma operação de banco de dados ou de rede, tente liberar memória cache) ou abort graciosamente se a exceção não pode ser tratada
  • A Falha na asserção deve ser fatal para seu sistema - ou seja, ao contrário de uma exceção, não tente e catch ou alça falhou Asserts - seu código está operando em território inesperado. Rastreamentos de pilha e despejos de memória pode ser usado para determinar o que deu errado.

As afirmações têm um enorme benefício:

  • Para ajudar a encontrar a validação falta de entradas do usuário, ou bugs a montante no código de nível superior.
  • Afirma na base de código transmitir claramente as suposições feitas no código para o leitor
  • Assert serão verificados em tempo de execução em Debug constrói.
  • Uma vez que o código foi exaustivamente testado, reconstruindo o código como lançamento irá remover a sobrecarga de desempenho de verificar a hipótese (mas com a vantagem de que uma compilação de depuração mais tarde será sempre reverter as verificações, se necessário).

... Mais detalhes

Debug.Assert expressa uma condição que foi assumida sobre estado pelo remanescente do bloco de código dentro do controlo do programa. Isso pode incluir o estado dos parâmetros fornecidos, estado de membros de uma instância de classe, ou que o retorno de uma chamada de método está em sua gama de contratados / projetado. Normalmente, afirma deverá falhar o thread / processo / programa com todas as informações necessárias (Stack Trace, Bater Dump, etc), como eles indicam a presença de um erro ou condição unconsidered que não tenha sido projetado para (ou seja, não tente e catch ou lidar com falhas de declaração), com uma possível exceção de quando uma própria afirmação poderia causar mais danos do que o bug (Controladores de tráfego aéreo por exemplo não iria querer um YSOD quando uma aeronave vai submarino, embora seja discutível se uma compilação de depuração deve ser implantado para produção ...)

Quando você deve usar Asserts? - Em qualquer ponto em um sistema, ou API biblioteca ou serviço onde as entradas para uma função ou estado de uma classe são considerados válidos (por exemplo, quando a validação já foi feito na entrada do usuário na camada de apresentação de um sistema, o negócio e aulas de camada de dados normalmente assumem que as verificações de nulos, cheques alcance, cadeia de comprimento cheques etc na entrada já foi feito). - verificações Assert comuns incluem onde uma suposição inválida resultaria em uma dereference nulo objeto, um fora divisor de zero, numérico ou data de estouro aritmético, e geral de banda / não foi concebido para o comportamento (por exemplo, se um de 32 bits int foi usada para modelar um ser humano idade, seria prudente Assert que a idade é realmente entre 0 e 125 ou menos -. valores de -100 e 10 ^ 10 não foram projetados para)

contratos de código .Net
Na Pilha .Net, Código podem ser usados ??in additisobre a, ou como uma alternativa para usando Debug.Assert. Contratos código pode verificação do estado ainda formalizar, e pode ajudar na detecção de violações de premissas pelo ~ tempo de compilação (ou pouco depois, se executado como uma verificação de antecedentes em uma IDE).

Projeto por Contrato (DBC) cheques disponíveis incluem:

  • Contract.Requires - Contratado Preconditions
  • Contract.Ensures - Contratado postconditions
  • Invariant -. Expressa uma suposição sobre o estado de um objeto em todos os pontos em sua vida útil
  • Contract.Assumes -. Pacifica o verificador estático quando uma chamada para não-contrato decorado métodos é feita

De acordo com a IDesign Padrão , você deve

Assert cada suposição. Em média, cada quinta linha é uma afirmação.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

Como um aviso que eu devo mencionar que eu não tê-lo encontrado prática para implementar esta IRL. Mas este é o seu padrão.

Use afirmações única em casos em que você deseja que o cheque removido para compilações. Lembre-se, suas afirmações não incêndio, se você não compilar no modo de depuração.

Dado o seu check-for-nulo exemplo, se este é de uma API interna-somente, eu poderia usar uma afirmação. Se está em uma API pública, eu definitivamente utilizar a verificação explícita e jogar.

Todos afirma deve ser um código que pode ser otimizado para:

Debug.Assert(true);

Porque ele está verificando algo que você já tenha assumido é verdade. Por exemplo:.

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

No exemplo acima, existem três abordagens diferentes para parâmetros nulos. O primeiro aceita como permitido (ele só não faz nada). O segundo lança uma exceção para o código de chamada para pega (ou não, resultando em uma mensagem de erro). A terceira parte do princípio que não pode acontecer, e afirma que é assim.

No primeiro caso, não há nenhum problema.

No segundo caso, há um problema com o código de chamada -. Não deveria ter chamado GetFirstAndConsume com nulo, assim que começa uma volta exceção

No terceiro caso, há um problema com este código, porque ele já deveria ter sido verificado que en != null antes que ele nunca foi chamado, de modo que não é verdade é um bug. Ou em outras palavras, deve ser um código que poderia, teoricamente, ser otimizado para Debug.Assert(true), en != null sicne deve ser sempre true!

Eu pensei que eu iria adicionar mais quatro casos, quando Debug.Assert pode ser a escolha certa.

1) Algo que eu não vi mencionado aqui é a cobertura conceitual adicional Afirma pode fornecer durante automatizado testando . Como um exemplo simples:

Quando alguns chamador de nível superior é modificado por um autor que acredita que eles têm ampliado o alcance do código para lidar com cenários adicionais, idealmente (!) Eles vão escrever testes de unidade para cobrir esta nova condição. Nesse caso, pode ser que o código totalmente integrado parece funcionar bem.

No entanto, na verdade, uma falha sutil foi introduzido, mas não foi detectado nos resultados dos testes. O receptor tornou-se não-determinístico, neste caso, e só acontece para fornecer o resultado esperado. Ou talvez rendeu um erro de arredondamento que foi despercebido. Ou causou um erro que foi compensado igualmente em outros lugares. Ou concedido não só o acesso solicitado, mas privilégios adicionais que não deve ser concedido. Etc.

Neste ponto, o Debug.Assert () declarações contidas o receptor juntamente com o novo caso (ou caso extremo) impulsionado pelos testes de unidade pode fornecer notificação de valor inestimável durante o teste que as suposições do autor original ter sido invalidado, eo código não deve ser liberado sem revisão adicional. Afirma com testes de unidade são os parceiros perfeitos.

2) Além disso, alguns testes são simples de escrever, mas de alto custo e desnecessário dadas as suposições iniciais . Por exemplo:

Se um objeto só pode ser acessado a partir de um certo ponto de entrada protegida, deve uma consulta adicional ser feita para um banco de dados de direitos de rede de cada método de objeto para garantir o chamador tem permissões? Certamente não. Talvez a solução ideal inclui caching ou alguma outra expansão de recursos, mas o projeto não requer isso. Um Debug.Assert () irá mostrar imediatamente quando o objecto tiver sido ligado a um ponto de entrada inseguro.

3) seguida, em alguns casos, a sua produto pode ter nenhuma interação de diagnóstico útil para a totalidade ou parte de suas operações quando implantado no modo de versão . Por exemplo:

Suponha que é um dispositivo em tempo real incorporado. Lançar exceções e reiniciar quando encontra um pacote malformado é contra-produtivo. Em vez disso, o dispositivo pode beneficiar da operação de melhor esforço, até mesmo ao ponto de tornar o ruído em sua saída. Ele também não pode ter uma interface humana, dispositivo de registro, ou mesmo ser fisicamente acessíveis por humanos em tudo quando implantado no modo de versão, e consciência de erros é melhor fornecida por avaliar a mesma saída. Neste caso, as afirmações liberais e os testes de pré-lançamento completa são mais valiosas do que exceções.

4) Por último, alguns testes são desnecessários só porque o receptor é percebido como extremamente confiável . Na maioria dos casos, o código mais reutilizável é, mais esforço tem sido colocado em tornando-se confiável. Por isso, é comum a exceção para os parâmetros inesperados de chamadores, mas Assert para resultados inesperados de callees. Por exemplo:

Se uma operação de núcleo String.Find afirma ele retornará um -1 quando os critérios de pesquisa não for encontrado, você pode ser capaz de executar com segurança uma operação, em vez de três. No entanto, se ele realmente voltou -2, você pode ter nenhum curso de ação razoável. Seria inútil para substituir o cálculo mais simples com um que testa separadamente para um valor -1 e razoável na maioria dos ambientes de liberação para a maca seu código com testes garantindo bibliotecas centrais estão a funcionar como esperado. Neste caso Afirma são ideais.

citação tirada de The Pragmatic Programmer: From Journeyman para Mestre

Deixe Afirmações ligado

Há um equívoco comum sobre afirmações, promulgada pelo as pessoas que escrevem compiladores e ambientes de linguagem. vai algo como isto:

As afirmações adicionar alguma sobrecarga ao código. Porque eles verificar se há coisas que nunca deveria acontecer, eles vão se desencadeou apenas por um erro no código. Depois que o código foi testado e enviado, eles não são mais necessário, e deve ser desligado para tornar o código funcionar mais rápido. Afirmações são uma instalação de depuração.

Existem duas hipóteses patentemente erradas aqui. Primeiro, eles assumem que testando encontra todos os bugs. Na realidade, para qualquer programa complexo você é improvável para testar até mesmo uma porcentagem minúscula das permutações seu código será submetido a (ver Ruthless Testing).

Em segundo lugar, os otimistas estão esquecendo que o seu programa é executado em um mundo perigoso. Durante o teste, os ratos provavelmente não vai roer através de um cabo de comunicação, alguém jogar um jogo vai memória não escape, e arquivos de log não vai encher o disco rígido. Essas coisas podem acontecer quando seu programa é executado em um ambiente de produção. Sua primeira linha de defesa está verificando qualquer erro possível, e sua segunda está usando afirmações para tentar detectar aqueles que você perdeu.

Desligar afirmações quando você entregar um programa para a produção é como atravessar um fio de alta sem rede porque você fez isso uma vez do outro lado, na prática . Não há valor dramático, mas é difícil para conseguir a vida seguro.

Mesmo se você tiver problemas de desempenho, desligue apenas aqueles afirmações de que realmente bater em você .

Você deve sempre usar a segunda abordagem (lançar exceções).

Além disso, se você está em produção (e ter uma liberação-build), é melhor para lançar uma exceção (e deixar o app falha no pior caso) do que trabalhar com valores inválidos e talvez destruir os dados do seu cliente (que pode custar milhares de dólares).

Você deve usar Debug.Assert de teste para erros lógicos em seus programas. O compilador só pode informá-lo de erros de sintaxe. Então você deve definitivamente usar instruções Assert para testar erros lógicos. Como dizem testando um programa que vende carros que apenas BMWs que são azuis deve obter um desconto de 15%. O compilador poderia dizer nada sobre se o seu programa é logicamente correta em realizar este, mas uma instrução assert podia.

Eu li as respostas aqui e eu pensei que eu deveria adicionar uma distinção importante. Há duas maneiras muito diferentes em que afirma são usados. Um é como um atalho desenvolvedor temporário para "Isto não deve realmente acontecer isso, se ele não me avise para que eu possa decidir o que fazer", como uma espécie de ponto de interrupção condicional, nos casos em que o programa é capaz de continuar. O outro, é um como uma maneira de colocar hipóteses sobre estados programa válido em seu código.

No primeiro caso, as afirmações não precisa nem ser no código final. Você deve usar Debug.Assert durante o desenvolvimento e você pode removê-los, se / quando não for mais necessária. Se você quer deixá-los ou se você esquecer de removê-los sem problema, uma vez que não terá qualquer consequência em compilações de versão.

Mas no segundo caso, as afirmações são parte do código. Eles, assim, afirmar, que os seus pressupostos são verdadeiras e também de documentá-los. Nesse caso, você realmente quer deixá-los no código. Se o programa está em um estado inválido, não deve ser autorizado a continuar. Se você não podia pagar o desempenho atingido você não estaria usando C #. Por um lado, pode ser útil para ser capaz de anexar um depurador se isso acontece. Por outro lado, você não quer que o rastreamento de pilha aparecendo em seus usuários e talvez mais importante que você não quer que eles sejam capazes de ignorá-lo. Além disso, se estiver em um serviço que vai ser sempre ignorado. Portanto, na produção do comportamento correto seria lançar uma exceção, e usar a exceção normal de manipulação de seu programa, o que pode mostrar ao usuário uma mensagem agradável e log os detalhes.

Trace.Assert tem a maneira perfeita para conseguir isso. Não serão removidos na produção, e pode ser configurado com diferentes ouvintes usando app. Assim, para o desenvolvimento do manipulador padrão é bom, e para a produção você pode criar um TraceListener simples como abaixo, que lança uma exceção e ativá-lo no arquivo de configuração de produção.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

E no arquivo de configuração de produção:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>

Eu não sei como ele está em C # e .NET, mas em C vai afirmar () só funcionam se compilado com -DDEBUG - o usuário final nunca verá um assert () se ele é compilado sem. É apenas desenvolvedor. Eu usá-lo realmente, muitas vezes, às vezes é mais fácil de rastrear bugs.

Eu não usá-los no código de produção. Lançar exceções, captura e registro.

Também precisa ter cuidado em asp.net, como uma declaração pode aparecer no console e congelar o pedido (s).

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