Argumentos a favor ou contra o uso de Try / Catch como operadores lógicos [closed]

36

Acabei de descobrir um código adorável em nosso aplicativo de empresas que usa blocos Try-Catch como operadores lógicos.
Ou seja, "faça algum código, se isso gerar esse erro, faça esse código, mas se isso gerar esse erro, faça essa terceira coisa".
Ele usa "Finalmente" como a declaração "else" que aparece.
Eu sei que isso está errado por natureza, mas antes de começar uma briga, eu esperava alguns argumentos bem pensados.
E ei, se você tiver argumentos para o uso do Try-Catch dessa maneira, informe.

Para quem está se perguntando, o idioma é C # e o código em questão tem mais de 30 linhas e está procurando exceções específicas, não está tratando TODAS as exceções.

James P. Wright
fonte
13
Só tenho argumentos contra essa prática e, como você já parece convencido de que é uma má ideia, não vou me incomodar em publicá-la.
FrustratedWithFormsDesigner
15
@FrustratedWIthFormsDesigner: Esse é um raciocínio ruim. E as pessoas que não estão convencidas? E a minha pergunta real, onde eu pergunto especificamente por motivos, já que não sei realmente "por que" apenas que sei que está errado.
James P. Wright
3
Minhas opiniões dependem muito do que o código em questão realmente faz. Há coisas que não podem ser verificadas antecipadamente (ou permitem condições de corrida ao tentar, como muitas operações de arquivo - no atraso entre a verificação e a operação, tudo pode acontecer com o arquivo) e precisam ser try'd. Nem todo caso excepcional que justifique uma exceção em geral deve ser fatal neste caso específico. Então, você poderia fazer isso de uma maneira mais simples, igual ou mais robusta sem usar exceções?
11
Espero que eles realmente não usem o bloco "finally" como um "else" porque o bloco "finally" é sempre executado após o código anterior, independentemente de quaisquer exceções lançadas.
jimreed
2
Qual idioma eles estão usando? É perfeitamente bom, digamos, no OCaml, onde a biblioteca padrão lança exceções rotineiramente. O tratamento de exceções é muito barato lá. Mas não será tão eficiente na CLI ou na JVM.
SK-logic

Respostas:

50

O tratamento de exceções tende a ser uma maneira cara de lidar com o controle de fluxo (certamente para C # e Java).

O tempo de execução faz bastante trabalho quando um objeto de exceção é construído - reunindo o rastreamento da pilha, descobrindo onde a exceção é tratada e muito mais.

Tudo isso custa em recursos de memória e CPU que não precisam ser expandidos se instruções de controle de fluxo forem usadas para controle de fluxo.

Além disso, há um problema semântico. Exceções são para situações excepcionais, não para controle de fluxo normal. Deve-se usar o tratamento de exceções para lidar com situações imprevistas / excepcionais, não como o fluxo normal do programa, pois, caso contrário, uma exceção não capturada informará muito menos.

Além desses dois, há o problema de outras pessoas lerem o código. Usar exceções dessa maneira não é algo que a maioria dos programadores esperará, portanto, a legibilidade e o quão compreensível seu código é afetado. Quando alguém vê "Exceção", pensa - algo ruim aconteceu, algo que não deveria acontecer normalmente. Portanto, usar exceções dessa maneira é simplesmente confuso.

Oded
fonte
11
Um bom argumento sobre o desempenho, embora isso também dependa das especificidades da plataforma.
FrustratedWithFormsDesigner
4
Se essa é a única desvantagem, bem, obrigado - vou desistir do desempenho a qualquer dia se ele me comprar um código melhor (exceto em caminhos críticos para o desempenho), mas felizmente isso representa apenas 20% de todo o código, mesmo em aplicativos que geralmente são de desempenho -crítico).
5
@ delnan - Não, não é a única desvantagem.
Oded
2
Além da palavra não-aceita, concordo com a sua postagem. Há momentos no seu código em que as exceções acontecem. A maioria deles deve ser previsível, como falhas de conexão com o banco de dados ou uma chamada de serviço ou sempre que você estiver lidando com código não gerenciado. Você pega problemas fora do seu controle e lida com eles. Mas concordo que você nunca deve ter controle de fluxo com base em exceções que você pode evitar dentro do seu código.
precisa saber é o seguinte
4
"O tratamento de exceção tende a ser uma maneira cara de lidar com o controle de fluxo". Falso para Python.
S.Lott
17

