Por que as exceções devem ser usadas de maneira conservadora?

Muitas vezes vejo / ouço pessoas dizendo que as exceções devem ser usadas raramente, mas nunca explicam por quê. Embora isso possa ser verdade, a lógica é normalmente simplista: “é chamada de exceção por uma razão” , o que, para mim, parece ser o tipo de explicação que nunca deveria ser aceita por um respeitável programador / engenheiro.

Existe uma variedade de problemas que uma exceção pode ser usada para resolver. Por que é insensato usá-las para o stream de controle? Qual é a filosofia por trás de ser excepcionalmente conservadora com a forma como eles são usados? Semântica? Atuação? Complexidade? Estética? Convenção?

Já vi algumas análises sobre desempenho antes, mas em um nível que seria relevante para alguns sistemas e irrelevante para outros.

Novamente, eu não discordo necessariamente que eles devem ser salvos para circunstâncias especiais, mas eu estou querendo saber qual é o raciocínio de consenso (se tal coisa existe).

O principal ponto de atrito é a semântica. Muitos desenvolvedores abusam de exceções e as jogam em todas as oportunidades. A ideia é usar exceção para uma situação excepcional. Por exemplo, a input errada do usuário não conta como uma exceção porque você espera que isso aconteça e pronto para isso. Mas se você tentou criar um arquivo e não havia espaço suficiente no disco, então sim, essa é uma exceção definitiva.

Uma outra questão é que as exceções são frequentemente lançadas e engolidas. Os desenvolvedores usam essa técnica para simplesmente “silenciar” o programa e deixá-lo rodar o maior tempo possível até o colapso total. Isso está muito errado. Se você não processar exceções, se você não reagir apropriadamente liberando alguns resources, se você não registrar a ocorrência da exceção ou pelo menos não notificar o usuário, então você não está usando exceção para o que eles significam.

Respondendo diretamente a sua pergunta. Exceções raramente devem ser usadas porque situações excepcionais são raras e as exceções são caras.

Raro, porque você não espera que o seu programa trave a cada vez que o botão for pressionado ou a cada input do usuário malformada. Digamos, o database pode de repente não estar acessível, pode não haver espaço suficiente no disco, alguns serviços de terceiros dos quais você depende estão off-line, tudo isso pode acontecer, mas raramente, esses casos seriam claros e excepcionais.

Caro, porque lançar uma exceção interromperá o stream normal do programa. O tempo de execução desenrolará a pilha até encontrar um manipulador de exceção apropriado que possa manipular a exceção. Ele também coletará as informações da chamada durante todo o caminho a ser passado para o object de exceção que o manipulador receberá. Tudo tem custos.

Isso não quer dizer que não haja exceção ao uso de exceções (smile). Às vezes, pode simplificar a estrutura do código se você lançar uma exceção em vez de encaminhar os códigos de retorno por várias camadas. Como uma regra simples, se você espera que algum método seja chamado com frequência e descubra uma situação “excepcional” na metade do tempo, é melhor encontrar outra solução. Se, no entanto, você espera o stream normal de operação na maior parte do tempo, enquanto essa situação “excepcional” só pode surgir em algumas circunstâncias raras, então é bom jogar uma exceção.

@Comments: A exceção definitivamente pode ser usada em algumas situações menos excepcionais, se isso tornar seu código mais simples e fácil. Esta opção está aberta, mas eu diria que é bastante raro na prática.

Por que é insensato usá-las para o stream de controle?

Porque as exceções interrompem o “stream de controle” normal. Você gera uma exceção e a execução normal do programa é abandonada, potencialmente deixando objects em estado inconsistente e alguns resources abertos não definidos. Claro, C # tem a instrução using, que fará com que o object seja descartado, mesmo que uma exceção seja lançada do corpo de uso. Mas vamos abstrair no momento da linguagem. Suponha que a estrutura não disponha de objects para você. Você faz isso manualmente. Você tem algum sistema para solicitar e liberar resources e memory. Você tem um acordo em todo o sistema que é responsável por liberar objects e resources em quais situações. Você tem regras sobre como lidar com bibliotecas externas. Funciona muito bem se o programa segue o stream normal de operação. Mas, de repente, no meio da execução, você lança uma exceção. Metade dos resources não são remunerados. Metade ainda não foi solicitada. Se a operação foi feita para ser transacional agora, ela está quebrada. Suas regras para manipular resources não funcionarão porque essas partes do código responsáveis ​​por liberar resources simplesmente não serão executadas. Se alguém mais quiser usar esses resources, eles poderão encontrá-los em estado inconsistente e falhar, pois não poderão prever essa situação específica.

Digamos, você queria um método M () chamada método N () para fazer algum trabalho e arranjar algum recurso, em seguida, devolvê-lo de volta para M (), que irá usá-lo e, em seguida, eliminá-lo. Bem. Agora algo dá errado em N () e lança uma exceção que você não esperava em M (), então a exceção chega ao topo até que seja capturada em algum método C () que não fará ideia do que estava acontecendo no fundo em N () e se e como liberar alguns resources.

