Quando devo usar Debug.Assert ()?

220

Sou engenheiro de software profissional há cerca de um ano, tendo me formado em ciências da computação. Conheço as afirmações há algum tempo em C ++ e C, mas não fazia ideia de que elas existiam em C # e .NET até recentemente.

Nosso código de produção não contém declarações e minha pergunta é esta ...

Devo começar a usar o Asserts em nosso código de produção? E se sim, quando é o seu uso mais apropriado? Faria mais sentido fazer

Debug.Assert(val != null);

ou

if ( val == null )
    throw new exception();
Nicholas Mancuso
fonte
2
A dicotomia que você configurou é a pista. Não se trata de um ou de exceções e afirmações, seu código de defesa e de ambos. Quando fazer o que você deseja entender.
Casper Leon Nielsen
5
Certa vez, li alguém sugerir que uma exceção ou outro método de travamento é apropriado para condições em que "não há como me recuperar sensivelmente disso" e, adicionalmente, uma afirmação é apropriada para condições em que "isso nunca deveria acontecer". Mas que circunstâncias realistas satisfazem as últimas condições sem também satisfazer as primeiras? Vindo de uma experiência em Python em que afirmações permanecem em produção, nunca entendi a abordagem Java / C # de desativar algumas de suas validações em produção. O único caso para isso que eu realmente posso ver é se a validação é cara.
Mark Amery
2
Pessoalmente, uso exceções para métodos públicos e asserções para métodos privados.
31516 Fred

Respostas:

230

Na depuração de aplicativos Microsoft .NET 2.0, John Robbins possui uma grande seção sobre asserções. Seus principais pontos são:

  1. Afirme liberalmente. Você nunca pode ter muitas afirmações.
  2. As asserções não substituem exceções. Exceções cobrem as coisas que seu código exige; asserções cobrem o que supõe.
  3. Uma afirmação bem escrita pode dizer não apenas o que aconteceu e onde (como uma exceção), mas por quê.
  4. Uma mensagem de exceção geralmente pode ser enigmática, exigindo que você trabalhe de trás para frente no código para recriar o contexto que causou o erro. Uma asserção pode preservar o estado do programa no momento em que o erro ocorreu.
  5. As asserções dobram como documentação, informando a outros desenvolvedores quais são as suposições implícitas das quais seu código depende.
  6. A caixa de diálogo que aparece quando uma asserção falha permite anexar um depurador ao processo, para que você possa vasculhar a pilha como se tivesse colocado um ponto de interrupção lá.

PS: Se você gostou do Code Complete, recomendo segui-lo neste livro. Comprei-o para aprender sobre o uso do WinDBG e dos arquivos de despejo, mas a primeira metade contém dicas para ajudar a evitar erros.

Rory MacLeod
fonte
3
+1 para o resumo conciso e útil. Muito diretamente aplicável. A principal coisa que está faltando para mim, no entanto, é quando usar o Trace.Assert vs. Trace.Assert. Ou seja, algo sobre quando você os deseja ou não no seu código de produção.
Jon Coombs
2
JonCoombs é "Trace.Assert vs. Trace.Assert" um erro de digitação?
Thelem
1
@thelem Talvez Jon significava Debug.Assertvs Trace.Assert. O último é executado em uma compilação de versão e em uma depuração.
DavidRR
Por que devo preferir Debug.Assert a lançar exceção?
Bark Akkurt
86

Coloque Debug.Assert()em qualquer lugar do código onde você deseja realizar verificações de sanidade para garantir invariantes. Quando você compila uma versão do Release (ou seja, sem DEBUGconstante do compilador), as chamadas para Debug.Assert()serão removidas para que não afetem o desempenho.

Você ainda deve lançar exceções antes de ligar Debug.Assert(). A afirmação apenas garante que tudo esteja como o esperado enquanto você ainda está desenvolvendo.

