Quando devo usar a depuração.Asserção?

Sou engenheiro de software profissional há cerca de um ano, tendo-me graduado com uma licenciatura em Informática. Eu sabia sobre asserções por um tempo em C++ E C, mas não fazia ideia de que existiam em C# e.NET até recentemente.

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

Devo começar a usar afirmações no nosso código de produção? E em caso afirmativo, quando é que a sua utilização é mais adequada? Faria mais sentido fazer

Debug.Assert(val != null);

ou

if ( val == null )
    throw new exception();
Author: Even Mien, 2008-09-24

20 answers

Em a depuração das aplicações Microsoft.NET 2.0 O John Robbins tem uma grande secção sobre asserções. Os seus pontos principais são:

  1. afirmar liberalmente. Nunca se pode ter demasiadas afirmações.
  2. As afirmações não substituem excepções. Exceções cobrem as coisas que seu código exige; afirmações cobrem as coisas que ele assume. [6]uma afirmação bem escrita pode dizer-lhe não apenas o que aconteceu e onde (como uma exceção), mas porquê.
  3. uma mensagem de excepção pode muitas vezes ser críptico, requerendo que você trabalhe para trás através do código para recriar o contexto que causou o erro. Uma afirmação pode preservar o estado do programa no momento em que o erro ocorreu.
  4. As afirmações dobram como documentação, dizendo aos outros programadores quais as suposições implícitas das quais o seu código depende.
  5. a janela que aparece quando uma afirmação falha permite-lhe anexar um depurador ao processo, para que possa procurar na pilha como se tivesse posto um ponto de paragem lá.

PS: Se você gostou de código completo, eu recomendo seguir com este livro. Eu comprei-o para aprender sobre o uso de WinDBG e arquivos de dump, mas a primeira metade está cheia de dicas para ajudar a evitar bugs em primeiro lugar.

 215
Author: Rory MacLeod, 2008-09-24 19:47:21

Coloque {[[0]} em todo o lado no código onde você quer ter verificações de sanidade para garantir invariantes. Quando você compila uma compilação de lançamento (isto é, não DEBUG constante de compilador), as chamadas para Debug.Assert() serão removidas para que não afetem o desempenho.

Ainda deve abrir excepções antes de ligar. O assert apenas garante que tudo está como esperado enquanto você ainda está se desenvolvendo.
 83
Author: Mark Cidade, 2014-03-28 05:35:05

De Código Completo

8 Programação Defensiva [8]}8.2 afirmações Uma afirmação é um código que é usado durante o desenvolvimento-geralmente uma rotina ou macro - que permite a um programa verificar-se à medida que ele corre. Quando um a afirmação é verdadeira, Isso significa que tudo está funcionando como esperado. Quando é falso, isso significa que detectou um erro inesperado no codigo. Por exemplo, se o sistema assumir que uma informação ao cliente o ficheiro nunca tem mais de 50.000 registros, o programa pode conter uma afirmação segundo a qual o número de registos é inferior ou igual a 50.000. Desde que o número de registos seja inferior ou igual a 50.000, a afirmação será silenciosa. Se encontrar mais de 50.000 discos, no entanto, em voz alta "afirmar" que há um erro no programa.

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

Uma afirmação normalmente leva dois argumentos: uma expressão booleana que descreve a suposição que é suposto ser verdade e uma mensagem para mostrar se não for.

(…)

Normalmente, não quer que os utilizadores vejam as mensagens de asserção em código de produção; afirmações são principalmente para uso durante o desenvolvimento e manutenção. As afirmações são normalmente compilado no código em tempo de desenvolvimento e compilado a partir do código de produção. Durante desenvolvimento, asserções eliminar pressupostos contraditórios, condições inesperadas, maus valores passaram para rotinas, e assim por diante. Durante a produção, eles são compilados a partir do código para que o as afirmações não degradam o desempenho do sistema.

 46
Author: juan, 2015-11-09 16:01:14

Use as afirmações para verificar os pressupostos e excepções dos programadores para verificar os pressupostos ambientais.

 40
Author: Justin R., 2008-09-24 19:18:33

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

