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_page
pode 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_status
da 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 None
da 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 hits
estiver vazio, um IndexError
será lançado quando tentarmos hits[0]
. Mas não se deve esperar que o responsável pela chamada lide com uma IndexError
função lançada por minha função, pois ele não tem ideia de onde isso IndexError
veio; 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 status
nã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.