Mark Cidade
fonte
35
Você poderia esclarecer por que colocar uma afirmação se ainda lança uma exceção antes de chamá-la? Ou entendi mal sua resposta?
Roman Starkov
2
@romkyns Você ainda deve incluí-los, pois, se não o fizer, quando criar seu projeto no modo Release , todas as validações / verificações de erros desaparecerão.
Oscar Mederos
28
@ Oscar Eu pensei que esse era o objetivo de usar afirmações em primeiro lugar ... OK então, então você colocou as exceções antes delas - então por que colocar as afirmações depois?
Roman Starkov
4
@superjos: Eu tenho que discordar: o ponto 2 da resposta de MacLeod afirma que você realmente precisa de afirmações e exceções, mas não no mesmo lugar. É inútil lançar um NullRefEx em uma variável e logo depois fazer um Assert (o método assert nunca mostrará uma caixa de diálogo nesse caso, que é o ponto principal da afirmação). O que MacLeod significa é que, em alguns lugares, você precisará de uma exceção; em outros, uma afirmação será suficiente.
David
1
Pode tornar-se confuso para interpretar minha interpretação da resposta de outra pessoa :) De qualquer forma eu com você sobre estes: você precisa de ambos, e você deve não colocar a exceção antes do assert. Não tenho certeza do significado de "não está no mesmo lugar". Mais uma vez, recusando-me a interpretar, apenas declararei meus pensamentos / preferências: coloque uma ou mais afirmações para verificar as condições prévias antes do início de uma operação ou verificar as condições posteriores à operação. Além das declarações, e depois delas de qualquer maneira, verifique se algo está errado e precisa lançar exceções.
superjos
52

Do código completo

8 Programação defensiva

8.2 Declarações

Uma asserção é um código usado durante o desenvolvimento - geralmente uma rotina ou macro - que permite que um programa verifique a si próprio enquanto é executado. Quando uma afirmação é verdadeira, isso significa que tudo está funcionando como esperado. Quando é falso, significa que ele detectou um erro inesperado no código. Por exemplo, se o sistema assumir que um arquivo de informações do cliente nunca terá mais de 50.000 registros, o programa poderá conter uma afirmação de que o número de registros é menor ou igual a 50.000. Desde que o número de registros seja menor ou igual a 50.000, a asserção será silenciosa. Se encontrar mais de 50.000 registros, no entanto, "afirmará" que existe um erro no programa.

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

Uma asserção geralmente usa dois argumentos: uma expressão booleana que descreve a suposição que deveria ser verdadeira e uma mensagem a ser exibida, se não for.

(…)

Normalmente, você não deseja que os usuários vejam mensagens de asserção no código de produção; asserções são principalmente para uso durante o desenvolvimento e manutenção. As asserções são normalmente compiladas no código no momento do desenvolvimento e compiladas do código para produção. Durante o desenvolvimento, as afirmações eliminam suposições contraditórias, condições inesperadas, valores ruins transmitidos às rotinas e assim por diante. Durante a produção, eles são compilados a partir do código para que as asserções não prejudiquem o desempenho do sistema.

juan
fonte
7
Então, o que acontece se um arquivo de informações do cliente encontrado na produção contiver mais de 50.000 registros? Se a afirmação for compilada a partir do código de produção e essa situação não for tratada de outra forma, isso não significa problemas?
DavidRR
1
@DavidRR Sim, de fato. Mas assim que a produção sinalizar um problema e algum desenvolvedor (que talvez não conheça bem esse código) depure o problema, a declaração falhará e o desenvolvedor saberá imediatamente que o sistema não é usado conforme o esperado.
Marc
48

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

A idéia é que, com meus métodos particulares, sou eu quem estou sob controle, por isso, se eu começar a chamar um de meus métodos particulares com parâmetros incorretos, então eu quebrei minha própria suposição em algum lugar - eu nunca deveria ter conseguido nesse estado. Na produção, essas afirmações privadas deveriam idealmente ser um trabalho desnecessário, pois devo manter meu estado interno válido e consistente. Contraste com os parâmetros fornecidos aos métodos públicos, que podem ser chamados por qualquer pessoa em tempo de execução: eu ainda preciso impor restrições de parâmetros por meio de exceções.

Além disso, meus métodos particulares ainda podem gerar exceções se algo não funcionar em tempo de execução (erro de rede, erro de acesso a dados, dados incorretos recuperados de um serviço de terceiros, etc.). Minhas declarações estão lá apenas para garantir que eu não quebrei minhas próprias suposições internas sobre o estado do objeto.

Nicholas Piasecki
fonte
3
Esta é uma descrição muito clara de uma boa prática e fornece uma resposta muito razoável à pergunta.
Casper Leon Nielsen
42