Acabei de descobrir um código adorável em nosso aplicativo de empresas que usa blocos Try-Catch como operadores lógicos. Ou seja, "faça algum código, se isso gerar esse erro, faça esse código, mas se isso gerar esse erro, faça essa terceira coisa". Ele usa "Finalmente" como a declaração "else" que aparece. Eu sei que isso está errado inerentemente ...

Como você sabe disso? Desisti de todo esse tipo de "conhecimento" e agora acredito que o código mais simples é o melhor. Suponha que você deseje converter uma cadeia de caracteres em um Opcional que esteja vazio se a análise falhar. Não há nada errado com:

try { 
    return Optional.of(Long.valueOf(s)); 
} catch (NumberFormatException) { 
    return Optional.empty(); 
}

Discordo completamente da interpretação usual de "Exceções são para condições excepcionais". Quando uma função não pode retornar um valor utilizável ou um método não pode atender às suas pós-condições, lance uma exceção. Não importa com que frequência essas exceções são lançadas até que haja um problema de desempenho demonstrado.

As exceções simplificam o código, permitindo a separação do tratamento de erros do fluxo normal. Basta escrever o código mais simples possível e, se for mais fácil usar o try-catch ou lançar uma exceção, faça isso.

Exceções simplificam o teste, reduzindo o número de caminhos através do código. Uma função sem ramificações será concluída ou lançará uma exceção. Uma função com várias ifinstruções para verificar códigos de erro possui muitos caminhos possíveis. É muito fácil errar uma das condições ou esquecê-las completamente, para que alguma condição de erro seja ignorada.

kevin cline
fonte
4
Eu acho que esse é realmente um argumento muito bom. Seu código é limpo e conciso e faz sentido no que está fazendo. Na verdade, é semelhante ao que está acontecendo no código que estou vendo, mas é muito mais complexo, está aninhado e abrange mais de 30 linhas de código.
James P. Wright
@ James: parece que o código seria bom se você extraísse alguns métodos menores.
Kevin cline
11
Alguém poderia argumentar que o Java poderia realmente usar algo mais como o TryParse do C #. Em C # esse código seria int i; if(long.TryParse(s, out i)) return i; else return null;. TryParse retorna false se a sequência não puder ser analisada. Se puder ser analisado, ele define o argumento de saída com os resultados da análise e retorna true. Nenhuma exceção ocorre (mesmo internamente no TryParse) e, especialmente, nenhuma exceção do tipo que significa erro de programador, como o .NETs FormatExceptionsempre indica.
Kevin Cathcart
11
@ Kevin Cathcart, mas por que isso é melhor? o código que captura NumberFormatException é muito mais óbvio para mim do que verificar o resultado do TryParse por um nulo.
Winston Ewert 13/09
11
@ChrisMiskowiec, eu suspeito que qual você achar mais claro é basicamente uma questão do que você está acostumado. Acho a captura da exceção muito mais fácil de seguir do que a verificação de valores mágicos. Mas isso é tudo subjetivo. Objetivamente, acho que devemos preferir exceções porque elas têm um padrão sadio (travar o programa), e não um padrão que oculta um bug (continue como se nada tivesse acontecido). É verdade que o .NET tem um desempenho ruim, com exceções. Mas isso é resultado do design do .NET, não da natureza das exceções. Se estiver no .NET, sim, você precisará fazer isso. Mas, em princípio, as exceções são melhores.
Winston Ewert
12

O trabalho de depuração e manutenção é muito difícil quando o fluxo de controle é executado usando exceções.

