Exceções: de onde vêm, para onde vão e como organizá-las em Python
Quando e como tratar exceções e a importância dos tratamentos especializados.
Antes de mais nada, o que são exceções?
Podemos dizer que exceções são resultado de “eventos excepcionais” ocorridos durante o fluxo regular de um software.
Como assim?
Em termos práticos, se algo deu errado, uma exceção que corresponde ao problema ocorrido será lançada.
Um exemplo do mundo real:
Imagine que você acabou de clicar no botão “imprimir” de um PDF, sua impressora está ligada, configurada e com papel. No entanto, você percebe que a impressão não foi realizada e se depara com a seguinte mensagem:
IMPRESSORA SEM TINTA!
Pois é, você provavelmente já passou por isso! O fluxo normal seria imprimir o documento, mas algo aconteceu e esse fluxo foi interrompido… Em outras palavras, uma exceção ocorreu.
Um exemplo em Python
Vamos imaginar que estamos escrevendo o trecho de código responsável pelo processo de impressão:
try:
print_document(document)
except OutOfInkError:
display_message("IMPRESSORA SEM TINTA")
O exemplo acima nos diz: tente (try) imprimir, caso a impressora esteja sem tinta (except OutOfInkError), exiba no display a mensagem IMPRESSORA SEM TINTA!.
Múltiplas Exceções
Concorda comigo que faltar tinta não é o único possível erro em um processo de impressão? A impressora poderia, por exemplo, ter tinta e não ter papel!
Como você já deve imaginar, podemos nos preparar para mais de uma exceção:
try:
print_document(document)
except OutOfInkError:
display_message("IMPRESSORA SEM TINTA!")
except OutOfPaperError:
display_message("IMPRESSORA SEM PAPEL!")
Hierarquia de Exceções
Exceções geralmente são definidas de maneira hierárquica e conhecer essa hierarquia é extremamente importante para nós. Vejamos por exemplo, uma versão da hierarquia de exceções built-in do Python:
Observe o seguinte exemplo de hierarquia: AttributeError herda de Exception, que por sua vez herda de BaseException.
Na prática, o que isso muda?
Voltando ao nosso exemplo de impressão, vamos imaginar que nossas exceções OutOfInkError e OutOfPaperError herdam de PrintError, que por sua vez (assim como todas exceções em Python), herda de Exception:
Exception
^
|
|
PrintError
^ ^
| |
| |
OutOfInkError OutOfPaperError
Em Programação Orientada a Objetos, um objeto sempre passará no teste É- Um de uma classe pai, ou seja:
?OutOfInkError É Um PrintError
PrintError É Um Exception
Um exemplo mais lúdico seria a relação entre as classes Animal e Cachorro:
Animal
^
|
|
Cachorro
Concorda que todo cachorro É-Um animal? Em contrapardida, nem todo animal É-Um cachorro, certo? (Caso não tenha entendido o exemplo, dê uma olhada aqui)
A ordem dos fatores altera o produto
Se tratando de exceções, a ordem faz MUITA diferença! As exceções são tratadas de acordo com a ordem de seus blocos e é aí que entra a relacão É-Um.
É-Um
Supondo que agora as exceções do nosso processo de impressão sejam tratadas da seguinte maneira:
try:
print_document(document)
except PrintError:
display_message("ERRO AO IMPRIMIR DOCUMENTO!")
except OutOfInkError:
display_message("IMPRESSORA SEM TINTA!")
except OutOfPaperError:
display_message("IMPRESSORA SEM PAPEL!")
As exceções OutOfInkError e OutOfPaperError nunca seriam tratadas! Ambas seriam tratadas como:
ERRO AO IMPRIMIR DOCUMENTO!
O motivo? Ambas passam no teste É-Um do tratamento de PrintError.
Visibilidade
O ponto chave para o tratamento de exceções específicas é: visibilidade.
Como você saberá se precisa comprar tinta ou papel se a única informação disponível é: ERRO AO IMPRIMIR DOCUMENTO?
O mesmo se aplica ao seu software! Saber exatamente o que aconteceu fará com que tempo e esforço empregados para conter o incidente sejam significamente menores ;)
Conclusão
- Conheça a hieraquia de exceções da linguagem de programação em uso;
- A ordem importa: da mais específica para a mais generalizada, SEMPRE;
- Evite tratamentos excessivamente genéricos, você pode acabar suprimindo exceções que não deveria.
EXTRA: O (excelente) site realpython tem um artigo bem bacana sobre um anti-pattern amplamente usado em Python: https://realpython.com/the-most-diabolical-python-antipattern/
Vale a pena ler!
CYA :)