Use asserts para verificar suposições e exceções do desenvolvedor para verificar suposições ambientais.

Justin R.
fonte
31

Se eu fosse você, faria:

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

Ou para evitar repetidas verificações de condição

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}
Mark Ingram
fonte
5
Como isso está resolvendo o problema? Com isso, o debug.assert se torna inútil.
Quibblesome
43
Não, não - ele quebra o código imediatamente antes da exceção ser lançada. Se você tiver uma tentativa / captura em outro lugar do seu código, poderá nem perceber a exceção!
Mark Ingram
2
+1 Eu tive um monte de problemas, onde as pessoas simplesmente try / exceções de captura sem fazer nada por isso rastreamento bug foi um problema
dance2die
5
Suponho que há casos em que você pode querer fazer isso, mas nunca deve pegar uma exceção geral!
Casebash 25/10/09
8
@ MarkIngram -1 na sua resposta e +1 no seu comentário justificando-a. Esse é um bom truque para certas circunstâncias peculiares, mas parece uma coisa bizarra de se fazer em geral para toda validação.
Mark Amery
24

Se você quiser o Asserts no seu código de produção (por exemplo, versões de compilação), use Trace.Assert em vez de Debug.Assert.

Obviamente, isso adiciona uma sobrecarga ao seu executável de produção.

Além disso, se seu aplicativo estiver sendo executado no modo de interface do usuário, a caixa de diálogo Asserção será exibida por padrão, o que pode ser um pouco desconcertante para os usuários.

Você pode substituir esse comportamento removendo o DefaultTraceListener: consulte a documentação para Trace.Listeners no MSDN.

Em suma,

  • Use Debug.Assert liberalmente para ajudar a detectar erros nas compilações de depuração.

  • Se você usar Trace.Assert no modo de interface do usuário, provavelmente desejará 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, provavelmente é melhor lançar uma exceção, para garantir que a execução não continue. Esteja ciente de que um usuário pode optar por ignorar uma asserção.

Joe
fonte
1
+1 por apontar a distinção crucial entre Debug.Assert e Trace.Assert, já que o OP perguntou especificamente sobre o código de produção.
Jon Coombs
21

As declarações são usadas para capturar o erro do programador (seu), não o erro do usuário. Eles devem ser usados ​​apenas quando não houver chance de um usuário causar a ativação da declaração. Se você estiver escrevendo uma API, por exemplo, afirmações não devem ser usadas para verificar se um argumento não é nulo em nenhum método que um usuário da API possa chamar. Mas poderia ser usado em um método privado não exposto como parte da sua API para afirmar que SEU código nunca passa um argumento nulo quando não deveria.

Eu geralmente prefiro exceções do que afirmações quando não tenho certeza.

user19113
fonte
11

Em resumo

Asserts são usados ​​para proteções e para verificar as restrições de Design por contrato, a saber:

  • Assertsdeve ser apenas para versões de depuração e não produção. As declarações geralmente são ignoradas pelo compilador nas versões do Release.
  • Asserts pode verificar se há bugs / condições inesperadas que estão no controle do seu sistema
  • Asserts NÃO são um mecanismo para validação de primeira linha de entrada do usuário ou regras de negócios
  • Assertsnão deve ser usado para detectar condições ambientais inesperadas (que estão fora do controle do código), por exemplo, falta de memória, falha na rede, falha no banco de dados, etc. Embora raras, essas condições são esperadas (e o código do aplicativo não pode corrigir problemas como falha de hardware ou esgotamento de recursos). Normalmente, são lançadas exceções - seu aplicativo pode executar uma ação corretiva (por exemplo, tentar novamente um banco de dados ou operação de rede, tentar liberar memória em cache) ou abortar normalmente se a exceção não puder ser tratada.
  • Uma afirmação com falha deve ser fatal para o seu sistema - ou seja, diferente de uma exceção, não tente capturar ou manipular com falha Asserts- seu código está operando em território inesperado. Rastreamentos de pilha e despejos de memória podem ser usados ​​para determinar o que deu errado.

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

  • Ajudar a encontrar a validação ausente de entradas do usuário ou erros de upstream no código de nível superior.
  • Afirmações na base de código transmitem claramente as suposições feitas no código ao leitor
  • A declaração será verificada em tempo de execução nas Debugcompilações.
  • Depois que o código for exaustivamente testado, a reconstrução do código como Release removerá a sobrecarga de desempenho da verificação da suposição (mas com o benefício de que uma compilação posterior da Depuração sempre reverterá as verificações, se necessário).

