Pergunta

Imagine que um objeto com o qual você está trabalhando possui uma coleção de outros objetos associados a ele, por exemplo, a coleção Controls em um WinForm.Você deseja verificar um determinado objeto na coleção, mas a coleção não possui um Contains() método.Existem várias maneiras de lidar com isso.

  • Implemente o seu próprio Contains() método percorrendo todos os itens da coleção para ver se um deles é o que você está procurando.Esta parece ser a abordagem de "melhores práticas".
  • Recentemente me deparei com um código onde em vez de um loop, houve uma tentativa de acessar o objeto dentro de uma instrução try, como segue:
try  
{  
    Object aObject = myCollection[myObject];  
}  
catch(Exception e)  
{  
    //if this is thrown, then the object doesn't exist in the collection
}

Minha pergunta é quão ruim você considera a segunda opção como prática de programação e por quê?Como é o desempenho dele comparado a um loop pela coleção?

Foi útil?

Solução

Eu teria que dizer que esta é uma prática muito ruim.Embora algumas pessoas possam ficar felizes em dizer que o loop pela coleção é menos eficiente para lançar uma exceção, há uma sobrecarga para lançar uma exceção.Eu também questionaria por que você está usando uma coleção para acessar um item por chave quando seria mais adequado usar um dicionário ou tabela hash.

Meu principal problema com esse código, entretanto, é que, independentemente do tipo de exceção lançada, você sempre terá o mesmo resultado.

Por exemplo, uma exceção pode ser lançada porque o objeto não existe na coleção, ou porque a própria coleção é nula ou porque você não pode converter myCollect[myObject] em aObject.

Todas essas exceções serão tratadas da mesma maneira, o que pode não ser sua intenção.

Estes são alguns artigos interessantes sobre quando e onde é geralmente considerado aceitável lançar exceções:

Gosto particularmente desta citação do segundo artigo:

É importante que ocorram exceções apenas quando ocorre uma atividade inesperada ou inválida que impede que um método conclua sua função normal.O manuseio de exceção introduz uma pequena sobrecarga e diminui o desempenho, portanto, não deve ser usado para o fluxo normal do programa em vez do processamento condicional.Também pode ser difícil manter o código que use o tratamento de exceções dessa maneira.

Outras dicas

A regra geral é evitar o uso de exceções para o fluxo de controle, a menos que as circunstâncias que acionarão a exceção sejam "excepcionais" - por exemplo, extremamente raras!

Se isso acontecer normalmente e regularmente, definitivamente não deve ser tratado como uma exceção.

As exceções são muito, muito lentas devido a toda a sobrecarga envolvida, portanto também pode haver motivos de desempenho, se isso acontecer com frequência suficiente.

Se, ao escrever seu código, você espera que esse objeto esteja na coleção e, durante o tempo de execução, descobrir que não está, eu chamaria isso de caso excepcional e é apropriado usar uma exceção.

No entanto, se você estiver simplesmente testando a existência de um objeto e descobrir que ele não existe, isso não é excepcional.Usar uma exceção neste caso não é adequado.

A análise do desempenho do tempo de execução depende da coleção real que está sendo usada e do método de busca por ela.Isso não deveria importar.Não se deixe enganar pela ilusão da otimização e fazê-lo escrever códigos confusos.

Eu teria que pensar mais sobre o quanto eu gosto disso...meu instinto é, eh, nem tanto...

EDITAR:Os comentários de Ryan Fox sobre o caso excepcional são perfeitos, concordo

Quanto ao desempenho, depende do indexador da coleção.C # permite substituir o operador do indexador, portanto, se ele estiver executando um loop for como o método contains que você escreveria, será igualmente lento (talvez alguns nanossegundos mais lento devido ao try/catch ...mas não há nada com que se preocupar, a menos que o código em si esteja dentro de um loop enorme).

Se o indexador for O(1) (ou mesmo O(log(n))...ou qualquer coisa mais rápida que O(n)), então a solução try/catch seria mais rápida, é claro.

Além disso, estou assumindo que o indexador está lançando a exceção, se estiver retornando nulo, você poderia, é claro, apenas verificar se há nulo e não usar o try/catch.

Em geral, usar o tratamento de exceções para fluxo e lógica do programa é uma prática inadequada.Pessoalmente, considero que a última opção é uma utilização inaceitável de excepções.Dadas as características das linguagens comumente usadas atualmente (como Linq e lambdas em C#, por exemplo), não há razão para não escrever seu próprio método Contains().

Como reflexão final, hoje em dia a maioria das coleções fazer já tem um método contains.Então eu acho que na maior parte isso não é um problema.

As exceções devem ser excepcionais.

Algo como 'A coleção está faltando porque o banco de dados caiu' é excepcional

Algo como 'a chave não está presente' é um comportamento normal para um dicionário.

Para o seu exemplo específico de uma coleção WinForms Control, o Controls propriedade tem um ContainsKey método, que é o que você deve usar.

Não há ContainsValue porque ao lidar com dicionários/hashtables, não há maneira rápida a não ser iterar por toda a coleção, de verificar se algo está presente, então você fica realmente desanimado de fazer isso.

Quanto a POR QUE as exceções devem ser excepcionais, trata-se de duas coisas

  1. Indicando o que seu código está tentando fazer.Você deseja que seu código corresponda ao que ele está tentando alcançar, o mais próximo possível, para que seja legível e de fácil manutenção.O tratamento de exceções adiciona um monte de lixo extra que atrapalha esse propósito

  2. Brevidade do código.Você deseja que seu código faça o que está fazendo da maneira mais direta, para que seja legível e de fácil manutenção.Novamente, o lixo adicionado pelo tratamento de exceções atrapalha isso.

Dê uma olhada nesta postagem do blog de Krzystof: http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx

As exceções devem ser usadas para comunicar condições de erro, mas não devem ser usadas como lógica de controle (especialmente quando há maneiras muito mais simples de determinar uma condição, como Contém).

Parte do problema é que as exceções, embora não sejam dispendiosas lançar são caros para pegar e todas as exceções são detectadas em algum momento.

Esta última é uma solução aceitável.Embora eu definitivamente entendesse a exceção específica (ElementNotFound?) Que a coleção lança nesse caso.

Em termos rápidos, depende do caso comum.Se for mais provável que você encontre o elemento, a solução da exceção será mais rápida.Se houver maior probabilidade de falhar, isso dependerá do tamanho da coleção e da velocidade de iteração.De qualquer forma, você gostaria de comparar com o uso normal para ver se isso é realmente um gargalo antes de se preocupar com uma velocidade como essa.Busque primeiro a clareza, e a última solução será muito mais clara do que a primeira.

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