A configuração
Costumo ter problemas para determinar quando e como usar exceções. Vamos considerar um exemplo simples: suponha que eu esteja copiando uma página da Web, diga " http://www.abevigoda.com/ ", para determinar se Abe Vigoda ainda está vivo. Para fazer isso, basta baixar a página e procurar os horários em que a frase "Abe Vigoda" aparece. Retornamos a primeira aparição, pois isso inclui o status de Abe. Conceitualmente, ficará assim:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Onde parse_abe_status(s)pega uma sequência da forma "Abe Vigoda é alguma coisa " e retorna a parte " alguma coisa ".
Antes de argumentar que existem maneiras muito melhores e mais robustas de classificar esta página para o status de Abe, lembre-se de que este é apenas um exemplo simples e artificial usado para destacar uma situação comum em que estou.
Agora, onde esse código pode encontrar problemas? Entre outros erros, alguns "esperados" são:
download_pagepode não conseguir baixar a página e lança umIOError.- O URL pode não apontar para a página correta, ou a página é baixada incorretamente e, portanto, não há ocorrências.
hitsé a lista vazia, então. - A página da web foi alterada, possivelmente fazendo com que nossas suposições sobre a página estivessem erradas. Talvez esperemos 4 menções a Abe Vigoda, mas agora encontramos 5.
- Por alguns motivos,
hits[0]pode não ser uma sequência do formato "Abe Vigoda é alguma coisa " e, portanto, não pode ser analisada corretamente.
O primeiro caso não é realmente um problema para mim: um IOErroré lançado e pode ser tratado pelo chamador da minha função. Então, vamos considerar os outros casos e como eu posso lidar com eles. Mas primeiro, vamos supor que implementamos parse_abe_statusda maneira mais estúpida possível:
def parse_abe_status(s):
return s[13:]
Ou seja, ele não realiza nenhuma verificação de erro. Agora, para as opções:
Opção 1: Retorno None
Posso dizer ao chamador que algo deu errado ao retornar None:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
if not hits:
return None
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Se o interlocutor receber Noneda minha função, ele deve assumir que não houve menções a Abe Vigoda e, portanto, algo deu errado. Mas isso é bastante vago, certo? E isso não ajuda no caso em hits[0]que não é o que pensávamos.
Por outro lado, podemos colocar em algumas exceções:
Opção 2: Usando exceções
Se hitsestiver vazio, um IndexErrorserá lançado quando tentarmos hits[0]. Mas não se deve esperar que o responsável pela chamada lide com uma IndexErrorfunção lançada por minha função, pois ele não tem ideia de onde isso IndexErrorveio; poderia ter sido jogado por find_all_mentions, pelo que ele sabe. Portanto, criaremos uma classe de exceção personalizada para lidar com isso:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Agora, e se a página for alterada e houver um número inesperado de ocorrências? Isso não é catastrófico, pois o código ainda pode funcionar, mas um chamador pode querer ter um cuidado extra ou registrar um aviso. Então, eu vou lançar um aviso:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Por fim, podemos descobrir que statusnão está vivo ou morto. Talvez, por alguma estranha razão, hoje tenha sido comatose. Então não quero voltar False, pois isso implica que Abe está morto. O que devo fazer aqui? Lance uma exceção, provavelmente. Mas que tipo? Devo criar uma classe de exceção personalizada?
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
if status not in ['alive', 'dead']:
raise SomeTypeOfError("Status is an unexpected value.")
# he's either alive or dead
return status == "alive"
Opção 3: em algum lugar no meio
Eu acho que o segundo método, com exceções, é preferível, mas não tenho certeza se estou usando exceções corretamente nele. Estou curioso para ver como os programadores mais experientes lidariam com isso.