... Mais detalhes

Debug.Assertexpressa uma condição que foi assumida sobre o estado pelo restante do bloco de código dentro do controle do programa. Isso pode incluir o estado dos parâmetros fornecidos, o estado dos membros de uma instância de classe ou que o retorno de uma chamada de método está em seu intervalo contratado / projetado. Normalmente, as declarações devem travar o encadeamento / processo / programa com todas as informações necessárias (rastreamento de pilha, despejo de memória etc.), pois indicam a presença de um bug ou condição não considerada que não foi projetada (por exemplo, não tente capturar ou lidar com falhas de asserção), com uma possível exceção de quando uma asserção em si pode causar mais danos que o bug (por exemplo, os Controladores de Tráfego Aéreo não desejam um YSOD quando uma aeronave se torna submarina, embora seja discutível se uma compilação de depuração deve ser implantada para Produção ...)

Quando você deve usar Asserts? - Em qualquer ponto de um sistema, API de biblioteca ou serviço em que 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 na entrada do usuário na camada de apresentação de um sistema) , as classes de negócios e da camada de dados normalmente assumem que já foram realizadas verificações nulas, verificações de intervalo, verificações de comprimento de cadeia etc. - As Assertverificações comuns incluem onde uma suposição inválida resultaria em desreferência de objeto nulo, divisor zero, estouro aritmético numérico ou de data e fora da banda geral / não projetados para o comportamento (por exemplo, se um int de 32 bits foi usado para modelar a idade de um ser humano) , seria prudente Assertque a idade esteja realmente entre 0 e 125 - valores de -100 e 10 ^ 10 não foram projetados).

Contratos de código .Net
Na pilha .Net, os contratos de código podem ser usados além ou como alternativa ao uso Debug.Assert. Os contratos de código podem formalizar ainda mais a verificação de estado e ajudar na detecção de violações de suposições no momento da compilação (ou logo em seguida, se executadas como uma verificação de segundo plano em um IDE).

As verificações de Design by Contract (DBC) disponíveis incluem:

  • Contract.Requires - Condições prévias contratadas
  • Contract.Ensures - PostConditions contratados
  • Invariant - Expressa uma suposição sobre o estado de um objeto em todos os pontos de sua vida útil.
  • Contract.Assumes - pacifica o verificador estático quando é feita uma chamada para métodos não relacionados ao contrato.
StuartLC
fonte
Infelizmente, os contratos de código estão praticamente mortos desde que a MS parou de desenvolvê-lo.
Mike Lowery
10

Principalmente nunca no meu livro. Na grande maioria das ocasiões, se você quiser verificar se está tudo bem, jogue se não estiver.

O que eu não gosto é o fato de tornar uma compilação de depuração funcionalmente diferente de uma compilação de lançamento. Se uma declaração de depuração falhar, mas a funcionalidade funcionar na versão, como isso faz algum sentido? É ainda melhor quando o asserdor deixa a empresa há muito tempo e ninguém conhece essa parte do código. Depois, você precisa gastar um pouco do seu tempo explorando o problema para ver se é realmente um problema ou não. Se for um problema, por que a pessoa não está jogando em primeiro lugar?

Para mim, isso sugere o uso de Debug.Asserts que você está adiando o problema para outra pessoa, lide com o problema. Se algo deveria ser o caso e não é, então jogue.

Eu acho que existem cenários críticos de desempenho em que você deseja otimizar suas declarações e elas são úteis lá, no entanto, ainda estou para encontrar esse cenário.

Quibblesome
fonte
4
Sua resposta merece algum mérito, embora você destaque algumas preocupações frequentemente levantadas em relação a elas, o fato de elas interromperem a sessão de depuração e a chance de falso positivo. No entanto, você está perdendo algumas sutilezas e está escrevendo "optimize away asserts" - o que só pode ser baseado no pensamento de que lançar uma exceção e executar debug.assert é o mesmo. Não é, eles servem a propósitos e características diferentes, como você pode ver em algumas das respostas aprovadas. Dw
Casper Leon Nielsen
+1 para "O que eu não gosto é o fato de tornar uma compilação de depuração funcionalmente diferente de uma compilação de versão. Se uma declaração de depuração falha, mas a funcionalidade funciona na versão, como isso faz algum sentido?" No .NET, é System.Diagnostics.Trace.Assert()executado em uma compilação de versão e em uma depuração.
DavidRR
7

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