Inerentemente, as exceções são projetadas para serem um mecanismo para alterar o fluxo de controle normal do seu programa - de realizar atividades incomuns, causando efeitos colaterais incomuns, como uma maneira de sair de um vínculo particularmente rígido que não pode ser tratado com menos complicações. significa. Exceções são excepcionais. Isso significa que, dependendo do ambiente em que você está trabalhando, o uso de exceção no fluxo de controle regular pode causar:

  • Ineficiência Os obstáculos adicionais pelos quais o ambiente precisa passar para executar com segurança as mudanças de contexto relativamente difíceis necessárias para exceções requerem instrumentação e recursos.

  • Dificuldades de depuração Às vezes, informações úteis (ao tentar depurar um programa) são lançadas pela janela quando ocorrem exceções. Você pode perder o controle do estado ou histórico do programa relevante para entender o comportamento em tempo de execução

  • Problemas de manutenção O fluxo de execução é difícil de seguir através de saltos de exceção. Além disso, uma exceção pode ser lançada de dentro do código do tipo caixa preta, que pode não se comportar de maneiras fáceis de entender ao lançar uma exceção.

  • Más decisões de projeto Os programas criados dessa maneira incentivam um estado de espírito que, na maioria dos casos, não é facilmente mapeado para a solução de problemas com elegância. A complexidade do programa final desencoraja o programador de entender completamente sua execução e incentiva a tomada de decisões que levam a melhorias de curto prazo com altos custos de longo prazo.

blueberryfields
fonte
10

Eu já vi esse padrão usado várias vezes.

Existem 2 problemas principais:

  • É extremamente caro (instanciação do objeto de exceção, coleta da pilha de chamadas etc.). Alguns compiladores podem até mesmo otimizá-lo, mas eu não contaria com isso neste caso, porque as exceções não se destinam a esse uso; portanto, você não pode esperar que as pessoas o otimizem.
  • Usar exceções para o fluxo de controle é, de fato, um salto, bem como um goto. Os saltos são considerados prejudiciais, por várias razões. Eles devem ser usados, se todas as alternativas tiverem desvantagens consideráveis. De fato, em todo o meu código, lembro apenas de 2 casos, onde os saltos eram claramente a melhor solução.
back2dos
fonte
2
Apenas divulgando, se / else também é considerado um salto. A diferença é que é um salto que só pode ocorrer naquele local específico (e é muito mais fácil) e você não precisa se preocupar com os nomes dos rótulos (o que não é necessário com a tentativa / captura , mas comparando com goto). Mas apenas dizer "é um salto, o que é ruim" também diz que se / mais é ruim, quando não é.
jsternberg
11
@jsternbarg: Sim, se / else estiver de fato compilado para saltos no nível da máquina, mas no nível da linguagem, o alvo do salto é a próxima instrução, enquanto no caso de loops, é a declaração atual. Eu diria que isso é mais um passo, de um salto;)
back2dos
9

Às vezes, as exceções são mais rápidas. Vi casos em que as exceções de objetos nulos eram mais rápidas, mesmo em Java, do que o uso de estruturas de controle (não posso citar o estudo no momento, então você terá que confiar em mim). O problema surge quando o Java precisa levar algum tempo e preencher o rastreamento de pilha de uma classe de exceção personalizada em vez de usar as nativas (que parecem estar pelo menos parcialmente em cache). Antes de dizer que algo é unilateralmente mais rápido ou mais lento, seria bom fazer um benchmark.

No Python, não é apenas mais rápido, mas é muito mais correto fazer algo que possa causar uma exceção e depois manipular o erro. Sim, você pode aplicar um sistema de tipos, mas isso contraria a filosofia da linguagem - em vez disso, você deve simplesmente tentar chamar o método e obter o resultado! (Testar se um arquivo é gravável é semelhante - tente gravá-lo e pegue o erro).

Eu já vi momentos em que é mais rápido fazer algo estúpido como tabelas de consulta que não existiam do que descobrir se existe uma tabela no PHP + MySQL (a questão em que eu comparo essa é na verdade minha única resposta aceita com votos negativos) .

