No final dos anos 90, trabalhei bastante com uma base de código que usava exceções como controle de fluxo. Ele implementou uma máquina de estado finito para direcionar aplicativos de telefonia. Ultimamente, sou lembrado desses dias porque tenho feito aplicativos da Web MVC.
Ambos têm Controller
s que decidem para onde ir em seguida e fornecem os dados para a lógica de destino. As ações do usuário do domínio de um telefone antigo, como tons DTMF, tornaram-se parâmetros para os métodos de ação, mas, em vez de retornar algo como a ViewResult
, eles deram a StateTransitionException
.
Eu acho que a principal diferença foi que os métodos de ação eram void
funções. Não me lembro de todas as coisas que fiz com esse fato, mas hesitei em lembrar de muita coisa porque, desde aquele trabalho, como há 15 anos, nunca vi isso no código de produção em nenhum outro trabalho . Eu assumi que isso era um sinal de que era um chamado anti-padrão.
É esse o caso? Em caso afirmativo, por quê?
Atualização: quando fiz a pergunta, eu já tinha em mente a resposta do @ MasonWheeler, então fui com a resposta que mais me agradava. Eu acho que a dele também é uma boa resposta.
fonte
Respostas:
Há uma discussão detalhada sobre isso no Wiki de Ward . Geralmente, o uso de exceções para controle de fluxo é um anti-padrão, com muitos situação notável - e de idioma específico (ver, por exemplo Python ) tosse exceções tosse .
Como um resumo rápido do motivo, geralmente, é um antipadrão:
Leia a discussão no wiki de Ward para obter informações muito mais detalhadas.
Veja também uma duplicata desta pergunta, aqui
fonte
if
eforeach
também são sofisticadosGOTO
. Francamente, acho que a comparação com goto não é útil; é quase como um termo depreciativo. O uso do GOTO não é intrinsecamente mau - ele tem problemas reais e as exceções podem compartilhá-los, mas não podem. Seria mais útil ouvir esses problemas.O caso de uso para o qual as exceções foram criadas é "Acabei de encontrar uma situação com a qual não posso lidar adequadamente neste momento, porque não tenho contexto suficiente para lidar com isso, mas a rotina que me chamou (ou algo mais adiante na chamada pilha) deve saber como lidar com isso ".
O caso de uso secundário é "Acabei de encontrar um erro grave e, neste momento, sair desse fluxo de controle para evitar corrupção de dados ou outros danos é mais importante do que tentar continuar adiante".
Se você não estiver usando exceções por um desses dois motivos, provavelmente há uma maneira melhor de fazer isso.
fonte
const myvar = theFunction
porque o myvar precisa ser criado a partir do try-catch, assim, não é mais constante. Isso não significa que eu não o uso em C #, pois é um mainstream lá por qualquer motivo, mas estou tentando reduzi-los de qualquer maneira.Exceções são tão poderosas quanto Continuações e
GOTO
. Eles são uma construção de fluxo de controle universal.Em alguns idiomas, eles são o único construto de fluxo de controle universal. O JavaScript, por exemplo, não tem Continuações nem
GOTO
possui chamadas de cauda apropriadas. Então, se você quiser implementar o fluxo de controle sofisticados em JavaScript, você tem de usar exceções.O projeto Microsoft Volta era um projeto de pesquisa (agora descontinuado) para compilar código .NET arbitrário em JavaScript. O .NET possui exceções cuja semântica não é exatamente mapeada para JavaScript, mas, mais importante, possui Threads , e você precisa mapeá-las de alguma forma para JavaScript. Volta fez isso implementando Volta Continuations usando exceções JavaScript e, em seguida, implemente todas as construções de fluxo de controle .NET em termos de Volta Continuations. Eles tiveram que usar Exceções como fluxo de controle, porque não há outra construção de fluxo de controle suficientemente poderosa.
Você mencionou State Machines. As SMs são triviais para implementar com chamadas de cauda apropriadas: todo estado é uma sub-rotina, toda transição de estado é uma chamada de sub-rotina. As SMs também podem ser facilmente implementadas com
GOTO
Corotinas ou Continuações. No entanto, Java não tem qualquer um desses quatro, mas não tem exceções. Portanto, é perfeitamente aceitável usá-los como fluxo de controle. (Bem, na verdade, a escolha correta provavelmente seria usar uma linguagem com a construção de fluxo de controle apropriada, mas às vezes você pode ficar com o Java.)fonte
Como outros já mencionaram numerosamente ( por exemplo, nesta questão do Stack Overflow ), o princípio de menor espanto proibirá o uso excessivo de exceções apenas para fins de controle de fluxo. Por outro lado, nenhuma regra é 100% correta e sempre há casos em que uma exceção é "a ferramenta certa" - a exemplo de
goto
si mesma, a propósito, que é fornecida na formabreak
econtinue
em linguagens como Java, que geralmente são a maneira perfeita de saltar de loops muito aninhados, que nem sempre são evitáveis.A seguinte postagem no blog explica um caso de uso bastante complexo, mas também bastante interessante, para um não local
ControlFlowException
:Ele explica como dentro do jOOQ (uma biblioteca de abstração SQL para Java) (isenção de responsabilidade: eu trabalho para o fornecedor), essas exceções são ocasionalmente usadas para abortar o processo de renderização SQL mais cedo, quando alguma condição "rara" é atendida.
Exemplos de tais condições são:
Muitos valores de ligação são encontrados. Alguns bancos de dados não oferecem suporte a números arbitrários de valores de ligação em suas instruções SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). Nesses casos, o jOOQ interrompe a fase de renderização do SQL e renderiza novamente a instrução SQL com valores de ligação embutidos. Exemplo:
Se extraíssemos explicitamente os valores de ligação da AST da consulta para contá-los todas as vezes, desperdiçamos ciclos valiosos de CPU para 99,9% das consultas que não sofrem com esse problema.
Alguma lógica está disponível apenas indiretamente por meio de uma API que queremos executar apenas "parcialmente". O
UpdatableRecord.store()
método gera uma instruçãoINSERT
ouUPDATE
, dependendoRecord
dos sinalizadores internos do. Do lado de fora, não sabemos em que tipo de lógica está contidastore()
(por exemplo, bloqueio otimista, manipulação de ouvinte de eventos etc.), portanto, não queremos repetir essa lógica quando armazenamos vários registros em uma instrução em lote, onde gostaríamos destore()
gerar apenas a instrução SQL e não executá-la. Exemplo:Se tivéssemos externalizado a
store()
lógica na API "reutilizável" que pode ser personalizada para opcionalmente não executar o SQL, estaríamos tentando criar uma API bastante difícil de manter e dificilmente reutilizável.Conclusão
Em essência, nosso uso desses
goto
s não locais é exatamente como o que Mason Wheeler disse em sua resposta:Ambos os usos
ControlFlowExceptions
foram bastante fáceis de implementar em comparação com suas alternativas, permitindo reutilizar uma ampla gama de lógica sem refatorá-la dos internos relevantes.Mas a sensação de que isso é uma surpresa para os futuros mantenedores permanece. O código parece bastante delicado e, embora tenha sido a escolha certa nesse caso, sempre preferimos não usar exceções para o fluxo de controle local , onde é fácil evitar o uso de ramificações comuns
if - else
.fonte
O uso de exceções para o fluxo de controle é geralmente considerado um antipadrão, mas há exceções (sem trocadilhos).
Já foi dito mil vezes que exceções são destinadas a condições excepcionais. Uma conexão de banco de dados interrompida é uma condição excepcional. Um usuário digitando letras em um campo de entrada que deve permitir apenas números não é .
Um bug no seu software que faz com que uma função seja chamada com argumentos ilegais, por exemplo,
null
onde não é permitido, é uma condição excepcional.Usando exceções para algo que não é excepcional, você está usando abstrações inadequadas para o problema que está tentando resolver.
Mas também pode haver uma penalidade de desempenho. Alguns idiomas têm uma implementação de manipulação de exceção mais ou menos eficiente; portanto, se o seu idioma de escolha não tiver um tratamento de exceção eficiente, pode ser muito caro, em termos de desempenho *.
Mas outros idiomas, por exemplo, Ruby, têm uma sintaxe semelhante a exceção para o fluxo de controle. Situações excepcionais são tratadas pelos operadores
raise
/rescue
. Mas você pode usarthrow
/catch
para construções de fluxo de controle de exceção **.Portanto, embora as exceções geralmente não sejam usadas para o fluxo de controle, seu idioma de escolha pode ter outros idiomas.
* Por exemplo, o uso caro de exceções de desempenho: uma vez fui definido para otimizar um aplicativo ASP.NET Web Form com desempenho insatisfatório. Aconteceu que a renderização de uma mesa grande estava exigindo
int.Parse()
aprox. mil cadeias de caracteres vazias em uma página média, resultando em aprox. mil exceções sendo tratadas. Ao substituir o código porint.TryParse()
eu raspei um segundo! Para cada solicitação de página única!** Este pode ser muito confuso para um programador que vem para Ruby de outras línguas, pois ambos
throw
ecatch
são palavras-chave associadas com exceções em muitas outras línguas.fonte
É completamente possível lidar com condições de erro sem o uso de exceções. Alguns idiomas, principalmente o C, nem sequer têm exceções, e as pessoas ainda conseguem criar aplicativos bastante complexos com ele. A razão pela qual as exceções são úteis é que elas permitem especificar sucintamente dois fluxos de controle essencialmente independentes no mesmo código: um se ocorrer um erro e outro se não ocorrer. Sem eles, você acaba com o código em todo o lugar que se parece com isso:
Ou equivalente no seu idioma, como retornar uma tupla com um valor como status de erro, etc. Muitas vezes as pessoas que apontam o quão caro é o tratamento de exceções, negligenciam todas as
if
instruções extras como acima que você precisará adicionar se não Não use exceções.Embora esse padrão ocorra com mais frequência ao lidar com erros ou outras "condições excepcionais", na minha opinião, se você começar a ver código padrão como esse em outras circunstâncias, terá um bom argumento para usar exceções. Dependendo da situação e implementação, posso ver as exceções sendo usadas validamente em uma máquina de estados, porque você tem dois fluxos de controle ortogonais: um que está alterando o estado e outro para os eventos que ocorrem dentro dos estados.
No entanto, essas situações são raras e, se você fizer uma exceção (trocadilhos) à regra, é melhor estar preparado para mostrar sua superioridade em relação a outras soluções. Um desvio sem essa justificativa é justamente chamado de antipadrão.
fonte
No Python, as exceções são usadas para o encerramento do gerador e da iteração. O Python possui blocos try / except muito eficientes, mas, na verdade, gerar uma exceção tem alguma sobrecarga.
Devido à falta de quebras de vários níveis ou uma declaração goto no Python, às vezes eu usei exceções:
fonte
return
em vez degoto
.try:
blocos em 1 ou 2 linhas, por favor, nuncatry: # Do lots of stuff
.Vamos esboçar esse uso de exceção:
O algoritmo pesquisa recursivamente até que algo seja encontrado. Então, voltando da recursão, é preciso verificar o resultado para ser encontrado e retornar, caso contrário, continue. E isso repetidamente voltando de alguma profundidade de recursão.
Além de precisar de um booleano extra
found
(para ser empacotado em uma classe, onde, caso contrário, talvez apenas um int tivesse sido retornado), e para a profundidade da recursão, o mesmo postlude acontece.Esse desenrolar de uma pilha de chamadas é exatamente o que é uma exceção. Portanto, parece-me um meio de codificação não-goto, mais imediato e apropriado. Não é necessário, uso raro, talvez com estilo ruim, mas direto ao ponto. Comparável com a operação de corte Prolog.
fonte
Programação é sobre trabalho
Eu acho que a maneira mais fácil de responder a isso é entender o progresso que a OOP fez ao longo dos anos. Tudo o que é feito no OOP (e na maioria dos paradigmas de programação) é modelado em torno da necessidade de trabalho .
Toda vez que um método é chamado, o chamador está dizendo "Eu não sei como fazer esse trabalho, mas você sabe como, então você faz isso por mim".
Isso apresentou uma dificuldade: o que acontece quando o método chamado geralmente sabe como fazer o trabalho, mas nem sempre? Precisávamos de uma maneira de nos comunicar "Eu realmente queria ajudá-lo, mas realmente não posso fazer isso".
Uma metodologia inicial para comunicar isso era simplesmente retornar um valor "lixo". Talvez você espere um número inteiro positivo, portanto o método chamado retorna um número negativo. Outra maneira de conseguir isso era definir um valor de erro em algum lugar. Infelizmente, as duas formas resultaram no código padrão, deixe-me verificar aqui para garantir que tudo é kosher . À medida que as coisas ficam mais complicadas, esse sistema desmorona.
Uma analogia excepcional
Digamos que você tenha carpinteiro, encanador e eletricista. Você quer encanador para consertar sua pia, então ele dá uma olhada nela. Não é muito útil se ele disser apenas a você: "Desculpe, não posso consertar. Está quebrado." Inferno, é ainda pior se ele der uma olhada, sair e enviar uma carta para você dizendo que não poderia consertar. Agora você precisa verificar seu e-mail antes mesmo de saber que ele não fez o que você queria.
O que você prefere é que ele lhe diga: "Olha, eu não consegui consertar, porque parece que sua bomba não está funcionando".
Com essas informações, você pode concluir que deseja que o eletricista dê uma olhada no problema. Talvez o eletricista encontre algo relacionado ao carpinteiro, e você precisará que ele conserte.
Caramba, você pode nem saber que precisa de um eletricista, pode não saber de quem precisa. Você é apenas gerente de nível médio em um negócio de consertos domésticos e seu foco é o encanamento. Então você diz a seu chefe sobre o problema e ele diz ao eletricista para consertá-lo.
É isso que as exceções estão modelando: modos de falha complexos de maneira dissociada. O encanador não precisa saber sobre eletricista - ele nem precisa saber que alguém na cadeia pode resolver o problema. Ele apenas relata o problema que encontrou.
Então ... um anti-padrão?
Ok, entender o ponto de exceção é o primeiro passo. O próximo é entender o que é um antipadrão.
Para se qualificar como um antipadrão, ele precisa
O primeiro ponto é facilmente encontrado - o sistema funcionou, certo?
O segundo ponto é mais pegajoso. O principal motivo para usar exceções como fluxo de controle normal é ruim é porque esse não é o objetivo delas. Qualquer funcionalidade fornecida em um programa deve ter um objetivo relativamente claro, e a cooptação desse objetivo gera confusão desnecessária.
Mas isso não é um dano definitivo . É uma maneira ruim de fazer as coisas, e estranha, mas um anti-padrão? Não. Apenas ... estranho.
fonte