Afirme 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);

Como aviso, devo mencionar que não achei prático implementar esse IRL. Mas esse é o padrão deles.

devlord
fonte
Parece que Juval Lowy gosta de se citar.
Devlord #
6

Use asserções apenas nos casos em que você deseja que a verificação seja removida para compilações de versão. Lembre-se de que suas afirmações não serão acionadas se você não compilar no modo de depuração.

Dado o seu exemplo de verificação por nulo, se estiver em uma API somente interna, eu poderia usar uma asserção. Se estiver em uma API pública, eu definitivamente usaria a verificação e o lançamento explícitos.

Derek Park
fonte
No .NET, pode-se usar System.Diagnostics.Trace.Assert()para executar uma asserção em uma compilação de liberação (produção).
DavidRR
Regra de análise de código CA1062: validar argumentos de métodos públicos requer a verificação de um argumento para nullquando: "Um método visível externamente desreferencia um de seus argumentos de referência sem verificar se esse argumento é nulo ". Em tal situação, o método ou a propriedade deve ser lançada ArgumentNullException.
DavidRR
6

Todas as declarações devem ser um código que possa ser otimizado para:

Debug.Assert(true);

Porque está checando algo que você já assumiu como verdadeiro. 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());
}

Acima, há três abordagens diferentes para parâmetros nulos. O primeiro aceita como permitido (simplesmente não faz nada). O segundo lança uma exceção para o código de chamada manipular (ou não, resultando em uma mensagem de erro). O terceiro pressupõe que isso não possa acontecer e afirma que é assim.

No primeiro caso, não há problema.

No segundo caso, há um problema com o código de chamada - ele não deveria ter sido chamado GetFirstAndConsumecom nulo; portanto, ele recebe uma exceção de volta.

No terceiro caso, há um problema com esse código, porque ele já deveria ter sido verificado en != nullantes de ser chamado, para que não seja verdade é um bug. Ou, em outras palavras, deveria ser um código que, teoricamente, poderia ser otimizado Debug.Assert(true), como en != nullsempre deve ser true!

Jon Hanna
fonte
1
Então, no terceiro caso, o que acontece se estiver en == nullem produção? Você está dizendo que isso nuncaen == null pode acontecer na produção (já que o programa foi completamente depurado)? Nesse caso, pelo menos serve como alternativa a um comentário. Obviamente, se forem feitas alterações futuras, ele também continuará a ter valor para detectar uma possível regressão. Debug.Assert(en != null)
DavidRR
@ DavidDRR, estou de fato afirmando que nunca pode ser nulo, e assim é a afirmação no código, daí o nome. É claro que eu poderia estar errado ou estar errado com uma mudança, e esse é o valor da chamada de afirmação.
Jon Hanna
1
As chamadas para Debug.Assert()são removidas em uma compilação Release. Portanto, se você estiver errado, no terceiro caso , você não o conhecerá na produção (assumindo o uso de uma compilação Release na produção). No entanto, o comportamento do primeiro e do segundo casos é idêntico nas compilações de depuração e lançamento.
DavidRR
@DavidRR, que o torna apropriado apenas quando considero que não pode acontecer, pois é novamente uma afirmação de fato, não uma verificação do estado. É claro que também é inútil ter a afirmação, um bug que possa ser detectado e, no entanto, nunca atingir esse caso nos testes.
9119 Jon Hanna
4

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

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

Quando algum chamador de nível superior é modificado por um autor que acredita ter expandido o escopo do código para lidar com cenários adicionais, idealmente (!) Eles escreverão testes de unidade para cobrir essa nova condição. Pode ser que o código totalmente integrado pareça funcionar bem.