A ideia é que, com os meus métodos privados, sou eu que estou sob controlo, por isso, se começar a chamar um dos meus métodos privados com parâmetros incorrectos, então quebrei a minha própria suposição algures-nunca deveria ter entrado nesse estado. Na produção, estas afirmações privadas deveriam, idealmente, ser desnecessárias trabalho já que é suposto eu manter o meu estado interno válido e consistente. Contraste com os parâmetros dados aos métodos públicos, que poderiam ser chamados por qualquer um em tempo de execução: eu ainda preciso impor restrições de parâmetros lá, lançando exceções.

Além disso, os meus métodos privados ainda podem lançar excepções se algo não funcionar no tempo de execução (erro de rede, erro de acesso aos Dados, Dados incorrectos recuperados a partir de um serviço de terceiros, etc.). As minhas afirmações só estão lá para ter a certeza que eu não quebrei as minhas próprias suposições internas sobre o estado do objecto.

 39
Author: Nicholas Piasecki, 2011-02-17 15:48:58

Se eu fosse a ti faria:

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

Ou para evitar repetidos testes de Estado

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}
 32
Author: Mark Ingram, 2013-04-04 10:23:19

Se quiser afirmações no seu código de produção (isto é, Compilação de versões), pode usar Trace.Afirmar em vez de depurar.Afirmar.

É claro que isto aumenta o seu executável de produção.

Também se a sua aplicação estiver a correr no modo de interface do utilizador, a janela de asserção será apresentada por omissão, o que poderá ser um pouco desconcertante para os seus utilizadores.

Pode anular este comportamento removendo o DefaultTraceListener: veja a documentação para Rastreio.Ouvintes em MSDN.

Em resumo,

  • Usar A Depuração.Afirmar liberalmente para ajudar a pegar bugs em compilações de depuração.

  • Se usares vestígios.Afirmar no modo de interface de usuário, você provavelmente quer remover o DefaultTraceListener para evitar desconcertar os usuários.

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

 22
Author: Joe, 2008-09-24 19:15:52

As afirmações são usadas para apanhar o erro do programador (o seu), não o erro do utilizador. Eles devem ser usados apenas quando não há nenhuma chance de um usuário poderia causar o assert a disparar. Se você está escrevendo uma API, por exemplo, as afirmações não devem ser usadas para verificar que um argumento não é nulo em qualquer método que um usuário da API poderia chamar. Mas poderia ser usado em um método privado não exposto como parte de sua API para afirmar que seu código nunca passa um argumento nulo quando não é suposto.

Normalmente sou a favor excepções sobre afirmações quando não tenho a certeza.

 21
Author: user19113, 2008-09-24 19:04:35
Na maioria das vezes, nunca no meu livro. Na grande maioria das ocasiões, se você quiser verificar se tudo está são, então jogue se não estiver.

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

Para mim isto sugere usando a depuração.Afirma que está a adiar o problema para outra pessoa, e resolve o problema sozinho. Se algo é suposto ser o caso e não é, então atira.

Acho que há cenários críticos de desempenho em que queremos otimizar as nossas afirmações e elas são úteis lá, no entanto, ainda não encontrei tais cenários. cenario.
 10
Author: Quibblesome, 2008-12-14 05:19:18

Em Resumo