Com o lançamento de exceções, você cria uma maneira de trazer seu programa para muitos novos estados intermediários imprevisíveis, que são difíceis de prognosticar, entender e lidar. É um pouco semelhante ao uso de GOTO. É muito difícil criar um programa que possa saltar aleatoriamente sua execução de um local para outro. Também será difícil manter e depurar. Quando o programa cresce em complexidade, você só vai perder uma visão geral de quando e onde está acontecendo menos para consertá-lo.

Enquanto “lançar exceções em circunstâncias excepcionais” é a resposta simplista, você pode definir quais são essas circunstâncias: quando condições prévias são satisfeitas, mas pós-condições não podem ser satisfeitas . Isso permite que você escreva pós-condições mais estritas, mais rígidas e mais úteis sem sacrificar o tratamento de erros; caso contrário, sem exceções, você precisará alterar a pós-condição para permitir todos os possíveis estados de erro.

  • Pré-condições devem ser verdadeiras antes de chamar uma function.
  • Póscondição é o que a function garante após o retorno .
  • A segurança de exceção informa como as exceções afetam a consistência interna de uma function ou estrutura de dados e geralmente lidam com o comportamento transmitido de fora (por exemplo, functor, ctor de um parâmetro de modelo, etc.).

Construtores

Há muito pouco que você pode dizer sobre cada construtor para cada class que poderia ser escrita em C ++, mas há algumas coisas. O principal deles é que os objects construídos (isto é, para os quais o construtor foi bem sucedido retornando) serão destruídos. Você não pode modificar essa pós-condição porque a linguagem assume que é verdadeira e chamará os destruidores automaticamente. (Tecnicamente, você pode aceitar a possibilidade de um comportamento indefinido para o qual a linguagem não garante nada, mas isso provavelmente é melhor abordado em outros lugares.)

A única alternativa para lançar uma exceção quando um construtor não pode ter sucesso é modificar a definição básica da class (a “invariante de class”) para permitir estados “nulos” ou zumbis válidos e assim permitir que o construtor “tenha sucesso” construindo um zumbi .

Exemplo de zumbi

Um exemplo dessa modificação de zumbi é std :: ifstream , e você deve sempre verificar seu estado antes de poder usá-lo. Porque std :: string , por exemplo, não, você está sempre garantido que você pode usá-lo imediatamente após a construção. Imagine se você tivesse que escrever código como este exemplo, e se você esqueceu de verificar o estado de zumbi, você poderia silenciosamente obter resultados incorretos ou corromper outras partes do seu programa:

