Go ofrece dos métodos para crear errores en la biblioteca estándar: errors.New
y fmt.Errorf
. Cuando comunica información de error más complicada a sus usuarios, o a usted mismo al realizar una depuración, a veces estos dos mecanismos no son suficientes para capturar e informar de manera adecuada lo que sucedió. Para expresar esta información de error más compleja, y ampliar la funcionalidad, podemos implementar el tipo de interfaz de biblioteca estándar: error
.
La sintaxis para esto sería la siguiente:
type error interface {
Error() string
}
El paquete builtin
define error
como una interfaz con un único método Error()
que muestra un mensaje de error como una cadena. Al implementar este método, podemos transformar cualquier tipo que definamos en un error propio.
Intentaremos ejecutar el siguiente ejemplo para ver una implementación de la interfaz error
:
package main
import (
"fmt"
"os"
)
type MyError struct{}
func (m *MyError) Error() string {
return "boom"
}
func sayHello() (string, error) {
return "", &MyError{}
}
func main() {
s, err := sayHello()
if err != nil {
fmt.Println("unexpected error: err:", err)
os.Exit(1)
}
fmt.Println("The string:", s)
}
Veremos el siguiente resultado:
Outputunexpected error: err: boom
exit status 1
Aquí, hemos creado un nuevo tipo de estructura vacía, MyError
, y hemos definido el método Error()
en ella. El método Error()
muestra la cadena "boom"
.
En main()
, invocamos la función sayHello
que muestra una cadena vacía y una nueva instancia de MyError
. Ya que sayHello
siempre mostrará un error, la invocación fmt.PrintIn
dentro del cuerpo de la declaración if en main()
siempre se ejecutará. Utilizaremos fmt.PrintIn
para imprimir la cadena de prefijo corto "unexpected error:"
junto con la instancia de MyError
que está en la variable err
.
Observe que no tenemos que invocar directamente Error()
, ya que el paquete fmt
puede detectar automáticamente que esta es una implementación de error
. Invoca Error()
de forma transparente para obtener la cadena "boom"
y la concatena con la cadena de prefijo "unexpected error: err:"
.
A veces, un error personalizado es la alternativa más prolija para capturar información detallada de un error. Por ejemplo, supongamos que queremos capturar el código de estado de los errores producidos por una solicitud HTTP. Ejecute el siguiente programa para ver una implementación de error
que nos permita capturar de forma prolija esa información:
package main
import (
"errors"
"fmt"
"os"
)
type RequestError struct {
StatusCode int
Err error
}
func (r *RequestError) Error() string {
return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
}
func doRequest() error {
return &RequestError{
StatusCode: 503,
Err: errors.New("unavailable"),
}
}
func main() {
err := doRequest()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("success!")
}
Verá el siguiente resultado:
Outputstatus 503: err unavailable
exit status 1
En este ejemplo, creamos una nueva instancia de RequestError
y proporcionamos el código de estado y un error usando la función errors.New
de la biblioteca estándar. A continuación, imprimimos esto usando fmt.PrintIn
como en los ejemplos anteriores.
En el método Error()
de RequestError
, usamos la función fmt.Sprintf
para construir una cadena empleando la información proporcionada cuando se creó el error.
La interfaz error
expone solo un método, pero es posible que necesitemos acceder a otros métodos de implementaciones de error
para gestionar un error de forma correcta. Por ejemplo, es posible que tengamos varias implementaciones personalizadas de error
que sean temporales y puedan probarse nuevamente, denotadas por la presencia de un método Temporary()
.
Las interfaces proporcionan una vista reducida del conjunto, más amplio, de métodos proporcionados por los tipos, de modo que debemos usar una_ aserción de tipo_ para cambiar los métodos que la vista muestra o para eliminarla completamente.
El siguiente ejemplo aumenta el RequestError
previamente mostrado para tener un método Temporary()
que indicará si quienes realizan la invocación deberian intentar realizar nuevamente la solicitud o no:
package main
import (
"errors"
"fmt"
"net/http"
"os"
)
type RequestError struct {
StatusCode int
Err error
}
func (r *RequestError) Error() string {
return r.Err.Error()
}
func (r *RequestError) Temporary() bool {
return r.StatusCode == http.StatusServiceUnavailable // 503
}
func doRequest() error {
return &RequestError{
StatusCode: 503,
Err: errors.New("unavailable"),
}
}
func main() {
err := doRequest()
if err != nil {
fmt.Println(err)
re, ok := err.(*RequestError)
if ok {
if re.Temporary() {
fmt.Println("This request can be tried again")
} else {
fmt.Println("This request cannot be tried again")
}
}
os.Exit(1)
}
fmt.Println("success!")
}
Verá el siguiente resultado:
Outputunavailable
This request can be tried again
exit status 1
En main()
, invocamos doRequest()
que muestra una interfaz error
. Primero, imprimimos el mensaje de error mostrado por el método Error()
. A continuación, intentamos exponer todos los métodos de RequestError
usando la afirmación de tipo re, ok := err.( *RequestError)
. Si la aserción de tipo se realizó correctamente, usaremos el método Temporary()
para ver si este error es temporal. Debido a que el StatusCode
establecido por doRequest()
es 503
, que coincide con http.StatusServiceUnavailable
, con esto se muestra true
y se imprime "This request can be tried again"
. En la práctica, en vez de eso, realizaríamos otra solicitud en vez de imprimir un mensaje.
Comúnmente, un error se generará a partir de algo externo a su programa, como una base de datos y una conexión de red, entre otros ejemplos. Los mensajes de error proporcionados a partir de estos errores no ayudan a encontrar el origen del error. Ajustar los errores con información adicional al principio de un mensaje de error proporcionará el contexto necesario para realizar correctamente la depuración.
En el siguiente ejemplo, se demuestra la forma en que podemos añadir información contextual a un error
, mostrado por alguna otra función, que de otra forma sería enigmático.
package main
import (
"errors"
"fmt"
)
type WrappedError struct {
Context string
Err error
}
func (w *WrappedError) Error() string {
return fmt.Sprintf("%s: %v", w.Context, w.Err)
}
func Wrap(err error, info string) *WrappedError {
return &WrappedError{
Context: info,
Err: err,
}
}
func main() {
err := errors.New("boom!")
err = Wrap(err, "main")
fmt.Println(err)
}
Verá el siguiente resultado:
Outputmain: boom!
WrappedError
es una estructura con dos campos: un mensaje de contexto como una string
y un error
sobre el cual WrappedError
proporciona más información. Cuando se invoca el método Error()
, de nuevo usamos fmt.Sprintf
para imprimir el mensaje de contexto; luego el error
(fmt.Sprintf
sabe cómo invocar de forma implícita el método Error()
también).
En main()
, creamos un error usando errors.New
y luego ajustamos ese error usando la función Wrap
que definimos. Esto nos permite indicar que este error
se generó en "main"
. Además, ya que nuestro WrappedError
es también un error
, podríamos ajustar otro WrappedError
; esto nos permitiría ver una cadena para poder rastrear el origen del error. Con algo de ayuda de la biblioteca estándar, podemos incluso integrar seguimientos de pila completos en nuestros errores.
Debido a que la interfaz error
es solo un método único, hemos visto que disponemos de una gran flexibilidad para proporcionar diferentes tipos de error para diferentes situaciones. Esto puede abarcar todo, desde la comunicación de varios fragmentos de información como parte de un error hasta la implementación de un retroceso exponencial. Aunque los mecanismos de gestión de errores en Go pueden parecer, a primera vista, simplistas, podemos lograr un manejo bastante bueno usando estos errores personalizados para gestionar situaciones comunes y atípicas.
Go tiene otro mecanismo para comunicar el comportamiento inesperado: los panics. En nuestro siguiente artículo de la serie de gestión de errores, veremos los panics, qué son y la forma gestionarlos.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.