Módulo 16 - Projeto final: agenda de contatos
Tratando erros com uma exceção própria
12 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Criar a exceção própria ContatoNaoEncontradoError.
- Fazer buscar_por_nome e remover levantarem a exceção quando preciso.
- Validar o telefone com uma expressão regular.
- Tratar o erro com try e except no ponto onde dá para responder ao usuário.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Tratando erros com uma exceção própria.
Os objetivos desta aula. Criar a exceção própria ContatoNaoEncontradoError. Fazer buscar_por_nome e remover levantarem a exceção quando preciso. Validar o telefone com uma expressão regular. Tratar o erro com try e except no ponto onde dá para responder ao usuário.
Veja o essencial, parte por parte.
Uma exceção com nome próprio. ContatoNaoEncontradoError herda de Exception e nomeia o problema.
Remover e validar o telefone. Com a busca levantando exceção, o método remover fica elegante: ele reaproveita buscar_por_nome.
Tratar o erro no lugar certo. Levantar a exceção é metade do trabalho; a outra metade é capturá-la onde dá para fazer algo útil.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Uma exceção com nome próprio
Devolver None quando o contato não existe tem um defeito silencioso: quem chama pode esquecer de conferir e tentar usar o None como se fosse um contato, o que estoura um erro confuso lá adiante, longe da causa. A alternativa madura é falhar cedo e com clareza. Em vez de um None ambíguo, o método levanta uma exceção com nome que diz exatamente o que houve. Criar essa exceção é simples: uma classe vazia que herda de Exception já basta, e o nome carrega o significado.
class ContatoNaoEncontradoError(Exception):
"""Levantada quando um contato nao existe na agenda."""
class Agenda:
def __init__(self):
self.contatos = []
def adicionar_contato(self, contato):
self.contatos.append(contato)
def listar(self):
return list(self.contatos)
def buscar_por_nome(self, nome):
for contato in self.contatos:
if nome.lower() in contato.nome.lower():
return contato
raise ContatoNaoEncontradoError(nome)buscar_por_nome agora levanta a exceção em vez de devolver None.
A mudança em buscar_por_nome é pequena e profunda. Se o laço encontra um contato, devolve na hora. Se percorre tudo sem achar, em vez de terminar devolvendo None, executa raise ContatoNaoEncontradoError(nome). O raise interrompe o método e sinaliza o problema para quem chamou. Passar o nome procurado para a exceção guarda a informação útil: depois dá para saber qual nome falhou. Quem chama agora é obrigado a lidar com a possibilidade de erro, o que é bom, porque o erro deixou de passar despercebido.
Remover e validar o telefone
Com a busca levantando exceção, o método remover fica elegante: ele reaproveita buscar_por_nome. Se o contato existe, buscar_por_nome o devolve e remover o tira da lista. Se não existe, buscar_por_nome já levanta ContatoNaoEncontradoError, e remover nem precisa se preocupar, pois o erro sobe sozinho. É a vantagem de compor métodos: a regra de o que fazer quando não encontra mora em um lugar só. Aproveitamos também para validar o telefone, garantindo que só entrem números no formato esperado.
import re
TELEFONE_RE = re.compile(r"^\d{10,11}$")
def telefone_valido(telefone):
return TELEFONE_RE.match(telefone) is not None
class Agenda:
# ... init, adicionar_contato, listar e buscar_por_nome como antes ...
def remover(self, nome):
contato = self.buscar_por_nome(nome)
self.contatos.remove(contato)remover reaproveita a busca; telefone_valido confere o formato com regex.
A expressão regular \d{10,11} lê-se: de dez a onze dígitos, do começo ao fim do texto. As âncoras ^ e $ garantem que nada além dos números seja aceito, então 11999998888 passa e 11 9999-8888 não, porque tem espaço e traço. A função telefone_valido devolve True ou False, o que a torna fácil de usar em um if. Note que a validação não vive dentro da classe Agenda: ela é uma regra sobre o texto do telefone, útil antes mesmo de criar o contato. Deixá-la como função separada mantém cada responsabilidade no seu lugar.
Tratar o erro no lugar certo
Levantar a exceção é metade do trabalho; a outra metade é capturá-la onde dá para fazer algo útil. E esse lugar não é dentro dos métodos da agenda. A agenda não sabe conversar com o usuário; ela só conhece contatos. Quem conversa é o menu, que lê o que a pessoa digita e responde na tela. Então é no menu que o try e except entram: ele chama buscar_por_nome dentro de um try e, se vier a exceção, imprime uma mensagem amigável em vez de deixar o programa quebrar.
nome = input("Nome a buscar: ").strip()
try:
contato = agenda.buscar_por_nome(nome)
print(contato)
except ContatoNaoEncontradoError:
print(f"Nao encontrei '{nome}'.")O tratamento fica no menu, onde há como responder ao usuário.
Esse é um princípio que vale além da agenda: levante a exceção onde o problema acontece, mas trate onde há contexto para reagir. Os métodos da agenda levantam o erro porque é ali que se descobre que o contato não existe. O menu trata o erro porque é ali que se sabe o que dizer ao usuário. Se a agenda tratasse o erro sozinha, imprimindo algo, ela ficaria presa a esse jeito de responder e seria inútil em um programa com interface gráfica, por exemplo. Separar quem sinaliza de quem reage deixa cada parte reaproveitável.
Teste rápido
Por que o tratamento com try e except fica no menu, e não dentro de buscar_por_nome?
Perguntas frequentes
- Por que criar uma exceção própria em vez de usar ValueError?
- Porque o nome comunica a intenção. ContatoNaoEncontradoError diz exatamente o que aconteceu, enquanto ValueError é genérico. Um except ContatoNaoEncontradoError captura só esse caso, sem pegar por engano outros erros de valor que você talvez queira tratar de forma diferente.
- A classe da exceção pode ficar vazia mesmo?
- Sim. Herdando de Exception, ela já ganha todo o comportamento necessário. O corpo pode ser só uma docstring, que documenta quando o erro ocorre. O valor da exceção está no nome e na hierarquia, não em ter código dentro.
- O que significam ^ e $ na expressão regular do telefone?
- São âncoras: ^ marca o início do texto e $ o fim. Juntas, exigem que o texto inteiro seja só os dígitos, sem sobras. Sem elas, um telefone com letras no meio poderia passar, desde que tivesse dígitos suficientes em algum trecho.
- Por que telefone_valido é função solta e não método da agenda?
- Porque validar o formato de um telefone é uma regra sobre o texto, independente da agenda. Você pode querer validar antes mesmo de ter um contato ou uma agenda. Manter a função separada segue a ideia de cada responsabilidade no seu lugar.
- remover realmente não precisa de try dentro dele?
- Não. Ele chama buscar_por_nome, que levanta a exceção se o contato não existe. Como remover não sabe responder ao usuário, deixa a exceção subir para quem o chamou, o menu, que trata. É a mesma lógica de sempre: sinalizar em um lugar, tratar em outro.
- Posso validar o e-mail com regex também?
- Pode, e é um bom exercício. Validar e-mail perfeitamente é surpreendentemente difícil, então na prática costuma-se checar apenas se há um arroba com texto dos dois lados. Deixamos essa melhoria como sugestão na última aula para não alongar o projeto.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.