Ir a Técnicas de manejo de errores [cerrado]

108

Recién estoy comenzando con Go. Mi código está empezando a tener mucho de esto:

   if err != nil {
      //handle err
   }

o esto

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

¿Existen algunos buenos modismos / estrategias / mejores prácticas para verificar y manejar errores en Go?

EDITAR para aclarar: no estoy regañando ni sugiriendo que el equipo de Go proponga algo mejor. Estoy preguntando si lo estoy haciendo bien o si me perdí alguna técnica que se le ocurrió a la comunidad. Gracias a todos.

gmoore
fuente
4
No, no lo hay realmente. Ese es un tema que se discute a menudo y es sensato. También hubo muchas propuestas de evolución. La respuesta del equipo parece ser que no debería ser un problema en un código bien escrito.
Denys Séguret
Tenga en cuenta que esta pregunta relacionada no es realmente la misma que esta. Las respuestas son demasiado específicas.
Denys Séguret
También hay una razón para esta molestia: dificulta la escritura rápida de un programa, pero también dificulta la creación de errores simplemente volviendo a generar errores.
Denys Séguret
Puede encontrar que Andrew Gerrand y Brad Fitzpatrick escriben los inicios de un cliente HTTP / 2 en Go de una manera más o menos similar youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Respuestas:

61

Su código es idiomático y, en mi opinión, es la mejor práctica disponible. Algunos estarían en desacuerdo con seguridad, pero yo diría que este es el estilo que se ve en todas las bibliotecas estándar de Golang . En otras palabras, los autores de Go escriben el manejo de errores de esta manera.

zzzz
fuente
12
"Los autores de Go escriben el manejo de errores de esta manera". Suena bien para mí.
gmoore
"Algunos estarían en desacuerdo con seguridad" : no estoy seguro de que alguien diga que no es la mejor práctica disponible en la actualidad. Algunos piden azúcar de sintaxis u otros cambios, pero hoy en día no creo que ningún codificador serio verifique los errores de otra manera.
Denys Séguret
@dystroy: OK, algunos dicen " it sux ", otros lo llaman "los errores se manejan con valores de retorno. Estilo de los 70". , y así sucesivamente ;-)
zzzz
2
@jnml Manejar errores de esta manera es una cuestión de diseño del lenguaje, que es un tema muy controvertido. Afortunadamente, hay decenas de idiomas para elegir.
fuz
4
Lo que me mata es cómo se usa el mismo patrón para cada llamada de función. Esto hace que el código sea bastante ruidoso en algunos lugares y simplemente está pidiendo azúcar sintáctico para simplificar el código sin perder ninguna información, que es esencialmente la definición de concisión (que creo que es un atributo superior a la verbosidad, pero podría decirse que es un tema polémico punto). El principio es sólido, pero la sintaxis deja mucho que desear en mi humilde opinión. Sin embargo, las quejas están prohibidas, así que beberé mi kool-aid ahora ;-)
Thomas
30

Seis meses después de que se hiciera esta pregunta, Rob Pike escribió una publicación de blog titulada Los errores son valores .

Allí argumenta que no es necesario programar de la forma presentada por el OP, y menciona varios lugares en la biblioteca estándar donde usan un patrón diferente.

Por supuesto, una declaración común que involucra un valor de error es probar si es nulo, pero hay muchas otras cosas que se pueden hacer con un valor de error, y la aplicación de algunas de esas otras cosas puede mejorar su programa, eliminando gran parte del texto estándar que surge si cada error se verifica con una instrucción if de memoria.

...

Utilice el lenguaje para simplificar su manejo de errores.

Pero recuerde: hagas lo que hagas, ¡siempre revisa tus errores!

Es una buena lectura.

