log error debug error-handling go chaining

error-handling - error - jstl system out



Ir al método de encadenamiento y manejo de errores. (5)

Quiero crear una API de encadenamiento de métodos en Go. En todos los ejemplos puedo encontrar que las operaciones encadenadas parecen tener éxito, lo cual no puedo garantizar. Por lo tanto, trato de extender estos para agregar el valor de retorno de error.

Si lo hago asi

package main import "fmt" type Chain struct { } func (v *Chain)funA() (*Chain, error ) { fmt.Println("A") return v, nil } func (v *Chain)funB() (*Chain, error) { fmt.Println("B") return v, nil } func (v *Chain)funC() (*Chain, error) { fmt.Println("C") return v, nil } func main() { fmt.Println("Hello, playground") c := Chain{} d, err := c.funA().funB().funC() // line 24 }

El compilador me dice chain-err-test.go:24: multiple-value c.funA() in single-value context y no compila. ¿Hay una buena manera de que funcA, funcB y funcC puedan reportar un error y detener esa cadena?


¿Hay una buena manera de que funcA, funcB y funcC puedan reportar un error y detener esa cadena?

Desafortunadamente, no, no hay una buena solución para su problema. Las soluciones son lo suficientemente complejas (agregando canales de error, etc.) que el costo excede la ganancia.

El encadenamiento de métodos no es una expresión idiomática en Go (al menos no para los métodos que posiblemente puedan tener errores). Esto no se debe a que haya algo particularmente incorrecto con las cadenas de métodos, sino una consecuencia del lenguaje de devolver errores en lugar de entrar en pánico. Las otras respuestas son soluciones alternativas, pero ninguna es idiomática.

¿Puedo preguntar si no es idiomático encadenar métodos en Go debido a la consecuencia de devolver el error como lo hacemos en Go, o es más generalmente una consecuencia de tener múltiples devoluciones de métodos?

Buena pregunta, pero no es porque Go admite varias devoluciones. Python admite múltiples devoluciones, y Java también puede hacerlo a través de una clase Tuple<T1, T2> ; Las cadenas de métodos son comunes en ambos idiomas. La razón por la que estos idiomas pueden salirse con la suya es que comunican errores de forma idiomática a través de excepciones. Las excepciones detienen la cadena de métodos inmediatamente y saltan al controlador de excepciones correspondiente. Este es el comportamiento que los desarrolladores de Go intentaron evitar específicamente al elegir devolver los errores en su lugar.


¿Qué le parece este enfoque? Cree una estructura que delegue la Chain y el error , y devuélvala en lugar de dos valores. p.ej:

package main import "fmt" type Chain struct { } type ChainAndError struct { *Chain error } func (v *Chain)funA() ChainAndError { fmt.Println("A") return ChainAndError{v, nil} } func (v *Chain)funB() ChainAndError { fmt.Println("B") return ChainAndError{v, nil} } func (v *Chain)funC() ChainAndError { fmt.Println("C") return ChainAndError{v, nil} } func main() { fmt.Println("Hello, playground") c := Chain{} result := c.funA().funB().funC() // line 24 fmt.Println(result.error) }


Puedes hacer que tu cadena sea perezosa recolectando una porción de funciones.

package main import ( "fmt" ) type ( chainFunc func() error funcsChain struct { funcs []chainFunc } ) func Chain() funcsChain { return funcsChain{} } func (chain funcsChain) Say(s string) funcsChain { f := func() error { fmt.Println(s) return nil } return funcsChain{append(chain.funcs, f)} } func (chain funcsChain) TryToSay(s string) funcsChain { f := func() error { return fmt.Errorf("don''t speek golish") } return funcsChain{append(chain.funcs, f)} } func (chain funcsChain) Execute() (i int, err error) { for i, f := range chain.funcs { if err := f(); err != nil { return i, err } } return -1, nil } func main() { i, err := Chain(). Say("Hello, playground"). TryToSay("go cannot into chains"). Execute() fmt.Printf("i: %d, err: %s", i, err) }


Puedes intentarlo así: https://play.golang.org/p/dVn_DGWt1p_H

package main import ( "errors" "fmt" ) type Chain struct { err error } func (v *Chain) funA() *Chain { if v.err != nil { return v } fmt.Println("A") return v } func (v *Chain) funB() *Chain { if v.err != nil { return v } v.err = errors.New("error at funB") fmt.Println("B") return v } func (v *Chain) funC() *Chain { if v.err != nil { return v } fmt.Println("C") return v } func main() { c := Chain{} d := c.funA().funB().funC() fmt.Println(d.err) }


Si tiene control sobre el código y la firma de la función es idéntica, puede escribir algo como:

func ChainCall(fns ...func() (*Chain, error)) (err error) { for _, fn := range fns { if _, err = fn(); err != nil { break } } return }

playground