Asserts são utilizados para os guardas e para verificar a concepção por restrições contratuais, por exemplo:

  • {[[0]} deve ser apenas para compilações de depuração e não-Produção. Asserts are typically ignored by the compiler in Release builds.
  • Asserts pode verificar se existem erros / condições inesperadas que estão no controlo do seu sistema
  • Asserts não são um mecanismo de validação em primeira linha das regras de entrada ou de negócio do utilizador
  • Asserts deve Não ser utilizado para detectar condições ambientais inesperadas (que estão fora do controlo do código), por exemplo, sem memória, falha da rede, falha da base de dados, etc. Apesar de raras, estas condições são de esperar (e o seu código do aplicativo não pode corrigir problemas como falha de hardware ou esgotamento de recursos). Normalmente, as exceções serão lançadas-a sua aplicação pode então tomar medidas corretivas (por exemplo, repetir uma operação de banco de dados ou de rede, tentar libertar a memória em cache), ou interromper graciosamente se a excepção não puder ser tratada.
  • uma afirmação falhada deve ser fatal para o seu sistema - isto é, ao contrário de uma excepção, não tente apanhar ou manipular falhou {[[0]} - o seu código está a funcionar em território inesperado. Stack Traces and crash dumps can be used to determine what wrong.

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

  • para ajudar a encontrar a validação em falta das entradas do utilizador, ou erros a montante em código de nível superior.
  • afirma no base de código transmitir claramente ao leitor as suposições feitas no código
  • Assert será verificado no tempo de execução em Debug builds.
  • Uma vez que o código tenha sido exaustivamente testado, a reconstrução do código como Versão irá remover o desempenho acima de verificar a suposição (mas com o benefício de que uma compilação posterior de depuração irá sempre reverter as verificações, se necessário).

... Mais Detalhes

Debug.Assert expressa uma condição que foi assumida sobre o estado por o restante do bloco de código dentro do controle do programa. Isto pode incluir o estado dos parâmetros fornecidos, estado dos membros de uma instância de classe, ou que o retorno de uma chamada de método está em sua gama contratada / projetada. Normalmente, afirma devem falhar a thread / processo / programa com todas as informações necessárias (Rastreamento de Pilha, de Despejo de memória, etc), pois eles indicam a presença de um bug ou desconsiderados condição de que não tenha sido projetado para (i.e. não tentar pegar ou manipular falhas de asserção), com uma possível exceção de quando uma asserção em si pode causar mais danos do que o bug (por exemplo, controladores de tráfego aéreo não querem um YSOD quando uma aeronave vai submarina, embora seja discutível se uma compilação de depuração deve ser implantada para a produção ...)

Quando deve utilizar Asserts? - Em qualquer ponto de um sistema, ou API de biblioteca, ou serviço onde as entradas para uma função ou estado de uma classe são consideradas válidas (por exemplo, quando a validação já foi feita no usuário a entrada no nível de apresentação de um sistema, As classes de negócio e de nível de dados normalmente assumem que as verificações nulas, as verificações de Gama, as verificações de comprimento de cadeia de caracteres, etc.de entrada já foram feitas). - Verificações comuns Assert incluem onde uma suposição inválida resultaria em uma dereferência de objeto nulo, um divisor zero, excesso numérico ou de data aritmética, e geral fora da banda / não projetado para o comportamento (por exemplo, se um int de 32 bits foi usado para modelar a idade de um humano, seria prudente Assert que a idade é de facto, não foram concebidos para valores entre 0 e 125 de -100 e 10^10).

.net Contratos De Código
Na pilha. Net, Os contratos de código podem ser usados em adição a ou como alternativa a usando Debug.Assert. Os contratos de código podem formalizar ainda mais a verificação do estado, e podem ajudar na detecção de violações de suposições em ~tempo de compilação (ou pouco depois, se executado como uma verificação de antecedentes em uma IDE).

Verificação por contrato (DBC) disponível incluem:

  • Contract.Requires - Condições Prévias Contraídas
  • Contract.Ensures - Condições Pós-Contratuais
  • Invariant - expressa uma suposição sobre o estado de um objeto em todos os pontos de sua vida.
  • - Acalma o verificador estático quando é feita uma chamada para métodos decorados sem contrato.
 9
Author: StuartLC, 2018-01-30 06:35:09

De acordo com a norma de IDesign , deve

Afirmar todas as suposições. Em média, cada quinta linha é uma afirmação.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);
A título de declaração de exoneração de responsabilidade, devo dizer que não achei prático aplicar esta IRL. Mas este é o padrão deles.
 7
Author: devlord, 2008-09-24 20:04:43

Use as afirmações apenas nos casos em que deseja que a verificação seja removida para as compilações de versões. Lembre-se, as suas afirmações não dispararão se não compilar no modo de depuração.

Dado o teu exemplo de check-for-null, se isto estiver numa API interna, posso usar uma afirmação. Se estiver numa API pública, eu definitivamente usaria o cheque explícito e atirar.

 6
Author: Derek Park, 2008-09-24 19:03:56

Todas as afirmações devem ser códigos que possam ser optimizados para:

Debug.Assert(true);
Porque está a verificar algo que já presumiste ser 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 acima, há três abordagens diferentes para parâmetros nulos. O primeiro aceita-o como permitido (ele simplesmente não faz nada). O segundo lança uma exceção para o código de chamada para lidar (ou não, resultando em uma mensagem de erro). O terceiro assume que não pode acontecer, e afirma que é assim.

Em no primeiro caso, não há problema.

No segundo caso, há um problema com o código de chamada - ele não deveria ter chamado {[[2]} com nulo, então ele recebe uma exceção de volta.

No terceiro caso, há um problema com este código, porque ele já deveria ter sido verificado que {[[3]} antes de ser chamado, de modo que não é verdade é um bug. Ou, por outras palavras, deve ser um código que teoricamente possa ser optimizado para Debug.Assert(true), sicne en != null deve ser sempre true!

 6
Author: Jon Hanna, 2013-12-29 22:43:55
Pensei em adicionar mais quatro casos, onde depurar.Afirmar pode ser a escolha certa.

1) algo que eu não vi mencionado aqui é a cobertura conceitual adicional que pode fornecer durante os testes automatizados . Como um exemplo simples:

Quando alguém de nível superior é modificado por um autor que acredita ter expandido 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. Ele pode então ser que o código totalmente integrado pareça funcionar bem. No entanto, foi introduzida uma falha subtil, mas não detectada nos resultados dos testes. O chamado tornou-se não-determinístico neste caso, e apenas acontece para fornecer o resultado esperado. Ou talvez tenha produzido um erro de arredondamento que não foi notado. Ou causou um erro que foi compensado igualmente em outro lugar. Ou concedido não só o acesso solicitado, mas privilégios adicionais que não devem ser concedidos. Etc. Neste momento, a depuração.Afirmar () afirmações contidas no callee juntamente com o novo caso (ou caso de borda) impulsionado por testes de unidade pode fornecer uma notificação inestimável durante o teste de que os pressupostos do autor original foram invalidados, e o código não deve ser divulgado sem revisão adicional. Afirma-se que os testes unitários são os parceiros perfeitos.

2) Além disso, alguns testes são simples de escrever, mas de alto custo e desnecessário dado o pressupostos. Por exemplo:

Se um objecto só pode ser acedido a partir de um determinado ponto de entrada seguro, deve ser feita uma consulta adicional a uma base de dados de direitos de rede de cada método de objecto para garantir que a pessoa que ligou tem permissões? Claro que não. Talvez a solução ideal inclui caching ou alguma outra expansão de recursos, mas o projeto não requer isso. depuracao.Assert () irá mostrar imediatamente quando o objecto foi ligado a um ponto de entrada inseguro.

3) seguinte, em alguns casos, o seu produto pode não ter uma interacção de diagnóstico útil para a totalidade ou parte das suas operações quando utilizado no modo de libertação. Por exemplo:

