Pergunta

Acabei de começar a ler 'Debugging MS .Net 2.0 Applications' de John Robbins e fiquei confuso com seu evangelismo para Debug.Assert(...).

Ele ressalta que Asserts bem implementados armazenam, de certa forma, o estado de uma condição de erro, por exemplo:

Debug.Assert(i > 3, "i > 3", "This means I got a bad parameter");

Agora, pessoalmente, parece uma loucura para mim que ele adore reafirmar seu teste sem um comentário realmente sensato de 'lógica de negócios', talvez "i <= 3 nunca deva acontecer por causa do processo de widgitificação flobittyjam".

Então, acho que entendo Asserts como uma espécie de coisa de baixo nível "Vamos proteger minhas suposições" ...supondo que se sinta que este é um teste que só precisa ser feito na depuração - ou seja,você está se protegendo contra colegas e futuros programadores e esperando que eles realmente testem as coisas.

Mas o que não entendi é que ele continua dizendo que você deve usar asserções além do tratamento normal de erros;agora o que imagino é algo assim:

Debug.Assert(i > 3, "i must be greater than 3 because of the flibbity widgit status");
if (i <= 3)
{
    throw new ArgumentOutOfRangeException("i", "i must be > 3 because... i=" + i.ToString());
}

O que ganhei com a repetição Debug.Assert do teste de condição de erro?Acho que entenderia se estivéssemos falando sobre verificação dupla apenas de depuração de um cálculo muito importante ...

double interestAmount = loan.GetInterest();
Debug.Assert(debugInterestDoubleCheck(loan) == interestAmount, "Mismatch on interest calc");

... mas não entendo isso para testes de parâmetros que certamente valem a pena verificar (nas compilações DEBUG e Release) ...ou não.o que estou perdendo?

Foi útil?

Solução

As asserções não são para verificação de parâmetros.A verificação dos parâmetros deve ser sempre feita (e precisamente de acordo com as pré-condições especificadas na sua documentação e/ou especificação), e o ArgumentOutOfRangeException jogado conforme necessário.

As asserções são para testar situações "impossíveis", ou seja, coisas que você (na lógica do seu programa) presumir são verdadeiros.As afirmações existem para lhe dizer se essas suposições foram quebradas por algum motivo.

Espero que isto ajude!

Outras dicas

Há um aspecto de comunicação entre afirmações e lançamento de exceções.

Digamos que temos uma classe User com uma propriedade Name e um método ToString.

Se ToString for implementado assim:

public string ToString()
{
     Debug.Assert(Name != null);
     return Name;
}

Diz que Name nunca deve ser nulo e que há um bug na classe User, se for.

Se ToString for implementado assim:

public string ToString()
{
     if ( Name == null )
     {
          throw new InvalidOperationException("Name is null");
     }

     return Name;
}

Diz que o chamador está usando ToString incorretamente se Name for nulo e deve verificar isso antes de ligar.

A implementação com ambos

public string ToString()
{
     Debug.Assert(Name != null);
     if ( Name == null )
     {
          throw new InvalidOperationException("Name is null");
     }

     return Name;
}

diz que se Name for nulo, haverá um bug na classe User, mas queremos lidar com isso de qualquer maneira.(O usuário não precisa verificar o Nome antes de ligar.) Acho que esse é o tipo de segurança que Robbins estava recomendando.

Eu pensei muito sobre isso quando se trata de fornecer orientação sobre depuração vs.afirmar com relação às preocupações de teste.

Você deve ser capaz de testar sua classe com entrada errada, estado incorreto, ordem de operações inválida e qualquer outra condição de erro concebível e uma afirmação deve nunca viagem.Cada afirmação está verificando algo que deveria sempre ser verdadeiro independentemente das entradas ou cálculos realizados.

Boas regras práticas às quais cheguei:

  1. As declarações não substituem um código robusto que funciona corretamente independentemente da configuração.Eles são complementares.

  2. As declarações nunca devem ser acionadas durante uma execução de teste de unidade, mesmo ao alimentar valores inválidos ou testar condições de erro.O código deve lidar com essas condições sem ocorrer uma afirmação.

  3. Se uma afirmação disparar (em um teste de unidade ou durante o teste), a classe está com bug.

Para todos os outros erros - normalmente relacionados ao ambiente (perda de conexão de rede) ou uso indevido (o chamador passou um valor nulo) - é muito mais agradável e compreensível usar verificações e exceções rígidas.Se ocorrer uma exceção, o chamador sabe que provavelmente a culpa é dele.Se ocorrer uma afirmação, o chamador sabe que provavelmente é um bug no código onde a afirmação está localizada.

Em relação à duplicação:Concordo.Não vejo por que você replicaria a validação com um Debug.Assert E uma verificação de exceção.Isso não apenas adiciona algum ruído ao código e turva as águas sobre quem é o culpado, mas também é uma forma de repetição.