Tudo isso dito, o uso de exceções deve ser limitado por vários motivos:

  1. Engolir acidentalmente de exceções aninhadas. Isso é importante. Se você pegar alguma exceção profundamente aninhada com a qual alguém está tentando lidar, você acabou de dar um tiro no seu colega programador (talvez até você!).
  2. Os testes se tornam não óbvios. Um bloco de código que possui uma exceção pode ter uma de várias coisas erradas nele. Um booleano, por outro lado, embora teoricamente possa ser irritante para depurar, geralmente não é. Isso é especialmente verdadeiro porque os try...catchadeptos do fluxo de controle geralmente (na minha experiência) não seguem a filosofia "minimizar código no bloco de tentativa".
  3. Não permite um else ifbloco. Já disse o suficiente (e se alguém responder com um "Mas eles poderiam usar classes de exceções diferentes", minha resposta é "Vá para o seu quarto e não saia até que você tenha pensado no que disse").
  4. É gramaticalmente enganador. Exception, para o resto do mundo (como não aderentes à try...catchfilosofia de controle-fluxo), significa que algo entrou em um estado instável (embora talvez recuperável). Estados instáveis ​​são ruins e devem nos manter acordados à noite se realmente tivermos exceções evitáveis ​​(na verdade, me assusta, sem mentiras).
  5. Não é compatível com o estilo de codificação comum. Nosso trabalho, tanto com nosso código quanto com nossa interface do usuário, é tornar o mundo o mais óbvio possível. try...catcho fluxo de controle vai contra o que são comumente consideradas melhores práticas. Isso significa que leva mais tempo para alguém novato em um projeto aprender o projeto, o que significa um aumento no número de horas-homem para absolutamente nenhum ganho.
  6. Isso geralmente leva à duplicação de código. Finalmente, os blocos, embora isso não seja estritamente necessário, precisam resolver todos os ponteiros pendentes que foram deixados em aberto pelo bloco try interrompido. Então tente blocos. Isso significa que você pode ter um try{ obj.openSomething(); /*something which causes exception*/ obj.doStuff(); obj.closeSomething();}catch(Exception e){obj.closeSomething();}. Em um if...elsecenário mais tradicional, closeSomething()é menos provável (novamente, experiência pessoal) ser um trabalho de copiar e colar. (É certo que esse argumento em particular tem mais a ver com pessoas que conheci do que com a própria filosofia).
cwallenpoole
fonte
O AFAIK, nº 6, é um problema apenas em Java, que, diferente de qualquer outra linguagem moderna, não possui fechamentos nem gerenciamento de recursos baseados em pilha. Certamente não é um problema inerente ao uso de exceções.
Kevin cline
4 e 5 parecem ser o mesmo ponto para mim. 3-6 não se aplicam se o seu idioma for python. 1 e 2 são problemas em potencial, mas acho que eles são tratados minimizando o código no bloco try.
Winston Ewert 13/09
11
@Winston eu discordo. 1 e 2 são questões importantes que, embora reduzidas por melhores padrões de codificação, ainda nos fazem pensar se talvez não seja melhor simplesmente evitar o possível erro inicial. 3 se aplica se o seu idioma for Python. 4-5 também podem se inscrever no Python, mas eles não precisam. 4 e 5 são pontos muito diferentes - enganar é diferente das diferenças estilísticas. Código enganoso pode estar fazendo algo como nomear o gatilho para dar partida no veículo, "botão de parada". Estilísticamente, por outro lado, pode também ser análogo para evitar todo o recuo do bloco em C.
cwallenpoole
3) Python tem um outro bloco para exceções. É maciçamente útil para evitar os problemas que você normalmente obter para 1 e 2.
Winston Ewert
11
Só para esclarecer, não estou defendendo o uso de exceções como seu mecanismo geral de controle de fluxo. Defendo a criação de uma exceção para qualquer tipo de modo "falha", por menor que seja. Penso que a versão de tratamento de exceções é mais limpa e menos provável de ocultar erros do que a versão de verificação do valor de retorno. E, idealmente, gostaria que os idiomas tornassem mais difícil a captura de erros aninhados.
Winston Ewert 13/09
4

