Pergunta

Já me deparei com o seguinte tipo de código muitas vezes e me pergunto se esta é uma boa prática (do ponto de vista do desempenho) ou não:

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

Basicamente, o que o codificador está fazendo é englobar a exceção em uma exceção personalizada e lançá-la novamente.

Como isso difere em desempenho dos dois seguintes:

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

ou

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

Deixando de lado quaisquer argumentos de práticas recomendadas funcionais ou de codificação, há alguma diferença de desempenho entre as três abordagens?

Foi útil?

Solução

@Brad Tutterow

A exceção não está sendo perdida no primeiro caso, ela está sendo passada para o construtor.Concordo com você no resto, porém, a segunda abordagem é uma péssima ideia por causa da perda de rastreamento de pilha.Quando trabalhei com .NET, me deparei com muitos casos em que outros programadores fizeram exatamente isso, e fiquei extremamente frustrado quando precisei ver a verdadeira causa de uma exceção, apenas para descobrir que ela estava sendo lançada novamente a partir de um enorme bloco try onde Agora não tenho ideia de onde o problema se originou.

Também concordo com o comentário de Brad de que você não deveria se preocupar com o desempenho.Esse tipo de micro otimização é uma ideia HORRÍVEL.A menos que você esteja falando sobre lançar uma exceção em cada iteração de um loop for que está em execução por um longo tempo, é mais do que provável que você não tenha problemas de desempenho devido ao uso da exceção.

Sempre otimize o desempenho quando você tiver métricas que indiquem que você PRECISA otimizar o desempenho e, em seguida, atinja os pontos que comprovadamente são os culpados.

É muito melhor ter código legível com recursos fáceis de depuração (ou seja, sem ocultar o rastreamento de pilha) em vez de fazer algo rodar um nanossegundo mais rápido.

Uma nota final sobre agrupar exceções em uma exceção personalizada...esta pode ser uma construção muito útil, especialmente ao lidar com UIs.Você pode agrupar todos os casos excepcionais conhecidos e razoáveis ​​em alguma exceção personalizada básica (ou uma que se estenda da referida exceção básica) e então a UI pode simplesmente capturar essa exceção básica.Quando detectada, a exceção precisará fornecer meios de exibir informações ao usuário, digamos, uma propriedade ReadableMessage ou algo nesse sentido.Portanto, sempre que a UI perde uma exceção, é por causa de um bug que você precisa corrigir, e sempre que ela detecta uma exceção, é uma condição de erro conhecida que pode e deve ser tratada adequadamente pela UI.

Outras dicas

Obviamente você incorre na penalidade de criar novos objetos (a nova Exceção), então, exatamente como você faz com cada linha de código que você anexa ao seu programa, você deve decidir se a melhor categorização das exceções compensa o trabalho extra.

Como um conselho para tomar essa decisão, se seus novos objetos não estiverem carregando informações extras sobre a exceção, você poderá esquecer a construção de novas exceções.

Porém, em outras circunstâncias, ter uma hierarquia de exceções é muito conveniente para o usuário de suas classes.Suponha que você esteja implementando o padrão Façade, nenhum dos cenários considerados até agora é bom:

  1. não é bom que você levante todas as exceções como um objeto Exception porque você está perdendo (provavelmente) informações valiosas
  2. não é bom nem levantar todo tipo de objeto que você pega porque fazendo isso você está falhando na criação da fachada

Neste caso hipotético, a melhor coisa a fazer é criar uma hierarquia de classes de exceção que, abstraindo seus usuários das complexidades internas do sistema, lhes permita saber algo sobre o tipo de exceção produzida.

Como uma nota rodapé:

Pessoalmente, não gosto do uso de exceções (hierarquias de classes derivadas da classe Exception) para implementar lógica.Como no caso:

try {
        // something that will raise an exception almost half the time
} catch( InsufficientFunds e) {
        // Inform the customer is broke
} catch( UnknownAccount e ) {
        // Ask for a new account number
}

Assim como David, suponho que o segundo e o terceiro tenham melhor desempenho.Mas será que algum dos três teria um desempenho ruim o suficiente para passar algum tempo se preocupando com isso?Acho que há problemas maiores do que o desempenho para se preocupar.

FxCop sempre recomenda a terceira abordagem em vez da segunda para que o rastreamento de pilha original não seja perdido.

Editar:Removi coisas que estavam simplesmente erradas e Mike teve a gentileza de apontar.

Não faça:

try
{
    // some code
}
catch (Exception ex) { throw ex; }

Pois isso perderá o rastreamento de pilha.

Em vez disso, faça:

try
{
    // some code
}
catch (Exception ex) { throw; }

Basta lançar, você só precisa passar a variável de exceção se quiser que ela seja a exceção interna em uma nova exceção personalizada.

Como outros já afirmaram, o melhor desempenho vem de baixo, já que você está apenas relançando um objeto existente.A do meio é a menos correta porque perde a pilha.

Eu pessoalmente uso exceções personalizadas se quiser desacoplar certas dependências no código.Por exemplo, tenho um método que carrega dados de um arquivo XML.Isso pode dar errado de muitas maneiras diferentes.

Pode haver falha na leitura do disco (FileIOException), o usuário pode tentar acessá-lo de algum lugar onde não é permitido (SecurityException), o arquivo pode estar corrompido (XmlParseException), os dados podem estar no formato errado (DeserialisationException).

Nesse caso, é mais fácil para a classe chamadora entender tudo isso, todas essas exceções lançam novamente uma única exceção personalizada (FileOperationException), de modo que significa que o chamador não precisa de referências a System.IO ou System.Xml, mas ainda pode acesse qual erro ocorreu através de um enum e qualquer informação importante.

Como afirmado, não tente micro-otimizar algo assim, o ato de lançar uma exceção é a coisa mais lenta que ocorre aqui.A melhor melhoria a fazer é tentar evitar qualquer exceção.

public bool Load(string filepath)
{
  if (File.Exists(filepath)) //Avoid throwing by checking state
  {
    //Wrap anyways in case something changes between check and operation
    try { .... }
    catch (IOException ioFault) { .... }
    catch (OtherException otherFault) { .... }
    return true; //Inform caller of success
  }
  else { return false; } //Inform caller of failure due to state
}

O lance no seu primeiro exemplo tem a sobrecarga da criação de um novo objeto CustomException.

O relançamento em seu segundo exemplo lançará uma exceção do tipo Exception.

O relançamento no seu terceiro exemplo lançará uma exceção do mesmo tipo que foi lançada pelo seu "algum código".

Portanto, o segundo e o terceiro exemplos utilizam menos recursos.

Espere....por que nos preocupamos com o desempenho se uma exceção for lançada?A menos que estejamos usando exceções como parte do fluxo normal do aplicativo (o que é MUITO contra as práticas recomendadas).

Só vi requisitos de desempenho em relação ao sucesso, mas nunca em relação ao fracasso.

Do ponto de vista puramente de desempenho, acho que o terceiro caso tem o melhor desempenho.Os outros dois precisam extrair um rastreamento de pilha e construir novos objetos, ambos potencialmente demorados.

Dito isto, estes três blocos de código têm muito comportamentos diferentes (externos), portanto compará-los é como perguntar se o QuickSort é mais eficiente do que adicionar um item a uma árvore vermelha e preta.Não é tão importante quanto selecionar a coisa certa a fazer.

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