No entanto, na verdade, uma falha sutil foi introduzida, mas não detectada nos resultados do teste. O receptor tornou-se não-determinístico, neste caso, e só acontece para fornecer o resultado esperado. Ou talvez tenha gerado um erro de arredondamento despercebido. Ou causou um erro que foi compensado igualmente em outro lugar. Ou concedido não apenas o acesso solicitado, mas privilégios adicionais que não devem ser concedidos. Etc.

Nesse momento, as instruções Debug.Assert () contidas no receptor juntamente com o novo caso (ou caso de borda) acionado por testes de unidade podem fornecer uma notificação inestimável durante o teste de que as suposições do autor original foram invalidadas e o código não deve ser lançado sem revisão adicional. Afirmações 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ários, dadas as premissas iniciais . Por exemplo:

Se um objeto puder ser acessado apenas a partir de um determinado ponto de entrada seguro, uma consulta adicional deve ser feita no banco de dados de direitos de rede de cada método de objeto para garantir que o chamador tenha permissões? Certamente não. Talvez a solução ideal inclua armazenamento em cache ou alguma outra expansão de recursos, mas o design não exige isso. Um Debug.Assert () mostrará imediatamente quando o objeto foi anexado a um ponto de entrada não seguro.

3) Em seguida, em alguns casos, seu produto pode não ter uma interação de diagnóstico útil para todas ou parte de suas operações quando implantado no modo de liberação . Por exemplo:

Suponha que seja um dispositivo incorporado em tempo real. Lançar exceções e reiniciar quando encontrar um pacote mal formado é contraproducente. Em vez disso, o dispositivo pode se beneficiar da operação com o melhor esforço, até o ponto de renderizar ruído em sua saída. Ele também pode não ter uma interface humana, um dispositivo de registro ou até mesmo ser fisicamente acessível por humanos quando implantado no modo de liberação, e a conscientização dos erros é melhor fornecida pela avaliação da mesma saída. Nesse caso, asserções liberais e testes completos de pré-lançamento são mais valiosos que exceções.

4) Por fim, alguns testes não são necessários apenas porque o chamado é percebido como extremamente confiável . Na maioria dos casos, quanto mais reutilizável for o código, mais esforço será feito para torná-lo confiável. Portanto, é comum a exceção para parâmetros inesperados de chamadores, mas afirmar para resultados inesperados de callees. Por exemplo:

Se uma String.Findoperação principal declarar que retornará a -1quando o critério de pesquisa não for encontrado, você poderá executar com segurança uma operação em vez de três. No entanto, se ele realmente retornou -2, você pode não ter um curso de ação razoável. Seria inútil substituir o cálculo mais simples por outro que testa separadamente um -1valor e não é razoável na maioria dos ambientes de lançamento para agrupar seu código com testes, garantindo que as principais bibliotecas estejam operando conforme o esperado. Nesse caso, as afirmações são ideais.

Shannon
fonte
4

Citação tirada do programador pragmático: do viajante ao mestre

Deixar afirmações ativadas

Há um mal-entendido comum sobre asserções, promulgado pelas pessoas que escrevem compiladores e ambientes de linguagem. É algo como isto:

As asserções adicionam alguma sobrecarga ao código. Como eles verificam o que nunca deveria acontecer, eles são acionados apenas por um bug no código. Depois que o código for testado e enviado, ele não será mais necessário e deverá ser desativado para acelerar a execução do código. As asserções são um recurso de depuração.

Existem duas suposições claramente erradas aqui. Primeiro, eles assumem que o teste encontra todos os erros. Na realidade, para qualquer programa complexo é improvável que você teste até uma porcentagem minúscula das permutações que seu código passará (consulte Ruthless Testing).

Segundo, os otimistas estão esquecendo que seu programa é executado em um mundo perigoso. Durante o teste, os ratos provavelmente não vão roer um cabo de comunicação, alguém que joga um jogo não gasta memória e os arquivos de log não enchem 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 se há algum erro possível e a segunda está usando afirmações para tentar detectar aqueles que você perdeu.

Desativar afirmações quando você entrega um programa para produção é como atravessar um fio alto sem rede, porque você o fez na prática . Há um valor dramático, mas é difícil obter seguro de vida.

Mesmo se você tiver problemas de desempenho, desative apenas as afirmações que realmente afetam você .

Teoman shipahi
fonte
2

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

