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();
language-agnostic
exception
testing
assertions
defensive-programming
Nicholas Mancuso
fonte
fonte
Respostas:
Na depuração de aplicativos Microsoft .NET 2.0, John Robbins possui uma grande seção sobre asserções. Seus principais pontos são:
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.
fonte
Debug.Assert
vsTrace.Assert
. O último é executado em uma compilação de versão e em uma depuração.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, semDEBUG
constante do compilador), as chamadas paraDebug.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.fonte
Do código completo
fonte
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 usarDebug.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.
fonte
Use asserts para verificar suposições e exceções do desenvolvedor para verificar suposições ambientais.
fonte
Se eu fosse você, faria:
Ou para evitar repetidas verificações de condição
fonte
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.
fonte
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.
fonte
Em resumo
Asserts
são usados para proteções e para verificar as restrições de Design por contrato, a saber:Asserts
deve 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 sistemaAsserts
NÃO são um mecanismo para validação de primeira linha de entrada do usuário ou regras de negóciosAsserts
nã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.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:
Debug
compilações.... Mais detalhes
Debug.Assert
expressa 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. - AsAssert
verificaçõ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 prudenteAssert
que 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 contratadasContract.Ensures
- PostConditions contratadosInvariant
- 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.fonte
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.
fonte
System.Diagnostics.Trace.Assert()
executado em uma compilação de versão e em uma depuração.De acordo com o Padrão IDesign , você deve
Como aviso, devo mencionar que não achei prático implementar esse IRL. Mas esse é o padrão deles.
fonte
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.
fonte
System.Diagnostics.Trace.Assert()
para executar uma asserção em uma compilação de liberação (produção).null
quando: "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çadaArgumentNullException
.Todas as declarações devem ser um código que possa ser otimizado para:
Porque está checando algo que você já assumiu como verdadeiro. Por exemplo:
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
GetFirstAndConsume
com 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 != null
antes de ser chamado, para que não seja verdade é um bug. Ou, em outras palavras, deveria ser um código que, teoricamente, poderia ser otimizadoDebug.Assert(true)
, comoen != null
sempre deve sertrue
!fonte
en == null
em 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)
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.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.Find
operação principal declarar que retornará a-1
quando 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-1
valor 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.fonte
Citação tirada do programador pragmático: do viajante ao mestre
fonte
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).
fonte
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.
fonte
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.Assert
durante 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.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.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.E no arquivo de configuração de produção:
fonte
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.
fonte
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.
fonte