Hace algún tiempo nuestro querido David L. Carrascal nos hablaba de la gestión de errores en Go y además nos daba unas pinceladas de lo que nos encontraríamos en la versión 1.13 de Go. Pues la versión 1.13 ya nos acompaña desde algunos meses y queremos entrar en más detalle en como utilizar las nuevas funcionalidades que se han añadido al paquete errors
.
El método Unwrap
Go 1.13 introduce una nueva funcionalidad tanto en el paquete errors
como en el paquete fmt
que nos simplifica la manera que tenemos de trabajar con errores que contienen otros errores. Si recordamos, hasta ahora para poder hacer eso teníamos que recurrir a paquetes como pkg/errors, o crear nuestros tipos personalizados, los cuales se encargarían de contener dicho comportamiento.
Ahora en Go 1.13 tenemos a nuestra disposición un método, cuya firma será Unwrap() error
, así que un error
que contenga otro debe de implementar dicho método para acceder al error contenido.
Resumiendo si err1.Unwrap()
devuelve err2
, entonces podemos decir que err1
tiene wrapeado err2
, y podemos hacer unwrap
de err1
para obtener err2
.
Así pues si tenemos nuestro propio error personalizado, sería tan sencillo como hacer.
type EmptyError struct {
msg string
err error
}
func (e *EmptyError) Unwrap() error { return e.err }
Además pensar que el error devuelto puede tener a su vez el método Unwrap
; así pues se llamará a los respectivos métodos Unwrap
repetidamente en la cadena de errores.
Comparando nuestros errores con Is y As
Además del método Unwrap
el paquete errors
ahora incluye dos funciones nuevas Is
y As
.
La función Is
se utiliza para comparar un error según su valor.
Anteriormente cuando teníamos un sentinel error
, debíamos de comprobar si un error era igual a otro de esta manera.
var ErrEmpty = errors.New("error empty")
func foo() {
// code here...
if err == ErrEmpty {
// something to do...
}
}
Pero ahora, podemos valernos de la función Is
para hacer lo mismo, y que dicha comprobación quede más semántica.
var ErrEmpty = errors.New("error empty")
func foo() {
// code here...
if errors.Is(err, ErrEmpty) {
// something to do...
}
}
Mientras que con la función As
comprobará si puede realizarse una asignación por tipo, es decir anteriormente teníamos un código como este:
func foo() {
// code here...
if e, ok := err.(*EmptyError); ok {
// something to do...
}
}
Y ahora lo podemos transformar a:
func foo() {
// code here...
var e *EmptyError
if errors.As(err, &e) {
// something to do...
}
}
Además las funciones Is
y As
revisaran toda la cadena de errores hasta encontrar un error
que cumpla con dicha condición, con lo cual no tenemos que ir realizando dobles comprobaciones o cosas similares. Simplemente tendremos que wrapear nuestros errores.
Wrapping errors con el nuevo verbo %w
Una de las maneras que lleva proporcionándonos la librería estándar desde hace tiempo de inicializar errores, además del método New()
es con el paquete fmt
.
if err != nil {
return fmt.Errorf("unmarshalling fail: %v", err)
}
De esta forma conseguiamos añadir algo más de contexto a lo que estaba sucediendo en ese preciso momento.
En Go 1.13 la función fmt.Errorf
soporta un nuevo verbo %w
. Cuando este verbo esta presente, el error que será devuelto tendrá un método Unwrap
que contendrá el error
que hayamos añadido durante el format. El resto de comportamientos será el mismo que usar %v
.
if err != nil {
return fmt.Errorf("unmarshalling fail: %w", err)
}
Así que si queremos aprovechar las funciones Is
y As
anteriormente mencionadas podemos hacer algo como:
err := fmt.Errorf("the argument: %v, is required: %w", name, ErrEmpty)
...
if errors.Is(err, ErrEmpty) {...}
Stack trace
Todo esto introducido en el paquete error
seguro que os suena del famoso paquete golang.org/x/xerrors ya que este es el paquete que se creó a partir de la propuesta para errores de Go 2.
Pero si bien es cierto que han implementado en la librería estándar gran parte de su funcionalidad, no la han llevado toda como es el stack trace
, es decir no podemos printar donde han ocurrido dichos errores.
Esto era algo que ademas de xerrors
ofrecía la librería de Dave Cheney pkg/errors
, pero si vamos hoy a su repositorio, nos encontramos con un mensaje, que dice así:
Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
Before sending a PR, please discuss your change by raising an issue.
Que traducido sería:
Debido a los cambios de errores para Go 2, este paquete no aceptara propuestas para nuevas funcionalidades. Dicho esto se aceptan pull requests, corrección de errores y reporte de incidencias.
Antes de enviar un PR, por favor, abre una incidencia para discutirlo.
Es decir el paquete entra en mantenimiento, así que no veremos las nuevas features de la 1.13 en él, o eso pensabamos ya que si seguimos el hilo de una PR en concreto (Add support for Go 1.13 error chains), vemos que hace poco Dave Cheney se ha pronunciado para decir que revisará en noviembre el tema.
Pero, mientras el paquete de pkg/errors
da esa funcionalidad o no, nosotros hemos querido dar una alternativa, y hemos creado un fork de dicho repositorio, dando funcionalidad completa a los errores de Go 1.13. Y no solo eso, sino que además si estás en transición ¡utilizando nuestra librería podrás acceder a dichas funcionalidades desde versiones anteriores a la 1.13¡
Para ello tan sólo tendrás que reemplazar allí donde usabas pkg/errors
por friendsofgo/errors
y si todavía no la usas, te recomendamos hacerlo.
¿Qué aporta friendsofgo/errors
Primero que nada centralizar todas las funciones mencionadas en un único paquete, es decir
errors.New()
errors.Is()
errors.As()
errors.Errorf()
Además como comentabamos compatibilidad con el paquete estandard, así que puedes crear errores de la manera que indicamos en el artículo y combinarlos con los de nuestro paquete y todo seguirá funcionando.
También al ser un fork de pkg/errors
tenemos los métodos Wrap
y Wrapf
con lo que puedes olvidarte, del nuevo verbo %w
err := fmt.Errorf("the argument: %v, is required: %w", name, ErrEmpty)
pasa a ser:
err := errors.Wrapf(ErrEmpty, "the argument: %v, is required", name)
Mucho más legible, todos los errores que utilicen nuestros métodos Wrap
y Wrapf
implementarán el método Unwrap()
, con lo que el Is
y el As
harán su trabajo.
Y además tal como hacía la librería de pkg/errors
podemos obtener el stack trace.
...
fmt.Pritnf("%+v \n", err)
...
Puedes encontrar nuestro paquete en https://github.com/friendsofgo/errors, recuerda que puedes enviar tus PRs para colaborar y darnos una estrellita si te ha gustado nuestra aportación.
Ya sabéis que si tenéis cualquier duda o comentario podéis dejarlo en los comentarios o en nuestro Twitter @FriendsofGoTech.