Eu uso verificações explícitas que lançam exceções público e protegido métodos e asserções sobre métodos privados.

Normalmente, as verificações explícitas evitam que os métodos privados vejam valores incorretos de qualquer maneira.Então, na verdade, a afirmação está verificando uma condição que deveria ser impossível.Se uma afirmação for acionada, ela indica que há um defeito na lógica de validação contida em uma das rotinas públicas da classe.

Uma exceção pode ser capturada e engolida, tornando o erro invisível para o teste.Isso não pode acontecer com Debug.Assert.

Ninguém deveria ter um manipulador catch que capturasse todas as exceções, mas as pessoas fazem isso de qualquer maneira, e às vezes é inevitável.Se o seu código for invocado do COM, a camada de interoperabilidade captura todas as exceções e as transforma em códigos de erro COM, o que significa que você não verá as exceções não tratadas.Afirmações não sofrem com isso.

Além disso, quando a exceção não for tratada, uma prática ainda melhor é fazer um minidespejo.Uma área em que o VB é mais poderoso que o C# é que você pode usar um filtro de exceção para capturar um minidespejo quando a exceção estiver em andamento e deixar o restante do tratamento da exceção inalterado. Postagem do blog de Gregg Miskelly sobre injeção de filtro de exceção fornece uma maneira útil de fazer isso em c#.

Outra observação sobre ativos...eles interagem mal com o teste de unidade das condições de erro em seu código.Vale a pena ter um wrapper para desligar o assert dos seus testes unitários.

IMO, é apenas uma perda de tempo de desenvolvimento.A exceção implementada corretamente fornece uma imagem clara do que aconteceu.Eu vi demais aplicativos mostrando obscuro "Asserção falhou:erros <10".Vejo a afirmação como uma solução temporária.Na minha opinião, nenhuma afirmação deveria estar na versão final de um programa.Na minha prática, usei asserções para verificações rápidas e sujas.A versão final do código deve levar em conta a situação errada e se comportar de acordo.Se algo ruim acontecer, você tem duas opções:lidar com isso ou deixá-lo.A função deve lançar uma exceção com uma descrição significativa se parâmetros errados forem passados.Não vejo nenhum ponto na duplicação da lógica de validação.

Exemplo de um bom uso de Assert:

Debug.Assert(flibbles.count() < 1000000, "too many flibbles"); // indicate something is awry
log.warning("flibble count reached " + flibbles.count()); // log in production as early warning

Eu pessoalmente acho que Assert deveria apenas ser usado quando você sabe que algo está fora desejável limites, mas você pode ter certeza de que é razoavelmente seguro continuar.Em todas as outras circunstâncias (sinta-se à vontade para apontar circunstâncias nas quais não pensei) use exceções para falhar forte e rapidamente.

A principal desvantagem para mim é se você deseja desativar um sistema ativo/de produção com uma exceção para evitar corrupção e facilitar a solução de problemas, ou se você encontrou uma situação que nunca deveria passar despercebida nas versões de teste/depuração, mas poderia ter permissão para continuar em produção (registrando um aviso, é claro).

cf. http://c2.com/cgi/wiki?FailFastcopiado e modificado da pergunta java: Exceção versus afirmação

Aqui está por 2 centavos.

Acho que a melhor maneira é usar asserções e exceções.As principais diferenças entre os dois métodos, imho, se as instruções Assert podem ser removidas facilmente do texto do aplicativo (define, atributos condicionais ...), enquanto as exceções lançadas dependem (tipicamente) de um código condicional que é mais difícil de remover ( seção multine com condicionais de pré-processador).

Cada exceção da aplicação deve ser tratada corretamente, enquanto as asserções devem ser satisfeitas apenas durante o desenvolvimento e teste do algoritmo.

Se você passar uma referência de objeto nulo como parâmetro de rotina e usar esse valor, obterá uma exceção de ponteiro nulo.De fato:por que você deve escrever uma afirmação?É uma perda de tempo neste caso.Mas e os membros da classe privada usados ​​nas rotinas da aula?Quando esses valores são definidos em algum lugar, é melhor verificar com uma asserção se um valor nulo está definido.Isso ocorre apenas porque quando você usa o membro, você recebe uma exceção de ponteiro nulo, mas não sabe como o valor foi definido.Isso causa uma reinicialização do programa, interrompendo todos os pontos de entrada usados ​​para definir o membro privado.

As exceções são mais úteis, mas podem ser (imho) muito pesadas de gerenciar e existe a possibilidade de usar muitas exceções.E requerem verificação adicional, talvez indesejada para otimizar o código.Pessoalmente, uso exceções apenas quando o código requer um controle catch profundo (as instruções catch estão muito baixas na pilha de chamadas) ou sempre que os parâmetros da função não estão codificados no código.

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