string s = "abc"; if (s.memory_allocation_succeeded()) { do_something_with(s); // etc. } 

Mesmo nomear esse método é um bom exemplo de como você deve modificar a class ‘invariant’ e a interface para uma string de situação não pode prever nem manipular a si mesma.

Exemplo de validação de input

Vamos abordar um exemplo comum: validar a input do usuário. Só porque queremos permitir a input com falha não significa que a function de análise precisa include isso em sua pós-condição. Isso significa que nosso manipulador precisa verificar se o analisador falha, no entanto.

 // boost::lexical_cast() is the parsing function here void show_square() { using namespace std; assert(cin); // precondition for show_square() cout < < "Enter a number: "; string line; if (!getline(cin, line)) { // EOF on cin // error handling omitted, that EOF will not be reached is considered // part of the precondition for this function for the sake of example // // note: the below Python version throws an EOFError from raw_input // in this case, and handling this situation is the only difference // between the two } int n; try { n = boost::lexical_cast(line); // lexical_cast returns an int // if line == "abc", it obviously cannot meet that postcondition } catch (boost::bad_lexical_cast&) { cout < < "I can't do that, Dave.\n"; return; } cout << n * n << '\n'; } 

Infelizmente, isso mostra dois exemplos de como o escopo do C ++ requer que você quebre o RAII / SBRM. Um exemplo em Python que não tem esse problema e mostra algo que eu gostaria que o C ++ tivesse - try-else:

 # int() is the parsing "function" here def show_square(): line = raw_input("Enter a number: ") # same precondition as above # however, here raw_input will throw an exception instead of us # using assert try: n = int(line) except ValueError: print "I can't do that, Dave." else: print n * n 

Condições prévias

As condições prévias não precisam ser verificadas estritamente - violar uma sempre indica uma falha lógica e elas são de responsabilidade do responsável pela chamada - mas, se você as verificar, a exceção é apropriada. (Em alguns casos, é mais apropriado devolver o lixo ou travar o programa; embora essas ações possam estar terrivelmente erradas em outros contextos. Como lidar melhor com comportamentos indefinidos é outro tópico.)

Em particular, contraste as ramificações std :: logic_error e std :: runtime_error da hierarquia de exceções stdlib. O primeiro é frequentemente usado para violações de pré-condição, enquanto o segundo é mais adequado para violações de pós-condição.

  1. Caro
    chamadas de kernel (ou outras invocações de API do sistema) para gerenciar interfaces de sinal do kernel (sistema)
  2. Difícil de analisar
    Muitos dos problemas da declaração goto aplicam a exceções. Eles pulam potencialmente grandes quantidades de código, muitas vezes em várias rotinas e arquivos de origem. Isso nem sempre é aparente na leitura do código-fonte intermediário. (Está em Java.)
  3. Nem sempre antecipado por código intermediário
    O código que é saltado pode ou não ter sido escrito com a possibilidade de uma exceção ser lembrada. Se originalmente assim escrito, pode não ter sido mantido com isso em mente. Pense: vazamentos de memory, vazamentos de descritor de arquivo, vazamentos de soquete, quem sabe?
  4. Complicações de manutenção
    É mais difícil manter o código que salta em torno de exceções de processamento.

Lançar uma exceção é, até certo ponto, semelhante a uma instrução goto. Faça isso para controle de stream e você acaba com um código de espaguete incompreensível. Pior ainda, em alguns casos, você nem sabe exatamente para onde o salto vai (ou seja, se você não está capturando a exceção no contexto dado). Isso descaradamente viola o princípio da “menor surpresa” que aumenta a capacidade de manutenção.

Exceções dificultam raciocinar sobre o estado do seu programa. Em C ++, por exemplo, você tem que pensar extra para garantir que suas funções sejam altamente seguras contra exceções, do que você teria que fazer se elas não precisassem ser.

A razão é que, sem exceções, uma chamada de function pode retornar ou pode terminar o programa primeiro. Com exceções, uma chamada de function pode retornar, ou pode terminar o programa, ou pode saltar para um bloco de captura em algum lugar. Assim, você não pode mais seguir o stream de controle apenas observando o código à sua frente. Você precisa saber se as funções chamadas podem jogar. Você pode precisar saber o que pode ser jogado e onde é capturado, dependendo se você se importa para onde o controle vai, ou apenas se importa que ele deixe o escopo atual.

Por esta razão, as pessoas dizem “não use exceções, a menos que a situação seja realmente excepcional”. Quando você chegar a ele, “realmente excepcional” significa “alguma situação ocorreu onde os benefícios de lidar com um valor de retorno de erro são compensados ​​pelos custos”. Então, sim, isso é algo de uma declaração vazia, embora uma vez que você tenha alguns instintos para “realmente excepcional”, torna-se uma boa regra prática. Quando as pessoas falam sobre controle de stream, elas significam que a capacidade de raciocinar localmente (sem referência a blocos de captura) é um benefício dos valores de retorno.

Java tem uma definição mais ampla de “realmente excepcional” que o C ++. É mais provável que os programadores de C ++ vejam o valor de retorno de uma function que os programadores de Java, portanto, em Java “realmente excepcional” pode significar “Não posso retornar um object não nulo como resultado dessa function”. Em C ++, é mais provável que signifique “duvido muito que o meu interlocutor possa continuar”. Assim, um stream Java será lançado se não puder ler um arquivo, enquanto um stream C ++ (por padrão) retornará um valor indicando erro. Em todos os casos, porém, é uma questão de qual código você está disposto a forçar seu interlocutor a escrever. Então, é realmente uma questão de estilo de codificação: você tem que chegar a um consenso de como seu código deve ser, e quanto código de “verificação de erros” você quer escrever em relação ao raciocínio de “exceção de segurança” que você quer fazer.

O amplo consenso em todos os idiomas parece ser o melhor modo de obter a recuperação do erro (já que erros irrecuperáveis ​​resultam em nenhum código com exceções, mas ainda precisam de uma verificação e retorno de sua propriedade). erro no código que usa retornos de erro). Então as pessoas esperam que “essa function que eu chamo lança uma exceção” signifique ” não posso continuar”, não apenas ” não pode continuar”. Isso não é inerente às exceções, é apenas um costume, mas como qualquer boa prática de programação, é um costume defendido por pessoas inteligentes que tentaram de outra maneira e não gostaram dos resultados. Eu também tive experiências ruins lançando muitas exceções. Então, pessoalmente, eu penso em termos de “realmente excepcional”, a menos que algo sobre a situação faça uma exceção particularmente atraente.

Btw, além de raciocinar sobre o estado do seu código, também há implicações de desempenho. Exceções são geralmente baratas agora, em idiomas onde você tem o direito de se preocupar com o desempenho. Eles podem ser mais rápidos do que vários níveis de “oh, o resultado é um erro, é melhor eu sair com um erro também, então”. Nos velhos tempos ruins, havia receios reais de que lançar uma exceção, pegá-la e continuar com a próxima coisa, tornaria o que você está fazendo tão lento a ponto de ser inútil. Então, nesse caso, “realmente excepcional” significa “a situação é tão ruim que o desempenho horrível não importa mais”. Isso não é mais o caso (embora uma exceção em um loop apertado ainda seja perceptível) e esperançosamente indica por que a definição de “realmente excepcional” precisa ser flexível.

Realmente não há consenso. A questão toda é um pouco subjetiva, porque a “adequação” de lançar uma exceção é frequentemente sugerida pelas práticas existentes dentro da biblioteca padrão da própria linguagem. A biblioteca padrão C ++ lança exceções com muito menos freqüência do que a biblioteca padrão Java, que quase sempre prefere exceções, mesmo para erros esperados, como input de usuário inválida (por exemplo, Scanner.nextInt ). Isso, acredito, influencia significativamente as opiniões dos desenvolvedores sobre quando é apropriado lançar uma exceção.

Como programador C ++, eu pessoalmente prefiro reservar exceções para circunstâncias muito “excepcionais”, por exemplo, sem memory, sem espaço em disco, o apocalipse acontecido, etc. Mas eu não insisto que esta é a maneira correta de fazer coisas.

Eu não acho que exceções raramente deveriam ser usadas. Mas.

Nem todas as equipes e projetos estão prontos para usar exceções. O uso de exceções requer alta qualificação de programadores, técnicas especiais e falta de código legado que não seja seguro de exceção. Se você tem uma grande e velha base de código, então quase sempre não é uma exceção. Tenho certeza de que você não quer reescrevê-lo.

Se você for usar exceções extensivamente, então:

  • estar preparado para ensinar seu povo sobre o que a segurança de exceção é
  • você não deve usar o gerenciamento de memory bruta
  • usar RAII extensivamente

Por outro lado, usar exceções em novos projetos com uma equipe forte pode tornar o código mais limpo, mais fácil de manter e ainda mais rápido:

  • você não vai perder ou ignorar erros
  • você não precisa escrever as verificações dos códigos de retorno, sem saber o que fazer com o código errado em nível baixo
  • quando você é forçado a escrever código seguro para exceção, ele se torna mais estruturado

EDIT 11/20/2009 :

Eu estava lendo este artigo do MSDN sobre como melhorar o desempenho do código gerenciado e esta parte me lembrou dessa questão:

O custo de desempenho de lançar uma exceção é significativo. Embora o tratamento estruturado de exceções seja a maneira recomendada de lidar com condições de erro, certifique-se de usar exceções apenas em circunstâncias excepcionais, quando ocorrerem condições de erro. Não use exceções para o stream de controle regular.

Naturalmente, isso é apenas para .NET, e também é direcionado especificamente para aqueles que desenvolvem aplicativos de alto desempenho (como eu); então obviamente não é uma verdade universal. Ainda assim, existem muitos desenvolvedores .NET lá fora, então eu senti que era importante notar.

EDITAR :

OK, antes de tudo, vamos esclarecer uma coisa: não tenho intenção de brigar com ninguém pela questão de desempenho. Em geral, na verdade, estou inclinado a concordar com aqueles que acreditam que a otimização prematura é um pecado. No entanto, deixe-me apenas fazer dois pontos:

  1. O autor está pedindo uma lógica objetiva por trás da sabedoria convencional de que as exceções devem ser usadas com parcimônia. Podemos discutir a legibilidade e design adequado tudo o que queremos; mas estes são assuntos subjetivos com pessoas prontas para discutir em ambos os lados. Eu acho que o cartaz está ciente disso. O fato é que o uso de exceções para controlar o stream do programa é muitas vezes uma maneira ineficiente de fazer as coisas. Não, nem sempre , mas frequentemente . É por isso que é um conselho razoável usar as exceções com moderação, assim como é um bom conselho comer carne vermelha ou beber vinho com moderação.

  2. Há uma diferença entre otimizar sem um bom motivo e escrever código eficiente. O corolário disso é que há uma diferença entre escrever algo robusto, se não otimizado, e algo que é simplesmente ineficiente. Às vezes eu acho que quando as pessoas discutem sobre coisas como exception handling, elas estão realmente falando umas com as outras, porque estão discutindo coisas fundamentalmente diferentes.

Para ilustrar o meu ponto, considere os seguintes exemplos de código C #.

Exemplo 1: Detectando input de usuário inválida

Este é um exemplo do que eu chamaria de abuso de exceção.

 int value = -1; string input = GetInput(); bool inputChecksOut = false; while (!inputChecksOut) { try { value = int.Parse(input); inputChecksOut = true; } catch (FormatException) { input = GetInput(); } } 

Este código é, para mim, ridículo. Claro que funciona . Ninguém está discutindo com isso. Mas deve ser algo como:

 int value = -1; string input = GetInput(); while (!int.TryParse(input, out value)) { input = GetInput(); } 

Exemplo 2: Verificando a existência de um arquivo

