O consenso geral "não use exceções!" Vem principalmente de outros idiomas e até mesmo algumas vezes está desatualizado.
No C ++, lançar uma exceção é muito caro devido ao “desenrolar da pilha”. Toda declaração de variável local é como uma with
declaração em Python, e o objeto nessa variável pode executar destruidores. Esses destruidores são executados quando uma exceção é lançada, mas também ao retornar de uma função. Esse "idioma do RAII" é um recurso de linguagem integral e é super importante escrever código correto e robusto - portanto, o RAII versus as exceções baratas foi uma desvantagem que o C ++ decidiu pelo RAII.
No C ++ inicial, muitos códigos não eram gravados de maneira segura para exceções: a menos que você realmente use RAII, é fácil vazar memória e outros recursos. Portanto, lançar exceções tornaria esse código incorreto. Isso não é mais razoável, pois mesmo a biblioteca padrão C ++ usa exceções: você não pode fingir que não existem. No entanto, as exceções ainda são um problema ao combinar o código C com o C ++.
Em Java, toda exceção tem um rastreamento de pilha associado. O rastreamento da pilha é muito valioso ao depurar erros, mas desperdiça esforço quando a exceção nunca é impressa, por exemplo, porque foi usada apenas para o fluxo de controle.
Portanto, nesses idiomas, as exceções são "muito caras" para serem usadas como fluxo de controle. No Python, isso é menos problemático e as exceções são muito mais baratas. Além disso, a linguagem Python já sofre com alguma sobrecarga que torna o custo das exceções imperceptíveis em comparação com outras construções de fluxo de controle: por exemplo, verificar se existe uma entrada dict com um teste de associação explícito if key in the_dict: ...
geralmente é exatamente tão rápido quanto simplesmente acessar a entrada the_dict[key]; ...
e verificar se você obtenha um KeyError. Alguns recursos de linguagem integral (por exemplo, geradores) são projetados em termos de exceções.
Portanto, embora não haja razão técnica para evitar especificamente exceções no Python, ainda há a questão de se você deve usá-las em vez dos valores de retorno. Os problemas no nível do design, com exceções, são:
eles não são de todo óbvios. Você não pode olhar facilmente para uma função e ver quais exceções podem ser lançadas, portanto nem sempre sabe o que capturar. O valor de retorno tende a ser mais bem definido.
As exceções são o fluxo de controle não local, o que complica seu código. Quando você lança uma exceção, não sabe onde o fluxo de controle será retomado. Para erros que não podem ser tratados imediatamente, essa é provavelmente uma boa idéia, ao notificar o chamador sobre uma condição, isso é totalmente desnecessário.
A cultura Python geralmente é inclinada em favor de exceções, mas é fácil exagerar. Imagine uma list_contains(the_list, item)
função que verifique se a lista contém um item igual a esse item. Se o resultado for comunicado por meio de exceções, é absolutamente irritante, porque temos que chamá-lo assim:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Retornar um bool seria muito mais claro:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Se a função já deve retornar um valor, retornar um valor especial para condições especiais é bastante suscetível a erros, porque as pessoas esquecem de verificar esse valor (provavelmente é a causa de 1/3 dos problemas em C). Uma exceção é geralmente mais correta.
Um bom exemplo é uma pos = find_string(haystack, needle)
função que procura a primeira ocorrência da needle
string na string `haystack e retorna a posição inicial. Mas e se a corda do palheiro não contiver a agulha?
A solução de C e imitada pelo Python é retornar um valor especial. Em C, este é um ponteiro nulo; em Python, esse é -1
. Isso levará a resultados surpreendentes quando a posição for usada como um índice de string sem verificação, especialmente como -1
é um índice válido no Python. Em C, seu ponteiro NULL fornecerá pelo menos um segfault.
No PHP, um valor especial de um tipo diferente é retornado: o booleano em FALSE
vez de um número inteiro. Acontece que isso não é realmente melhor devido às regras implícitas de conversão da linguagem (mas observe que no Python também os booleanos podem ser usados como ints!). Funções que não retornam um tipo consistente são geralmente consideradas muito confusas.
Uma variante mais robusta seria lançar uma exceção quando a cadeia não puder ser encontrada, o que garante que durante o fluxo de controle normal seja impossível usar acidentalmente o valor especial no lugar de um valor comum:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
Como alternativa, sempre é possível retornar um tipo que não pode ser usado diretamente, mas que primeiro deve ser desembrulhado, por exemplo, uma tupla de resultado-bool em que o booleano indica se ocorreu uma exceção ou se o resultado é utilizável. Então:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Isso obriga a lidar com problemas imediatamente, mas fica irritante muito rapidamente. Também impede que você encadeie a função facilmente. Toda chamada de função agora precisa de três linhas de código. Golang é uma linguagem que acha que esse incômodo vale a pena.
Então, para resumir, as exceções não são totalmente isentas de problemas e podem definitivamente ser superutilizadas, especialmente quando substituem um valor de retorno "normal". Porém, quando usado para sinalizar condições especiais (não necessariamente apenas erros), as exceções podem ajudá-lo a desenvolver APIs limpas, intuitivas, fáceis de usar e difíceis de mau uso.