Michael Deardeuff
fuente
¡Gracias! Lo comprobaré.
gmoore
El artículo es asombroso, básicamente presenta un objeto que puede estar en estado fallido, y si lo está, simplemente ignorará todo lo que haces con él y permanecerá en estado fallido. A mí me suena a casi mónada.
Waterlink
@Waterlink Su declaración no tiene sentido. Todo lo que tiene un estado es casi mónada, si lo miras un poco. Compararlo con en.wikipedia.org/wiki/Null_Object_pattern es más útil, creo.
user7610
@ user7610, Gracias por tus comentarios. Solo puedo estar de acuerdo.
Waterlink
2
Pike: "Pero recuerda: ¡Hagas lo que hagas, siempre revisa tus errores!" - esto es tan de los 80. Los errores pueden ocurrir en cualquier lugar, dejar de sobrecargar a los programadores y adoptar excepciones por el bien de Pete.
Slawomir
22

Estoy de acuerdo con la respuesta de jnml de que ambos son códigos idiomáticos y agrego lo siguiente:

Tu primer ejemplo:

if err != nil {
      //handle err
}

es más idiomático cuando se trata de más de un valor de retorno. por ejemplo:

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

Su segundo ejemplo es una buena taquigrafía cuando solo se ocupa del errvalor. Esto se aplica si la función solo devuelve un error, o si ignora deliberadamente los valores devueltos que no sean error. Como ejemplo, esto a veces se usa con las funciones Readery Writerque devuelven un intdel número de bytes escritos (a veces información innecesaria) y un error:

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

La segunda forma se denomina uso de una instrucción de inicialización if .

Entonces, en lo que respecta a las mejores prácticas, hasta donde yo sé (excepto por el uso del paquete "errors" para crear nuevos errores cuando los necesite), ha cubierto prácticamente todo lo que necesita saber sobre los errores en Go!

EDIT: Si descubre que realmente no puede vivir sin excepciones, se puede imitar con defer, panicyrecover .

Intermernet
fuente
4

Hice una biblioteca para simplificar el manejo de errores y la canalización a través de una cola de funciones de Go.

Puede encontrarlo aquí: https://github.com/go-on/queue

Tiene una variante sintáctica compacta y verbosa. A continuación, se muestra un ejemplo de la sintaxis corta:

import "github.com/go-on/queue/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    err := q.Q(                      
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        // q.V pipes the json from the previous function call
        json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()

    if err != nil {
       switch err {
         case *json.SyntaxError:
           ...
       }
    }
}

Tenga en cuenta que hay un poco de sobrecarga de rendimiento, ya que hace uso de la reflexión.

Además, este no es un código go idiomático, por lo que querrá usarlo en sus propios proyectos, o si su equipo está de acuerdo en usarlo.

metakeule
fuente
3
El hecho de que pueda hacer esto no significa que sea una buena idea. Esto se parece al patrón de Cadena de responsabilidad , excepto que quizás sea más difícil de leer (opinión). Sugeriría que no es "Go idiomático". Interesante, sin embargo.
Steven Soroka
2

Una "estrategia" para manejar errores en golang y en otros lenguajes es propagar continuamente errores en la pila de llamadas hasta que esté lo suficientemente alto en la pila de llamadas para manejar ese error. Si intentó manejar ese error demasiado pronto, es probable que termine repitiendo el código. Si lo maneja demasiado tarde, romperá algo en su código. Golang hace que este proceso sea muy fácil, ya que deja muy claro si está manejando un error en una ubicación determinada o propagándolo.

Si va a ignorar el error, un simple _ revelará este hecho con mucha claridad. Si lo está manejando, entonces exactamente qué caso del error está manejando es claro, ya que lo verificará en la declaración if.

Como la gente dijo anteriormente, un error es en realidad solo un valor normal. Esto lo trata como tal.

himanshu ojha
fuente
2

Los dioses Go han publicado un "borrador de diseño" para el manejo de errores en Go 2. Su objetivo es cambiar el idioma de los errores:

Resumen y diseño

¡Quieren comentarios de los usuarios!

Wiki de comentarios

