errors.Is() e errors.As()
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 56 de 107.
Quando erros são envolvidos, verificações de igualdade simples como err == ErrNotFound não funcionam mais. O pacote errors fornece duas funções para inspecionar cadeias de erros envolvidos: errors.Is() e errors.As().
errors.Is() verifica se algum erro na cadeia corresponde a um erro sentinela específico:
import "errors"
var ErrNotFound = errors.New("not found")
func FindUser(id int) error {
return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}
err := FindUser(42)
if errors.Is(err, ErrNotFound) {
fmt.Println("User doesn't exist")
}Embora o erro retornado esteja envolto, errors.Is() desembrulha a cadeia e encontra ErrNotFound dentro.
errors.As() verifica se algum erro na cadeia corresponde a um tipo específico e o extrai:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation failed: " + e.Field
}
err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("Invalid field:", ve.Field)
}Use errors.Is() ao verificar erros sentinela, e errors.As() quando você precisar acessar campos de um tipo de erro personalizado. Ambas as funções percorrem toda a cadeia de erros, tornando-as essenciais para lidar com erros envolvidos adequadamente.
Desafio
FácilVamos construir um sistema de processamento de arquivos que demonstra como inspecionar adequadamente cadeias de erros envelopados (wrapped error chains) usando errors.Is() e errors.As(). Você criará erros que são envelopados à medida que passam por diferentes camadas e, em seguida, usará essas funções para identificar e extrair informações deles.
Você organizará seu código em dois arquivos:
errors.go: Defina a infraestrutura de erro para o seu sistema de arquivos.Crie um erro sentinela:
ErrPermissionDeniedcom a mensagem"permission denied"
Crie um tipo de erro personalizado:
- Struct
FileErrorcom os camposFilename(string) eOperation(string) - Seu método
Error()deve retornar:[Operation] failed on file: [Filename]
Implemente duas funções que simulam operações de arquivo e envelopam erros:
ReadFile(filename string) error- Se o filename for"secret.txt", retorneErrPermissionDenied. Se o filename for"missing.txt", retorne um*FileErrorcom Operation"read"e o Filename fornecido. Caso contrário, retornenil.ProcessFile(filename string) error- ChamaReadFile. Se um erro for retornado, envolva-o usandofmt.Errorfcom o formato"processing failed: %w". Caso contrário, retornenil.
main.go: Leia um nome de arquivo da entrada e chameProcessFile. Useerrors.Is()eerrors.As()para inspecionar a cadeia de erros envelopados e imprimir as mensagens apropriadas com base no que encontrar dentro dela.
A seguinte entrada será fornecida:
- Linha 1: Nome do arquivo a ser processado
Manipule o resultado inspecionando a cadeia de erros:
- Se
errors.Is()encontrarErrPermissionDeniedna cadeia: imprima a mensagem de erro completa e, em uma nova linha, imprimaAccess denied - check file permissions - Se
errors.As()encontrar um*FileErrorna cadeia: imprima a mensagem de erro completa e, em uma nova linha, imprimaFile issue: [Filename] during [Operation] - Se nenhum erro ocorrer: imprima
File '[filename]' processed successfully
Por exemplo, dado secret.txt, sua saída deve ser:
processing failed: permission denied
Access denied - check file permissionsE dado missing.txt, sua saída deve ser:
processing failed: read failed on file: missing.txt
File issue: missing.txt during readE dado data.txt, sua saída deve ser:
File 'data.txt' processed successfullyObserve como, embora os erros sejam envelopados por ProcessFile, você ainda pode detectar o erro sentinela original com errors.Is() e extrair o tipo de erro personalizado com errors.As(). Essas funções percorrem toda a cadeia de erros para encontrar o que você está procurando.
Folha de consulta
Quando os erros são envolvidos (wrapped), use errors.Is() e errors.As() do pacote errors para inspecionar a cadeia de erros.
errors.Is() verifica se algum erro na cadeia corresponde a um erro sentinela (sentinel error):
import "errors"
var ErrNotFound = errors.New("not found")
err := fmt.Errorf("database lookup failed: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
// ErrNotFound was found in the chain
}errors.As() verifica se algum erro na cadeia corresponde a um tipo específico e o extrai:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation failed: " + e.Field
}
err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})
var ve *ValidationError
if errors.As(err, &ve) {
// ve now contains the ValidationError from the chain
fmt.Println(ve.Field)
}Use errors.Is() para erros sentinela e errors.As() quando precisar acessar campos de tipos de erro personalizados. Ambas as funções percorrem toda a cadeia de erros.
Experimente você mesmo
package main
import (
"errors"
"fmt"
)
func main() {
// Lê o nome do arquivo da entrada
var filename string
fmt.Scanln(&filename)
// Chama ProcessFile com o nome do arquivo
err := ProcessFile(filename)
// TODO: Tratar o resultado inspecionando a cadeia de erros
//
// Use errors.Is() para verificar se ErrPermissionDenied está na cadeia:
// - Se encontrado: imprima o erro completo, depois "Access denied - check file permissions"
//
// Use errors.As() para verificar se um *FileError está na cadeia:
// - Se encontrado: imprima o erro completo, depois "File issue: [Filename] during [Operation]"
//
// Se não houver erro: imprima "File '[filename]' processed successfully"
_ = errors.Is // dica: use errors.Is()
_ = errors.As // dica: use errors.As()
_ = err
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool