Go Error Handling Techniques [closed]


108

Ho appena iniziato con Go. Il mio codice sta iniziando ad avere molto di questo:

   if err != nil {
      //handle err
   }

o questo

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

Ci sono alcuni buoni idiomi / strategie / best practice per il controllo e la gestione degli errori in Go?

MODIFICA per chiarire: non sto mal di pancia o suggerisco che il team di Go escogiti qualcosa di meglio. Chiedo se lo sto facendo bene o ho perso qualche tecnica che la comunità ha escogitato. Ringrazia tutti.


4
No, non c'è davvero. Questo è un argomento spesso discusso e sensato. C'erano anche molte proposte di evoluzione. La risposta del team sembra essere che non dovrebbe essere un problema in un codice ben scritto.
Denys Séguret


Nota che questa domanda correlata non è proprio la stessa di questa. Le risposte sono troppo specifiche.
Denys Séguret

C'è anche una logica per questo fastidio: rende più difficile scrivere velocemente un programma, ma rende anche più difficile creare bug semplicemente rilanciando gli errori.
Denys Séguret

Puoi trovare Andrew Gerrand e Brad Fitzpatrick scrivere gli inizi di un client HTTP / 2 in Go in modo più o meno simile youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Risposte:


61

Il tuo codice è idiomatico e secondo me è la migliore pratica disponibile. Alcuni sarebbero sicuramente in disaccordo, ma direi che questo è lo stile visto in tutte le librerie standard di Golang . In altre parole, gli autori di Go scrivono la gestione degli errori in questo modo.


12
"Vai autori scrivono la gestione degli errori in questo modo." Per me va bene.
gmoore

"Alcuni non sarebbero d'accordo" : non sono sicuro che qualcuno direbbe che non è la migliore pratica disponibile oggi. Alcuni chiedono zucchero alla sintassi o altre modifiche, ma oggi non penso che nessun programmatore serio controllerebbe gli errori altrimenti.
Denys Séguret

@dystroy: OK, alcuni dicono " it sux ", altri lo chiamano "gli errori vengono gestiti nei valori di ritorno. Stile anni '70". e così via ;-)
zzzz

2
@jnml La gestione degli errori in questo modo è una questione di progettazione del linguaggio, che è un argomento molto controverso. Fortunatamente ci sono dozzine di lingue tra cui scegliere.
fuz

4
Quello che mi uccide è il modo in cui lo stesso modello viene utilizzato per ogni singola chiamata di funzione. Questo rende il codice piuttosto rumoroso in alcuni punti e sta solo urlando per lo zucchero sintattico per semplificare il codice senza perdere alcuna informazione, che è essenzialmente la definizione di concisione (che credo sia un attributo superiore alla verbosità, ma questo è probabilmente un controverso punto). Il principio è valido, ma la sintassi lascia molto a desiderare IMHO. Tuttavia lamentarsi è vietato, quindi berrò il mio kool-aid ora ;-)
Thomas

30

Sei mesi dopo questa domanda, Rob Pike ha scritto un post sul blog intitolato Gli errori sono valori .

Lì afferma che non è necessario programmare nel modo presentato dall'OP e menziona diversi punti nella libreria standard in cui usano un modello diverso.

Ovviamente un'affermazione comune che coinvolge un valore di errore è verificare se è nullo, ma ci sono innumerevoli altre cose che si possono fare con un valore di errore e l'applicazione di alcune di queste altre cose può migliorare il tuo programma, eliminando gran parte del boilerplate che si verifica se ogni errore viene controllato con un'istruzione if meccanica.

...

Usa la lingua per semplificare la gestione degli errori.

Ma ricorda: qualunque cosa tu faccia, controlla sempre i tuoi errori!

È una buona lettura.


Grazie! Lo verificherò.
gmoore

L'articolo è fantastico, fondamentalmente introduce un oggetto che può essere in uno stato non riuscito, e se lo è ignorerà semplicemente tutto ciò che fai con esso e rimarrà nello stato fallito. A me suona quasi come una monade.
Waterlink

@Waterlink La tua dichiarazione non ha senso. Tutto ciò che ha uno stato è quasi monade, se lo osservi un po '. Confrontarlo con en.wikipedia.org/wiki/Null_Object_pattern è più utile, penso.
user7610

@ user7610, grazie per il feedback. Posso solo essere d'accordo.
Waterlink

2
Pike: "Ma ricorda: qualunque cosa tu faccia, controlla sempre i tuoi errori!" - questo è così anni '80. Gli errori possono verificarsi ovunque, smettere di appesantire i programmatori e adottare eccezioni per il bene di Pete.
Slawomir

22

Sono d'accordo con la risposta di jnml che sono entrambi codici idiomatici e aggiungerei quanto segue:

Il tuo primo esempio:

if err != nil {
      //handle err
}

è più idiomatico quando si tratta di più di un valore di ritorno. per esempio:

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

Il tuo secondo esempio è una bella scorciatoia quando hai a che fare solo con il errvalore. Ciò si applica se la funzione restituisce solo un error, o se si ignorano deliberatamente i valori restituiti diversi da error. Ad esempio, a volte viene utilizzato con le funzioni Readere Writerche restituiscono un intnumero di byte scritti (a volte informazioni non necessarie) e un error:

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

La seconda forma viene definita utilizzando un'istruzione di inizializzazione if .

Quindi, per quanto riguarda le migliori pratiche, per quanto ne so (eccetto per l'utilizzo del pacchetto "errors" per creare nuovi errori quando ne avete bisogno) avete coperto praticamente tutto ciò che dovete sapere sugli errori in Go!

EDIT: Se scopri che davvero non puoi vivere senza eccezioni, puoi imitarle con defer, panic&recover .


4

Ho creato una libreria per la gestione degli errori semplificata e il piping attraverso una coda di funzioni Go.

Puoi trovarlo qui: https://github.com/go-on/queue

Ha una variante sintattica compatta e una verbosa. Ecco un esempio per la sintassi breve:

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:
           ...
       }
    }
}

Si prega di essere consapevoli del fatto che c'è un piccolo sovraccarico sulle prestazioni, poiché utilizza la riflessione.

Inoltre questo non è un codice go idiomatico, quindi vorrai usarlo nei tuoi progetti o se il tuo team è d'accordo nell'usarlo.


3
Solo perché puoi farlo, non significa che sia una buona idea. Questo sembra il modello della catena di responsabilità , tranne forse più difficile da leggere (opinione). Suggerirei che non è "idiomatico Go". Interessante, però.
Steven Soroka

2

Una "strategia" per la gestione degli errori in golang e in altri linguaggi consiste nel propagare continuamente gli errori nello stack di chiamate fino a quando non si è abbastanza in alto nello stack di chiamate da gestire quell'errore. Se hai provato a gestire l'errore troppo presto, probabilmente finirai per ripetere il codice. Se lo gestisci troppo tardi, romperai qualcosa nel tuo codice. Golang rende questo processo estremamente semplice in quanto rende estremamente chiaro se stai gestendo un errore in una data posizione o se lo stai propagando.

Se ignorerai l'errore, un semplice _ rivelerà questo fatto molto chiaramente. Se lo stai gestendo, allora esattamente quale caso dell'errore stai gestendo è chiaro poiché lo controllerai nell'istruzione if.

Come le persone hanno detto sopra, un errore è in realtà solo un valore normale. Questo lo tratta come tale.


2

Gli dei di Go hanno pubblicato una "bozza di progetto" per la gestione degli errori in Go 2. Ha lo scopo di cambiare l'idioma degli errori:

Panoramica e design

Vogliono feedback dagli utenti!

Wiki di feedback

In breve, sembra che:

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

AGGIORNAMENTO: La bozza del progetto ha ricevuto molte critiche, quindi ho redatto i requisiti da considerare per la gestione degli errori di Go 2 con un menu di possibilità per un'eventuale soluzione.


1

La maggior parte del settore segue le regole standard menzionate nella documentazione Golang Gestione degli errori e Go . E aiuta anche la generazione di documenti per il progetto.


Questa è essenzialmente una risposta solo link. Ti suggerirei di aggiungere del contenuto alla risposta in modo che se il link non fosse più valido, la tua risposta sarebbe comunque utile.
Neo

grazie per il prezioso commento.
pschilakanti

0

Di seguito è riportato il mio parere sulla riduzione della gestione degli errori per Go, l'esempio è per quando si ottengono i parametri URL HTTP:

(Modello di progettazione derivato da 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
}

chiamandolo per più parametri possibili sarebbe il seguente:

    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
    }

Questo non è un proiettile d'argento, lo svantaggio è che se hai più errori puoi ottenere solo l'ultimo errore.

Ma in questo caso è relativamente ripetitivo e a basso rischio, quindi posso solo ottenere l'ultimo errore possibile.


-1

È possibile ripulire il codice di gestione degli errori per gli errori simili (dal momento che gli errori sono valori che bisogna stare attenti qui) e scrivere una funzione che si chiama con l'errore passata per gestire l'errore. Allora non dovrai scrivere "if err! = Nil {}" ogni volta. Di nuovo, questo risulterà solo nella pulizia del codice, ma non penso che sia il modo idiomatico di fare le cose.

Anche in questo caso, solo perché si può non significa che si dovrebbe .


-1

goerr permette di gestire gli errori con le funzioni

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)
}

Ick. Complicare / nascondere la logica di gestione degli errori come questa non è una buona idea IMO. Il codice risultante (che richiede la pre-elaborazione del sorgente da parte di goerr) è più difficile da leggere / ragionare rispetto al codice Go idiomatico.
Dave C

-4

Se vuoi un controllo preciso degli errori, questa potrebbe non essere la soluzione, ma per me, il più delle volte, qualsiasi errore è un ostacolo.

Quindi, uso invece le funzioni.

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

fi, err := os.Open("mmm.txt")
Err(err)

Tali messaggi dovrebbero andare a stderranziché stdout, quindi usa semplicemente log.Fatal(err)o log.Fatalln("some message:", err). Poiché quasi nient'altro che maindovrebbe prendere una tale decisione di terminare l'intero programma (cioè restituire errori da funzioni / metodi, non interrompere) nel raro caso questo è quello che vuoi fare è più pulito e meglio farlo esplicitamente (es. if err := someFunc(); err != nil { log.Fatal(err) }) piuttosto che tramite una funzione "helper" che non è chiara su ciò che sta facendo (il nome "Err" non è buono, non dà alcuna indicazione che possa terminare il programma).
Dave C

Cosa nuova appresa! Grazie @DaveC
Gon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.