patterns pattern gof design-patterns go error-handling

design-patterns - gof - factory pattern



Ir. La mejor práctica para manejar el error desde múltiples niveles abstractos (2)

Debe manejar un error, o no manejarlo, sino delegarlo a un nivel superior (para la persona que llama). Manejar el error y devolverlo es una mala práctica, como si la persona que llama también hace lo mismo, el error podría ser manejado varias veces.

Manejar un error significa inspeccionarlo y tomar una decisión basada en eso, que puede ser que simplemente lo registre, pero eso también cuenta como "manejarlo".

Si opta por no manejarlo sino delegarlo en un nivel superior, eso puede estar perfectamente bien, pero no solo devuelva el valor de error que obtuvo, ya que puede carecer de sentido para la persona que llama sin contexto.

Una manera realmente agradable y recomendada de delegación es Anotar errores . Esto significa que crea y devuelve un nuevo valor de error, pero el anterior también está envuelto en el valor devuelto, que proporciona el contexto.

Hay una biblioteca pública para anotar errores: github.com/pkg/errors ; y su godoc: errors

Básicamente tiene 2 funciones: 1 para envolver un error existente:

func Wrap(cause error, message string) error

Y uno para extraer un error envuelto:

func Cause(err error) error

Con estos, así es como se verá el manejo de errores:

func (o *ObjectOne) CheckValue() error { if o.someValue == 0 { return errors.New("Object1 illegal state: value is 0") } return nil }

Y el segundo nivel:

func (oT *ObjectTwoHigherLevel) CheckObjectOneIsReady() error { if err := oT.objectOne.CheckValue(); err != nil { return errors.Wrap(err, "Object2 illegal state: Object1 is invalid") } return nil }

Y el tercer nivel: llamar solo al segundo nivel de verificación:

func (oTh *ObjectThreeHiggerLevel) CheckObjectTwoIsReady() error { if err := oTh.ObjectTwoHigherLevel.CheckObjectOneIsReady(); err != nil { return errors.Wrap(err, "Object3 illegal state: Object2 is invalid") } return nil }

Tenga en cuenta que dado que los métodos CheckXX() no manejan los errores, no registran nada. Ellos están delegando errores anotados.

Si alguien que usa ObjectThreeHiggerLevel decide manejar el error:

o3 := &ObjectThreeHiggerLevel{} if err := o3.CheckObjectTwoIsReady(); err != nil { fmt.Println(err) }

El siguiente buen resultado será presentado:

Object3 illegal state: Object2 is invalid: Object2 illegal state: Object1 is invalid: Object1 illegal state: value is 0

No hay contaminación de registros múltiples, y todos los detalles y el contexto se conservan porque usamos errors.Wrap() que produce un valor de error que formatea una string que conserva los errores envueltos, recursivamente: la pila de errores .

Puede leer más sobre esta técnica en una publicación de blog:

Dave Cheney: No solo revise los errores, trátelos con gracia

Me pregunto cuál es la mejor forma de manejar el error de la abstracción de múltiples niveles en el momento . Cada vez que debo agregar una nueva abstracción de nivel al programa, me veo obligado a transferir el código de error del nivel menos al nivel alto. De este modo hay comunidades duplicadas en el archivo de registro o debo recordar para borrar el nivel de formulario de comunicación y transferirlo al nivel más alto. Debajo simplemente ejemplo. Me salté la creación de cada objeto para más código en breve y celar, pero creo que entiendes mi problema

type ObjectOne struct{ someValue int } func (o* ObjectOne)CheckValue()error{ if o.someValue == 0 { SomeLogger.Printf("Value is 0 error program") // communicate form first level abstraction to logger return errors.New("Internal value in object is 0") } return nil } type ObjectTwoHigherLevel struct{ objectOne ObjectOne } func (oT* ObjectTwoHigherLevel)CheckObjectOneIsReady() error{ if err := oT.objectOne.CheckValue() ; err != nil{ SomeLogger.Printf("Value in objectOne is not correct for objectTwo %s" , err) // second communicate return err } return nil } type ObjectThreeHiggerLevel struct{ oT ObjectTwoHigherLevel } func (oTh* ObjectThreeHiggerLevel)CheckObjectTwoIsReady()error{ if err := oTh.oT.CheckObjectOneIsReady() ; err != nil{ SomeLogger.Printf("Value in objectTwo is not correct for objectThree %s" , err) return err } return nil }

En resultado en el archivo de registro obtengo publicaciones duplicadas

Value is 0 error program Value in objectOne is not correct for objectTwo Internal value in object is 0 Value in objectTwo is not correct for objectThree Internal value in object is 0

A su vez, si solo transfiero algunos err a un nivel superior sin un registro adicional, pierdo información sobre lo que sucedió en cada nivel.

¿Cómo solucionó esto? ¿Cómo se comunica el duplicado privent? O mi camino es el bueno y el único?

El problema es más frustrante si creo algunos objetos que buscan algo en la base de datos en un nivel de abstracción y luego obtengo algunas líneas de esta misma tarea en logFile.


Hay varias bibliotecas que incrustan rastros de pila en Go errors. Simplemente crea tu error con uno de esos, y aparecerá con el contexto completo de la pila que luego podrás inspeccionar o registrar.

Una de esas bibliotecas:

https://github.com/go-errors/errors

Y hay algunos otros que olvidé.