Meu argumento principal é que o uso de try / catch para a lógica interrompe o fluxo lógico. Seguir a "lógica" através de construções não-lógicas é (para mim) contra-intuitivo e confuso. Estou acostumado a ler minha lógica como "se Condição então A mais B". Ler a mesma instrução que "Tente uma captura e execute B" é estranho. Seria ainda mais estranho se a declaração Afosse uma atribuição simples, exigindo código extra para forçar uma exceção se conditionfor falsa (e fazer isso provavelmente exigiria uma ifdeclaração de qualquer maneira).

FrustratedWithFormsDesigner
fonte
2

Bem, apenas uma questão de etiqueta, antes de "iniciar uma discussão com eles", como você afirma, gostaria de perguntar "Por que eles usam tratamento de exceção em todos esses lugares diferentes?"

Quero dizer, existem várias possibilidades:

  1. eles são incompetentes
  2. existe uma razão perfeitamente válida para o que eles fizeram, que pode não aparecer à primeira vista.
  3. às vezes é uma questão de gosto e pode simplificar algum fluxo de programa complexo.
  4. É uma relíquia do passado que ninguém mudou ainda e eles ficariam felizes se alguém fizer isso

... Eu acho que tudo isso é igualmente provável. Então, basta perguntar-lhes bem.

dagnelies
fonte
1

O Try Catch só deve ser usado para manipulação de exceções. Mais ainda, manipulação de exceção específica. Sua tentativa de captura deve capturar apenas as exceções esperadas; caso contrário, ela não está bem formada. Se você precisar usar um catch all try catch, provavelmente está fazendo algo errado.

EDITAR:

Razão: Você pode argumentar que, se você usar o try catch como um operador condicional, como você considerará as exceções REAL?

AJC
fonte
2
O OP está pedindo razões / argumentos. Você não forneceu nenhum.
Oded
"Você pode argumentar que, se você usar o try catch como um operador condicional, como você considerará as exceções REAL?" - Capturando essas exceções reais em uma catchcláusula diferente daquela que captura exceções "não reais"?
@ delnan - Obrigado! você provou um ponto que eu estava prestes a fazer. Se uma pessoa respondesse o que você disse à minha razão e à de Oded, eu pararia de falar porque ele não está procurando por razões pelas quais está apenas sendo teimoso e não perco meu tempo com teimosia.
AJC 12/09
Você está me chamando de recém-nascido porque aponto falhas em alguns dos argumentos listados aqui? Observe como não tenho nada a dizer contra outros pontos mencionados aqui. Eu não sou fã de usar exceções para controle de fluxo (apesar de não condenar nada sem detalhes, portanto, minha consulta para elaboração pelo OP), estou apenas interpretando o advogado do diabo.
11
Usar o try-catch como um operador condicional de forma alguma impede que ele funcione para capturar exceções "reais".
Winston Ewert
1

Exceções são para quando coisas excepcionais acontecem. O programa está funcionando de acordo com o fluxo de trabalho regular excepcional?

jfrobishow
fonte
11
Mas por que? O objetivo da pergunta é por que (ou por que não) as exceções são boas apenas para isso.
Winston Ewert
Nem sempre "excepcional". Não encontrar uma chave em uma hashtable não é excepcional, por exemplo, e ainda assim a biblioteca OCaml tende a gerar uma exceção nesses casos. E tudo bem - o tratamento de exceções é muito barato lá.
SK-logic
11
-1: declaração tautológica
Cookies de farinha de arroz
1

Razão para usar exceções:

  1. Se você esquecer de capturar uma circunstância excepcional, seu programa morrerá e o rastreamento da pilha informará exatamente o motivo. Se você esquecer de manipular o valor de retorno para uma circunstância de exceção, não há indicação de quão longe o seu programa exibirá um comportamento incorreto.
  2. O uso de valores de retorno funciona apenas se houver um valor sentinela que você possa retornar. Se todos os possíveis valores de retorno já forem válidos, o que você fará?
  3. Uma exceção conterá informações adicionais sobre o que aconteceu, que podem ser úteis.