Além disso, se você estiver em produção (e tiver uma versão compilada), é melhor lançar uma exceção (e deixar o aplicativo travar na pior das hipóteses) do que trabalhar com valores inválidos e talvez destruir os dados de seu cliente (que podem custar milhares) de dólares).

Thomas Danecker
fonte
1
Não, o mesmo que algumas outras respostas aqui: você realmente não entende a diferença, então opta por uma das ofertas, criando uma falsa dicotomia entre elas no processo. Dw
Casper Leon Nielsen
3
Esta é a única resposta correta nesta lista IMO. Não descarte tão facilmente Casper. A declaração de depuração é um anti-padrão. Se é invariável no momento da depuração, é invariante no tempo de execução. Permitir que seu aplicativo continue com um invariante quebrado deixa você em um estado não determinístico e com problemas potencialmente piores do que travar. Na IMO, é melhor ter o mesmo código nas duas versões, que falha rapidamente com contratos quebrados e, em seguida, implementa um tratamento robusto de erros no nível superior. por exemplo, isole componentes e implemente a capacidade de reiniciá-los (como uma guia que trava no navegador não trava o navegador inteiro).
usar o seguinte código
1
Pode ser útil incluir Trace.Assert em sua discussão aqui, pois não pode ser descartado pelo mesmo argumento.
Jon Coombs
0

Você deve usar Debug.Assert para testar erros lógicos em seus programas. O complier pode apenas informar sobre erros de sintaxe. Portanto, você deve usar definitivamente instruções Assert para testar erros lógicos. Como dizer testando um programa que vende carros que somente BMWs azuis devem receber um desconto de 15%. O complier não pode lhe dizer nada sobre se o seu programa está logicamente correto ao executar isso, mas uma declaração de afirmação pode.

orlando calresian
fonte
2
desculpe, mas as exceções fazem as mesmas coisas; portanto, esta resposta não aborda a questão real.
11119 Roman Starkov
0

Eu li as respostas aqui e pensei em adicionar uma distinção importante. Existem duas maneiras muito diferentes pelas quais as declarações são usadas. Um é como um atalho temporário de desenvolvedor para "Isso realmente não deve acontecer, se me informar para que eu possa decidir o que fazer", como um ponto de interrupção condicional, nos casos em que seu programa possa continuar. A outra é uma maneira de colocar suposições sobre estados válidos do programa em seu código.

No primeiro caso, as asserções nem precisam estar no código final. Você deve usá-lo Debug.Assertdurante o desenvolvimento e removê-los se / quando não for mais necessário. Se você quiser deixá-los ou se esquecer de removê-los, não há problema, pois eles não terão nenhuma consequência nas compilações do Release.

Mas no segundo caso, as afirmações fazem parte do código. Eles afirmam que suas suposições são verdadeiras e também as documentam. Nesse caso, você realmente deseja deixá-los no código. Se o programa estiver em um estado inválido, não deverá ser permitido continuar. Se você não pudesse pagar pelo desempenho atingido, não usaria C #. Por um lado, pode ser útil anexar um depurador, se isso acontecer. Por outro lado, você não deseja que o rastreamento de pilha apareça em seus usuários e, talvez mais importante, não queira que eles possam ignorá-lo. Além disso, se estiver em um serviço, será sempre ignorado. Portanto, na produção, o comportamento correto seria lançar uma exceção e usar o tratamento de exceção normal do seu programa, o que pode mostrar ao usuário uma boa mensagem e registrar os detalhes.

Trace.Asserttem a maneira perfeita de conseguir isso. Ele não será removido na produção e pode ser configurado com diferentes ouvintes usando app.config. Portanto, para o desenvolvimento, o manipulador padrão é bom, e para a produção, você pode criar um TraceListener simples como abaixo, que gera uma exceção e a ativa no arquivo de configuração da 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>
AlexDev
fonte
-1

Eu não sei como é em C # e .NET, mas em C irá afirmar () só funcionará se compilado com -DDEBUG - o usuário final nunca verá um assert () se for compilado sem. É apenas para desenvolvedor. Eu o uso com muita frequência, às vezes é mais fácil rastrear bugs.

inexistente
fonte
-1

Eu não os usaria no código de produção. Lance exceções, capture e registre.

Também é preciso ter cuidado no asp.net, pois uma declaração pode aparecer no console e congelar as solicitações.

mattlant
fonte