Eu acho que esse cenário é realmente muito comum. Certamente parece muito mais “aceitável” para muitas pessoas, já que lida com arquivos de I / O:

 string text = null; string path = GetInput(); bool inputChecksOut = false; while (!inputChecksOut) { try { using (FileStream fs = new FileStream(path, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { text = sr.ReadToEnd(); } } inputChecksOut = true; } catch (FileNotFoundException) { path = GetInput(); } } 

Isso parece razoável o suficiente, certo? Estamos tentando abrir um arquivo; se não estiver lá, pegamos essa exceção e tentamos abrir um arquivo diferente … O que há de errado com isso?

Nada realmente. Mas considere esta alternativa, que não apresenta nenhuma exceção:

 string text = null; string path = GetInput(); while (!File.Exists(path)) path = GetInput(); using (FileStream fs = new FileStream(path, FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { text = sr.ReadToEnd(); } } 

É claro que, se o desempenho dessas duas abordagens fosse realmente o mesmo, isso seria puramente uma questão doutrinária. Então, vamos dar uma olhada. Para o primeiro exemplo de código, fiz uma lista de 10000 strings aleatórias, nenhuma das quais representava um inteiro adequado e, em seguida, adicionei uma string inteira válida no final. Usando ambas as abordagens acima, estes foram os meus resultados:

Usando o bloco try / catch : 25.455 segundos
Usando int.TryParse : 1.637 milissegundos

Para o segundo exemplo, eu fiz basicamente a mesma coisa: fiz uma lista de 10000 strings aleatórias, nenhuma das quais era um caminho válido, então adicionei um caminho válido até o final. Estes foram os resultados:

Usando o bloco try / catch : 29.989 segundos
Usando o File.Exists : 22.820 milissegundos

Muitas pessoas responderiam a isso dizendo: “Sim, bem, jogar e pegar 10.000 exceções é extremamente irreal; isso exagera os resultados”. Claro que sim. A diferença entre lançar uma exceção e manipular uma input incorreta por conta própria não será perceptível para o usuário. O fato é que o uso de exceções é, nesses dois casos, de 1.000 a 10.000 vezes mais lento do que as abordagens alternativas que são tão legíveis – se não mais.

É por isso que incluí o exemplo do método GetNine() abaixo. Não é que seja intoleravelmente lento ou inaceitavelmente lento; é que é mais devagar do que deveriapor nenhuma boa razão .

Mais uma vez, estes são apenas dois exemplos. É claro que haverá momentos em que o impacto no desempenho de usar exceções não é tão grave (Pavel está certo; afinal, isso depende da implementação). Tudo que estou dizendo é: vamos encarar os fatos, caras – em casos como o descrito acima, jogar e pegar uma exceção é análogo a GetNine() ; é apenas uma maneira ineficiente de fazer algo que poderia facilmente ser feito melhor .


Você está pedindo uma justificativa como se essa fosse uma daquelas situações em que todos pularam em um movimento sem saber por quê. Mas, na verdade, a resposta é óbvia, e acho que você já sabe disso. O tratamento de exceções tem um desempenho horrendo.

OK, talvez seja bom para o seu cenário de negócios em particular, mas relativamente falando , jogando / pegando uma exceção introduz muito mais sobrecarga do que é necessário em muitos, muitos casos. Você sabe disso, eu sei disso: na maioria das vezes , se você está usando exceções para controlar o stream do programa, está apenas escrevendo código lento.

Você também pode perguntar: por que esse código é ruim?

 private int GetNine() { for (int i = 0; i < 10; i++) { if (i == 9) return i; } } 

Eu apostaria que, se você fizesse o perfil desta function, você perceberia que ela funciona de forma bastante aceitável para o seu aplicativo de negócios típico. Isso não muda o fato de que é uma maneira horrivelmente ineficiente de realizar algo que poderia ser feito muito melhor.

Isso é o que as pessoas querem dizer quando falam sobre "abuso" de exceção.

Todas as regras gerais sobre exceções se resumem a termos subjetivos. Você não deve esperar obter definições duras e rápidas de quando usá-las e quando não. “Apenas em circunstâncias excepcionais”. Boa definição circular: as exceções são para circunstâncias excepcionais.

Quando usar exceções, cai no mesmo intervalo de “como sei se esse código é uma ou duas classs?” É em parte uma questão estilística, em parte uma preferência. Exceções são uma ferramenta. Eles podem ser usados ​​e abusados, e encontrar a linha entre os dois é parte da arte e habilidade de programação.

Há muitas opiniões e tradeoffs a serem feitas. Encontre algo que fale com você e siga-o.

Não é que as exceções raramente sejam usadas. É só que eles só devem ser jogados em circunstâncias excepcionais. Por exemplo, se um usuário digitar a senha errada, isso não é excepcional.

O motivo é simples: as exceções saem abruptamente de uma function e propagam a pilha até um bloco catch . Esse processo é muito dispendioso em termos computacionais: o C ++ constrói seu sistema de exceções para ter pouca sobrecarga em chamadas de function “normais”, portanto, quando uma exceção é levantada, ele precisa fazer muito trabalho para descobrir aonde ir. Além disso, uma vez que cada linha de código poderia levantar uma exceção. Se temos alguma function f que levanta exceções frequentemente, agora temos que tomar cuidado para usar nossos blocos try / catch torno de cada chamada de f . Isso é um acoplamento de interface / implementação muito ruim.

Mencionei esse problema em um artigo sobre exceções do C ++ .

A parte relevante:

Quase sempre, usar exceções para afetar o stream “normal” é uma má ideia. Como já discutimos na seção 3.1, as exceções geram caminhos de código invisíveis. Esses caminhos de código são aceitáveis ​​se forem executados apenas nos cenários de tratamento de erros. No entanto, se usarmos exceções para qualquer outra finalidade, nossa execução de código “normal” é dividida em uma parte visível e invisível e torna o código muito difícil de ler, entender e estender.

My approach to error handling is that there are three fundamental types of errors:

  • An odd situation that can be handled at the error site. This might be if a user inputs an invalid input at a command line prompt. The correct behavior is simply to complain to the user and loop in this case. Another situation might be a divide-by-zero. These situations aren’t really error situations, and are usually caused by faulty input.
  • A situation like the previous kind, but one that can’t be handled at the error site. For instance, if you have a function that takes a filename and parses the file with that name, it might not be able to open the file. In this case, it can’t deal with the error. This is when exceptions shine. Rather than use the C approach (return an invalid value as a flag and set a global error variable to indicate the problem), the code can instead throw an exception. The calling code will then be able to deal with the exception – for instance to prompt the user for another filename.
  • A situation that Should Not Happen. This is when a class invariant is violated, or a function receives an invalid paramter or the like. This indicates a logic failure within the code. Depending on the level of failure, an exception may be appropriate, or forcing immediate termination may be preferable (as assert does). Generally, these situations indicate that something has broken somewhere in the code, and you effectively cannot trust anything else to be correct – there may be rampant memory corruption. Your ship is sinking, get off.

To paraphrase, exceptions are for when you have a problem you can deal with, but you can’t deal with at the place you notice it. Problems you can’t deal with should simply kill the program; problems you can deal with right away should simply be dealt with.

I read some of the answers here. I’m still amazed on what all this confusion is about. I strongly disagree with all this exceptions==spagetty code. With confusion I mean, that there are people, which don’t appreciate C++ exception handling. I’m not certain how I learned about C++ exception handling — but I understood the implications within minutes. This was around 1996 and I was using the borland C++ compiler for OS/2. I never had a problem to decide, when to use exceptions. I usually wrap fallible do-undo actions into C++ classs. Such do-undo actions include:

  • creating/destroying a system handle (for files, memory maps, WIN32 GUI handles, sockets, and so on)
  • setting/unsetting handlers
  • allocating/deallocating memory
  • claiming/releasing a mutex
  • incrementing/decrementing a reference count
  • showing/hiding a window

Than there are functional wrappers. To wrap system calls (which do not fall into the former category) into C++. Eg read/write from/to a file. If something fails, an exception will be thrown, which contains full information about the error.

Then there is catching/rethrowing exceptions to add more information to a failure.

Overall C++ exception handling leads to more clean code. The amount of code is drasticly reduced. Finally one can use a constructor to allocate fallible resources and still maintain a corruption free environment after such a failure.

One can chain such classs into complex classs. Once a constructor of some member/base object is exectued, one can rely on that all other constructors of the same object (executed before) executed successfully.

Exceptions are a very unusual method of flow control compared to the traditional constructs (loops, ifs, functions, etc.) The normal control flow constructs (loops, ifs, function calls, etc.) can handle all the normal situations. If you find yourself reaching for an exception for a routine occurrence, then perhaps you need to consider how your code is structured.

But there are certain types of errors that cannot be handled easy with the normal constructs. Catastrophic failures (like resource allocation failure) can be detected at a low level but probably can’t be handled there, so a simple if-statement is inadequate. These types of failures generally need to be handled at a much higher level (eg, save the file, log the error, quit). Trying to report an error like this through traditional methods (like return values) is tedious and error-prone. Furthermore, it injects overhead into layers of mid-level APIs to handle this bizarre, unusual failure. The overhead distracts client of these APIs and requires them to worry about issues that are beyond their control. Exceptions provide a way to do non-local handling for big errors that’s mostly invisible to all the layers between the detection of the problem and the handler for it.

If a client calls ParseInt with a string, and the string doesn’t contain an integer, then the immediate caller probably cares about the error and knows what to do about it. So you’d design ParseInt to return a failure code for something like that.

On the other hand, if ParseInt fails because it couldn’t allocate a buffer because memory is horribly fragmented, then the caller isn’t going to know what to do about that. It would have to bubble this unusual error up and up to some layer that deals with these fundamental failures. That taxes everyone in between (because they have to accommodate the error passing mechanism in their own APIs). An exception makes it possible to skip over those layers (while still ensuring necessary clean-up happens).

When you’re writing low-level code, it can be hard to decide when to use traditional methods and when to throw exceptions. The low-level code has to make the decision (throw or not). But it’s the higher level code that truly knows what’s expected and what’s exceptional.

There’s several reasons in C++.

First, it’s frequently hard to see where exceptions are coming from (since they can be thrown from almost anything) and so the catch block is something of a COME FROM statement. It’s worse than a GO TO, since in a GO TO you know where you’re coming from (the statement, not some random function call) and where you’re going (the label). They’re basically a potentially resource-safe version of C’s setjmp() and longjmp(), and nobody wants to use those.

Second, C++ doesn’t have garbage collection built in, so C++ classs that own resources get rid of them in their destructors. Therefore, in C++ exception handling the system has to run all the destructors in scope. In languages with GC and no real constructors, like Java, throwing exceptions is a lot less burdensome.

Third, the C++ community, including Bjarne Stroustrup and the Standards Committee and various compiler writers, has been assuming that exceptions should be exceptional. In general, it’s not worth going against language culture. The implementations are based on the assumption that exceptions will be rare. The better books treat exceptions as exceptional. Good source code uses few exceptions. Good C++ developers treat exceptions as exceptional. To go against that, you’d want a good reason, and all the reasons I see are on the side of keeping them exceptional.

This is a bad example of using exceptions as control flow:

 int getTotalIncome(int incomeType) { int totalIncome= 0; try { totalIncome= calculateIncomeAsTypeA(); } catch (IncorrectIncomeTypeException& e) { totalIncome= calculateIncomeAsTypeB(); } return totalIncome; } 

Which is very bad, but you should be writing:

 int getTotalIncome(int incomeType) { int totalIncome= 0; if (incomeType == A) { totalIncome= calculateIncomeAsTypeA(); } else if (incomeType == B) { totalIncome= calculateIncomeAsTypeB(); } return totalIncome; } 

This second example obviously needs some refactoring (like using the design pattern strategy), but illustrates well that exceptions are not meant for control flow.

Exceptions also have some performance penalties associated, but performance problems should follow the rule: “premature optimization is the root of all evil”

  1. Maintainability: As mentioned by people above, throwing exceptions at a drop of a hat is akin to using gotos.
  2. Interoperability: You can’t interface C++ libraries with C/Python modules (atleast not easily) if you are using exceptions.
  3. Performance degradation: RTTI is used to actually find the type of the exception which imposes additional overhead. Thus exceptions are not suitable for handling commonly occurring use cases(user entered int instead of string etc).

I would say that exceptions are a mechanism to get you out of current context (out of current stack frame in the simplest sense, but it’s more than that) in a safe way. It’s the closest thing structured programming got to a goto. To use exceptions in the way they were intended to be used, you have to have a situation when you can’t continue what you’re doing now, and you can’t handle it at the point where you are now. So, for example, when user’s password is wrong, you can continue by returning false. But if the UI subsystem reports that it can’t even prompt the user, simply returning “login failed” would be wrong. The current level of code simply does not know what to do. So it uses an exception mechanism to delegate the responsibility to someone above who may know what to do.

One very practical reason is that when debugging a program I often flip on First Chance Exceptions (Debug -> Exceptions) to debug an application. If there are a lot of exceptions happening it’s very difficult to find where something has gone “wrong”.

Also, it leads to some anti-patterns like the infamous “catch throw” and obfuscates the real problems. For more information on that see a blog post I made on the subject.

I prefer to use exceptions as little as possible. Exceptions force the developer to handle some condition that may or may not be a real error. The definition of whether the exception in question is a fatal problem or a problem that must be handled immediately.

The counter argument to that is it just requires lazy people to type more in order to shoot themselves in their feet.

Google’s coding policy says to never use exceptions , especially in C++. Your application either isn’t prepared to handle exceptions or it is. If it isn’t, then the exception will probably propagate it up until your application dies.

It’s never fun to find out some library you have used throws exceptions and you were not prepared to handle them.

Legitimate case to throw an exception:

  • You try to open a file, it’s not there, a FileNotFoundException is thrown;

Illegitimate case:

  • You want to do something only if a file doesn’t exist, you try to open the file, and then add some code to the catch block.

I use exceptions when I want to break the flow of the application up to a certain point . This point is where the catch(…) for that exception is. For example, it’s very common that we have to process a load of projects, and each project should be processed independently of the others. So the loop that process the projects has a try…catch block, and if some exception is thrown during the project processing, everything is rolled back for that project, the error is logged, and the next project is processed. Life goes on.

I think you should use exceptions for things like a file that doesn’t exist, an expression that is invalid, and similar stuff. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there’s an easy/ cheap alternative to it. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there’s an easy/ cheap alternative to it because this sort of logic makes the code hard to understand:

 RecordIterator ri = createRecordIterator(); try { MyObject myobject = ri.next(); } catch(NoSuchElement exception) { // Object doesn't exist, will create it } 

Isso seria melhor:

 RecordIterator ri = createRecordIterator(); if (ri.hasNext()) { // It exists! MyObject myobject = ri.next(); } else { // Object doesn't exist, will create it } 

COMMENT ADDED TO THE ANSWER:

Maybe my example wasn’t very good – the ri.next() should not throw an exception in the second example, and if it does, there’s something really exceptional and some other action should be taken somewhere else. When the example 1 is heavily used, developers will catch a generic exception instead of the specific one and assume that the exception is due to the error that they’re expecting, but it can be due to something else. In the end, this leads to real exceptions being ignored as exceptions became part of the application flow, and not an exception to it.

The comments on this may add more than my answer itself.

Basically, exceptions are an unstructured and hard to understand form of flow control. This is necessary when dealing with error conditions that are not part of the normal program flow, to avoid having error handling logic clutter up the normal flow control of your code too much.

IMHO exceptions should be used when you want to provide a sane default in case the caller neglects to write error handling code, or if the error might best be handled further up the call stack than the immediate caller. The sane default is to exit the program with a reasonable diagnostic error message. The insane alternative is that the program limps along in an erroneous state and crashes or silently produces bad output at some later, harder to diagnose point. If the “error” is enough a normal part of program flow that the caller could not reasonably forget to check for it, then exceptions should not be used.

I think, “use it rarely” ist not the right sentence. I would prefer “throw only in exceptional situations”.

Many have explained, why exceptions should not used in normal situations. Exceptions have their right for error handling and purely for error handling.

I will focus on an other point:

An other thing is the performance issue. Compilers struggled long to get them fast. I am not sure, how the exact state is now, but when you use exceptions for control flow, than you will get an other trouble: Your program will become slow!

The reason is, that exceptions are not only very mighty goto-statements, they also have to unwind the stack for all the frames they leave. Thus implicitely also the objects on stack have to be deconstructed and so on. So without be aware of it, one single throw of an exception will really get a whole bunch of mechanics be involved. The processor will have to do a mighty lot.

So you will end up, elegantly burning your processor without knowing.

So: use exceptions only in exceptional cases — Meaning: When real errors occured!

The purpose of exceptions is to make software fault tolerant. However having to provide a response to every exception thrown by a function leads to suppression. Exceptions are just a formal structure forcing programmers to acknowledge that certain things can go wrong with a routine and that the client programmer needs to be aware of these conditions and cater for them as necessary.

To be honest, exceptions are a kludge added to programming languages to provide developers with some formal requirement that shifts the responsibility of handling error cases from the immediate developer to some future developer.

I believe that a good programming language does not support exceptions as we know them in C++ and Java. You should opt for programming languages that can provide alternative flow for all sorts of return values from functions. The programmer should be responsible for anticipating all forms of outputs of a routine and handle them in a seperate code file if I could have my way.

I use exceptions if:

  • an error occured that cannot be recovered from locally AND
  • if the error is not recovered from the program should terminate.

If the error can be recovered from (the user entered “apple” instead of a number) then recover (ask for the input again, change to default value, etc.).

If the error cannot be recovered from locally but the application can continue (the user tried to open a file but the file does not exist) then an error code is appropriate.

If the error cannot be recovered from locally and the application cannot continue without recovering (you are out of memory/disk space/etc.), then an exception is the right way to go.

Who said they should be used conservatively ? Just never use exceptions for flow control and thats it. And who cares about the cost of exception when it already thrown ?

My two cents:

I like to use exceptions, because it allows me to program as if no errors will happen. So my code remains readable, not scattered with all kinds of error-handling. Of course, the error handling (exception handling) is moved to the end (the catch block) or is considered the responsability of the calling level.

A great example for me, is either file handling, or database handling. Presume everything is ok, and close your file at the end or if some exception occurs. Or rollback your transaction when an exception occurred.

The problem with exceptions, is that it quickly gets very verbose. While it was meant to allow your code to remain very readable, and just focus on the normal flow of things, but if used consistently almost every function call needs to be wrapped in a try/catch block, and it starts to defeat the purpose.

For a ParseInt as mentioned before, i like the idea of exceptions. Just return the value. If the parameter was not parseable, throw an exception. It makes your code cleaner on the one hand. At the calling level, you need to do something like

 try { b = ParseInt(some_read_string); } catch (ParseIntException &e) { // use some default value instead b = 0; } 

The code is clean. When i get ParseInt like this scattered all over, i make wrapper functions that handle the exceptions and return me default values. Por exemplo

 int ParseIntWithDefault(String stringToConvert, int default_value=0) { int result = default_value; try { result = ParseInt(stringToConvert); } catch (ParseIntException &e) {} return result; } 

So to conclude: what i missed througout the discussion was the fact that exceptions allow me to make my code easier/more readable because i can ignore the error conditions more. Problemas:

  • the exceptions still need to be handled somewhere. Extra problem: c++ does not have the syntax that allows it to specify which exceptions a function might throw (like java does). So the calling level is not aware which exceptions might need to be handled.
  • sometimes code can get very verbose, if every function needs to be wrapped in a try/catch block. But sometimes this still makes sense.

So that makes it hard to find a good balance sometimes.

I’m sorry but the answer is “they are called exceptions for a reason.” That explanation is a “rule of thumb”. You can’t give a complete set of circumstances under which exceptions should or should not be used because what a fatal exception (English definition) is for one problem domain is normal operating procedure for a different problem domain. Rules of thumb are not designed to be followed blindly. Instead they are designed to guide your investigation of a solution. “They are called exceptions for a reason” tells you that you should determine ahead of time what is a normal error the caller can handle and what is an unusual circumstance the caller cannot handle without special coding (catch blocks).

Just about every rule of programming is really a guideline saying “Don’t do this unless you have a really good reason”: “Never use goto”, “Avoid global variables”, “Regular expressions pre-increment your number of problems by one”, etc. Exceptions are no exception….