Brevemente, se ve así:

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

ACTUALIZACIÓN: El diseño preliminar ha recibido muchas críticas, por lo que redacté los Requisitos a considerar para el manejo de errores de Go 2 con un menú de posibilidades para una solución eventual.

Liam
fuente
1

La mayoría en la industria siguen las reglas estándar mencionadas en la documentación de golang Manejo de errores y Go . Y también ayuda a la generación de documentos para proyectos.

pschilakanti
fuente
Esta es esencialmente una respuesta de solo enlace. Le sugiero que agregue algo de contenido a la respuesta para que si el enlace dejara de ser válido, su respuesta aún sea útil.
Neo
gracias por el valioso comentario.
pschilakanti
0

A continuación se muestra mi opinión sobre la reducción del manejo de errores en Go, el ejemplo es para cuando se obtienen parámetros de URL HTTP:

(Patrón de diseño derivado de https://blog.golang.org/errors-are-values )

type HTTPAdapter struct {
    Error *common.AppError
}

func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
    requestUUID := uuid.Parse(mux.Vars(r)[param])
    if requestUUID == nil { 
        adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
            possibleError, http.StatusBadRequest)
    }
    return requestUUID
}

llamarlo para múltiples parámetros posibles sería el siguiente:

    adapter := &httphelper.HTTPAdapter{}
    viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
    messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
    if adapter.Error != nil {
        return nil, adapter.Error
    }

Esta no es una solución milagrosa, la desventaja es que si tuvo varios errores, solo puede obtener el último error.

Pero en este caso es relativamente repetitivo y de bajo riesgo, por lo tanto, puedo obtener el último error posible.

mel3kings
fuente
-1

Tu puedes limpiar su código de manejo de errores para errores similares (ya que los errores son valores que hay que tener cuidado aquí) y escribe una función que se llama con el error en el pasado para controlar el error. No tendrá que escribir "if err! = Nil {}" cada vez que lo haga. Nuevamente, esto solo resultará en la limpieza del código, pero no creo que sea la forma idiomática de hacer las cosas.

Una vez más, solo porque puedas no significa que debas hacerlo .

20kLigas
fuente
-1

goerr permite manejar errores con funciones

package main

import "github.com/goerr/goerr"
import "fmt"

func ok(err error) {
    if err != nil {
        goerr.Return(err)
        // returns the error from do_somethingN() to main()
        // sequence() is terminated
    }
}

func sequence() error {
    ok(do_something1())
    ok(do_something2())
    ok(do_something3())

    return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
    fmt.Println("DOING 3")
    return nil
}

func main() {
    err_do_something := goerr.OR1(sequence)

    // handle errors

    fmt.Println(err_do_something)
}
Anlhord Smithson
fuente
Ick. En mi opinión, no es una buena idea complicar / ocultar la lógica de manejo de errores como esta. El código resultante (que necesita preprocesamiento de la fuente por parte de goerr) es más difícil de leer / razonar que el código idiomático de Go.
Dave C
-4

Si desea un control preciso de los errores, esta puede no ser la solución, pero para mí, la mayoría de las veces, cualquier error es un obstáculo.

Entonces, uso funciones en su lugar.

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)
Gon
fuente
Estos mensajes deberían ir a en stderrlugar de stdout, así que solo use log.Fatal(err)o log.Fatalln("some message:", err). Dado que casi nada más que maindebería tomar la decisión de finalizar todo el programa (es decir, devolver errores de funciones / métodos, no abortar), en el raro caso, esto es lo que desea hacer, es más limpio y mejor hacerlo explícitamente (es decir, if err := someFunc(); err != nil { log.Fatal(err) }) en lugar de a través de una función "auxiliar" que no tiene claro lo que está haciendo (el nombre "Err" no es bueno, no da ninguna indicación de que pueda terminar el programa).
Dave C
¡Aprendí algo nuevo! Gracias @DaveC
Gon