Razões para não usar exceções:

  1. Muitos idiomas não foram projetados para tornar as exceções rápidas.
  2. Exceções que percorrem várias camadas acima da pilha podem deixar um estado inconsistente em seu rastro

No final:

O objetivo é escrever um código que comunique o que está acontecendo. Exceções podem ajudar / dificultar isso, dependendo do que o código está fazendo. Uma tentativa / captura em python para um KeyError em uma referência de dicionário é perfeitamente (desde que você conheça o idioma) a tentativa / captura para o mesmo KeyError a cinco camadas de função é perigosa.

Winston Ewert
fonte
0

Eu uso try-> catch como controle de fluxo em determinadas situações. Se sua lógica principal depende de algo que falha, mas você não deseja apenas lançar uma exceção e sair ... É para isso que serve um bloco try-> catch. Eu escrevo muitos scripts unix do lado do servidor não monitorados e é muito mais importante para eles não falharem do que para eles falharem lindamente.

Portanto, tente o Plano A, e se o Plano A morrer, pegue e execute com o Plano B ... E se o plano B falhar, use finalmente para iniciar o Plano C, que corrigirá um dos serviços com falha em A ou B ou na página mim.

Satanicpuppy
fonte
0

Depende do idioma e talvez da implementação que está sendo usada. O padrão em C ++ é que as exceções devem ser salvas para eventos verdadeiramente excepcionais. Por outro lado, em Python, uma das diretrizes é " é mais fácil pedir perdão do que permissão "; portanto, a integração de blocos try / except na lógica do programa é incentivada.

Charles E. Grant
fonte
"O padrão em C ++ é que exceções devem ser salvas para eventos verdadeiramente excepcionais." Isso não faz sentido. Até o inventor da linguagem discorda de você .
Arayq2 27/10/2014
-1

É um mau hábito, mas faço-o de vez em quando em python. Como, em vez de verificar se existe um arquivo, apenas tento excluí-lo. Se isso der uma exceção, eu pego e continuo sabendo que não estava lá. <== (não necessariamente verdadeiro se outro usuário possuir o arquivo, mas eu o faço no diretório inicial do usuário para que isso NÃO DEVE acontecer).

Ainda assim, usar em excesso é uma prática ruim.

RobotHumans
fonte
3
Nesse exemplo, usar exceções em vez de "olhar antes de pular" é realmente uma boa idéia: evita as condições de corrida. Uma verificação da disponibilidade do arquivo (existência, permissões, não sendo bloqueado) ter êxito não significa que um acesso posterior (mesmo que seja apenas alguns códigos de código posteriores) será bem sucedido (como abrir, excluir, mover, bloquear, o que for), como o arquivo poderia ser alterado (excluído, movido, com suas permissões alteradas) no meio.
Se o python está lançando uma exceção apenas porque um arquivo não existe, parece estar encorajando maus hábitos, como delnan descreve.
Per Johansson
@ delnan: Embora seja verdade, IMHO se for provável que ocorra nessas condições de corrida, você deve repensar seu design. Nesse caso, há claramente uma falta de separação de preocupações em seus sistemas. A menos que você tenha boas razões para fazer as coisas dessa maneira, você deve unificar o acesso do seu lado ou usar um banco de dados adequado para lidar com vários clientes que tentam executar transações nos mesmos dados persistentes.
back2dos
11
Este não é um mau hábito. É o estilo recomendado em Python.
Winston Ewert
@ back2dos, você pode evitá-lo ao lidar com recursos externos, como outros usuários do banco de dados / sistema de arquivos?
Winston Ewert
-1

Gosto da maneira como a Apple o define : as exceções são apenas para erros de programadores e erros fatais de tempo de execução. Caso contrário, use códigos de erro. Isso me ajuda a decidir quando usá-lo, pensando comigo mesmo: "Isso foi um erro de programador?"

Per Johansson
fonte