Suponha que seja um dispositivo embutido em tempo real. Abrir exceções e reiniciar quando encontra um pacote malformado é contraproducente. Em vez disso, o dispositivo pode se beneficiar da operação de melhor esforço, até mesmo ao ponto de produzir ruído em sua saída. Ele também pode não ter uma interface humana, dispositivo de registro, ou mesmo ser fisicamente acessível pelo ser humano em tudo quando implantado no modo de liberação, e conscientização de erros é melhor fornecido através da avaliação do mesmo resultado. Neste caso, as afirmações liberais e os testes minuciosos de pré-lançamento são mais valiosos do que exceções.

4) por último, alguns testes são desnecessários apenas porque o callee é visto como extremamente fiável. Na maioria dos casos, quanto mais reutilizável for o código, mais esforço foi feito para torná-lo confiável. Por conseguinte, é comum a excepção para parâmetros inesperados das chamadas, mas afirmar para resultados inesperados das chamadas. Por exemplo:

Se uma operação principal {[[0]} declarar que irá devolver um -1 quando os critérios de pesquisa não forem encontrados, poderá ser capaz de efectuar uma operação com segurança em vez de três. No entanto, se ele realmente retornou -2, você pode não ter um curso razoável de ação. Seria inútil substituir o cálculo mais simples por um que testa separadamente um valor -1, e não razoável na maioria das versões ambientes para descartar o seu código com testes que garantem que as bibliotecas centrais estão operando como esperado. Neste caso, as afirmações são ideais.

 4
Author: shannon, 2014-09-26 06:40:45
Uma citação tirada do programador pragmático, do Jornaleiro ao Mestre.

Deixar As Asserções Ligadas

Há um mal-entendido comum sobre afirmações, promulgadas por as pessoas que escrevem Compiladores e ambientes de linguagem. Vai. algo do género:

As afirmações adicionam algumas despesas ao código. Porque eles verificam as coisas. isso nunca deve acontecer, eles serão acionados apenas por um bug no codigo. Uma vez o código foi testado e enviado, eles já não são necessário, e deve ser desligado para fazer o código correr mais rápido. Assertions are a debugging facility.

Há aqui duas suposições manifestamente erradas. Primeiro, eles assumem que testando encontra todos os bugs. Na realidade, para qualquer programa complexo você é pouco provável que testem mesmo uma minúscula percentagem das permutações seu código será colocado através (ver Testes impiedosos). Em segundo lugar, os optimistas esquecem-se de que a sua a aplicação corre em a mundo perigoso. Durante os testes, os ratos provavelmente não roem através de um cabo de comunicações, alguém a jogar um jogo não vai esgotar a memória, e ... os ficheiros de registo não preenchem o disco rígido. Estas coisas podem acontecer quando o seu programa funciona num ambiente de produção. A sua primeira linha de a defesa está a verificar qualquer erro possível, e o seu segundo está a usar afirmações para tentar detectar aqueles que você perdeu.

Desligar as afirmações quando entrega um programa para a produção é como atravessar um fio alto sem uma rede porque uma vez conseguiste na prática. Há um valor dramático, mas é difícil ter vida. seguro.

Mesmo que você tenha problemas de desempenho, desligue apenas aqueles afirmações que realmente te impressionam.

 4
Author: Teoman shipahi, 2015-12-11 00:02:51

Deve sempre utilizar a segunda abordagem (excepções).

Também se você está em produção( e tem um release-build), é melhor lançar uma exceção (e deixar o aplicativo estoirar 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).

 2
Author: Thomas Danecker, 2008-09-24 19:09:30

Deve usar a depuração.Afirmar para testar para erros lógicos em seus programas. O complier só pode informá-lo de erros de sintaxe. Então você deve usar definetely afirmações Assert para testar para erros lógicos. Como por exemplo testar um programa que vende carros que só BMWs que são azuis deve obter um desconto de 15%. O complier não poderia lhe dizer nada sobre se seu programa é logicamente correto em executar isso, mas uma afirmação poderia.

 0
Author: orlando calresian, 2008-09-24 19:03:40
Li as respostas e achei que devia acrescentar uma distinção importante. Há duas maneiras muito diferentes em que as afirmações são usadas. Um é como um atalho temporário de desenvolvedor para "isso realmente não deve acontecer, então se ele me deixa saber para que eu possa decidir o que fazer", como um ponto de paragem condicional, para casos em que o seu programa é capaz de continuar. O outro, é uma forma de colocar suposições sobre os estados do programa válidos em seu código. No primeiro caso, o as afirmações nem precisam de estar no código final. Deve utilizar Debug.Assert durante o desenvolvimento e pode removê-los se/quando já não for necessário. Se você quiser deixá-los ou se você se esquecer de removê-los nenhum problema, uma vez que eles não terão nenhuma consequência nas compilações de liberação.

Mas no segundo caso, as afirmações fazem parte do Código. Eles, bem, afirmam, que as suas suposições são verdadeiras, e também documentam-nas. Nesse caso, queres mesmo deixá-los no código. Se o programa for em um estado inválido não deve ser permitido continuar. Se não tivesses dinheiro para o sucesso, não estarias a usar C#. Por um lado, pode ser útil ser capaz de anexar um depurador se isso acontecer. Por outro lado, você não quer que o traço stack 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 num serviço será sempre ignorado. Portanto, na produção o comportamento correto seria lançar uma exceção, e usar o tratamento normal de exceção do seu programa, que pode mostrar ao usuário uma mensagem agradável e registar os detalhes.

Trace.Assert tem a maneira perfeita de conseguir isso. Ele não será removido na produção, e pode ser configurado com diferentes ouvintes usando app.configuracao. Assim, para o desenvolvimento o 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 na configuração da produção ficheiro:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>
 0
Author: AlexDev, 2016-08-22 13:29:41

Eu não sei como é em C# e. NET, mas em C will assert () só funciona se compilado com-DDEBUG - o utilizador final nunca verá um assert() se for compilado sem. É só para programadores. Eu uso-o muitas vezes, às vezes é mais fácil de rastrear bugs.

 -1
Author: unexist, 2008-09-24 19:00:12
Eu não os usaria em código de produção. Abrir excepções, apanhar e torrar.

Também tem de ter cuidado em asp.net, como um assert pode aparecer no console e congelar o(s) Pedido (s).

 -1
Author: mattlant, 2008-09-24 19:00:58