Go Fehlerbehandlungstechniken [geschlossen]


108

Ich fange gerade erst mit Go an. Mein Code fängt an, viel davon zu haben:

   if err != nil {
      //handle err
   }

oder dieses

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

Gibt es einige gute Redewendungen / Strategien / Best Practices für die Überprüfung und Behandlung von Fehlern in Go?

BEARBEITEN, um zu verdeutlichen: Ich mache keine Bauchschmerzen oder schlage vor, dass das Go-Team sich etwas Besseres einfallen lässt. Ich frage, ob ich es richtig mache oder ob ich eine Technik verpasst habe, die die Community entwickelt hat. Vielen Dank an alle.


4
Nein, das gibt es nicht wirklich. Das ist ein oft diskutiertes und vernünftiges Thema. Es gab auch viele Evolutionsvorschläge. Die Antwort des Teams scheint zu sein, dass es in einem gut geschriebenen Code kein Problem sein sollte.
Denys Séguret


Beachten Sie, dass diese verwandte Frage nicht wirklich mit dieser identisch ist. Die Antworten sind zu spezifisch.
Denys Séguret

Es gibt auch einen Grund für diesen Ärger: Es macht es schwieriger, ein Programm schnell zu schreiben, aber es macht es auch schwieriger, Fehler durch einfaches erneutes Auslösen von Fehlern zu erstellen.
Denys Séguret

Sie finden Andrew Gerrand und Brad Fitzpatrick schreiben die Anfänge eines HTTP / 2-Clients in Go auf mehr oder weniger ähnliche Weise youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Antworten:


61

Ihr Code ist idiomatisch und meiner Meinung nach die beste verfügbare Methode. Einige würden sicher nicht zustimmen, aber ich würde argumentieren, dass dies der Stil ist, der in allen Standardbibliotheken in Golang zu finden ist . Mit anderen Worten, Go-Autoren schreiben die Fehlerbehandlung auf diese Weise.


12
"Go-Autoren schreiben die Fehlerbehandlung auf diese Weise." Klingt gut für mich.
Gmoore

"Einige würden mit Sicherheit anderer Meinung sein" : Ich bin nicht sicher, ob jemand sagen würde, dass dies nicht die derzeit beste bewährte Methode ist. Einige fragen nach Syntaxzucker oder anderen Änderungen, aber heute glaube ich nicht, dass ein ernsthafter Codierer Fehler sonst überprüfen würde.
Denys Séguret

@dystroy: OK, einige sagen " it sux ", andere nennen es "Fehler werden in Rückgabewerten behandelt. Stil der 70er Jahre." und so weiter ;-)
zzzz

2
@jnml Der Umgang mit Fehlern auf diese Weise ist eine Frage des Sprachdesigns, das ein sehr kontroverses Thema ist. Zum Glück stehen Dutzende von Sprachen zur Auswahl.
Fuz

4
Was mich nur umbringt, ist, wie für jeden einzelnen Funktionsaufruf das gleiche Muster verwendet wird. Dies macht den Code an einigen Stellen ziemlich laut und schreit nur nach syntaktischem Zucker, um den Code zu vereinfachen, ohne Informationen zu verlieren. Dies ist im Wesentlichen die Definition von Prägnanz (was meiner Meinung nach ein überlegenes Attribut als Ausführlichkeit ist, aber das ist wohl umstritten Punkt). Das Prinzip ist Klang, aber die Syntax lässt meiner Meinung nach zu wünschen übrig. Das Beschweren ist jedoch verboten, deshalb trinke ich jetzt einfach meine Kool-Hilfe ;-)
Thomas

30

Sechs Monate nachdem diese Frage gestellt wurde, schrieb Rob Pike einen Blog-Beitrag mit dem Titel Errors are Values .

Dort argumentiert er, dass Sie nicht in der vom OP dargestellten Weise programmieren müssen, und erwähnt mehrere Stellen in der Standardbibliothek, an denen sie ein anderes Muster verwenden.

Natürlich besteht eine häufige Aussage, die einen Fehlerwert beinhaltet, darin, zu testen, ob er Null ist, aber es gibt unzählige andere Dinge, die man mit einem Fehlerwert tun kann, und die Anwendung einiger dieser anderen Dinge kann Ihr Programm verbessern und einen Großteil der Boilerplate eliminieren Dies tritt auf, wenn jeder Fehler mit einer roten if-Anweisung überprüft wird.

...

Verwenden Sie die Sprache, um die Fehlerbehandlung zu vereinfachen.

Aber denken Sie daran: Was auch immer Sie tun, überprüfen Sie immer Ihre Fehler!

Es ist eine gute Lektüre.


Vielen Dank! Werde das überprüfen.
Gmoore

Der Artikel ist fantastisch, er führt im Grunde ein Objekt ein, das sich in einem fehlerhaften Zustand befinden kann, und wenn dies der Fall ist, ignoriert er einfach alles, was Sie damit machen, und bleibt in einem fehlerhaften Zustand. Für mich klingt es fast wie eine Monade.
Waterlink

@Waterlink Ihre Aussage ist bedeutungslos. Alles, was einen Zustand hat, ist fast monadisch, wenn man ein bisschen darauf blinzelt. Ein Vergleich mit en.wikipedia.org/wiki/Null_Object_pattern ist meiner Meinung nach nützlicher.
user7610

@ user7610, Danke für ein Feedback. Ich kann nur zustimmen.
Waterlink

2
Pike: Aber denk dran: Was auch immer du tust, überprüfe immer deine Fehler! - Das sind so 80er Jahre. Fehler können überall auftreten, Programmierer nicht mehr belasten und Ausnahmen für Pete übernehmen.
Slawomir

22

Ich würde der Antwort von jnml zustimmen, dass beide idiomatischer Code sind, und Folgendes hinzufügen:

Ihr erstes Beispiel:

if err != nil {
      //handle err
}

ist idiomatischer, wenn es sich um mehr als einen Rückgabewert handelt. beispielsweise:

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

Ihr zweites Beispiel ist eine nette Abkürzung, wenn Sie sich nur mit dem errWert befassen . Dies gilt, wenn die Funktion nur ein zurückgibt erroroder wenn Sie die zurückgegebenen Werte außer dem absichtlich ignorieren error. Als Beispiel wird dies manchmal mit den Funktionen Readerund Writerverwendet, die eine intder geschriebenen Bytes (manchmal unnötige Informationen) und Folgendes zurückgeben error:

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

Die zweite Form wird als Verwendung einer if-Initialisierungsanweisung bezeichnet .

Soweit ich weiß, haben Sie in Bezug auf Best Practices (mit Ausnahme des Pakets "Fehler" , um neue Fehler zu erstellen, wenn Sie sie benötigen) so ziemlich alles behandelt, was Sie über Fehler in Go!

EDIT: Wenn Sie feststellen , wirklich nicht leben , ohne Ausnahmen, können Sie sie mit Mimik defer, panicundrecover .


4

Ich habe eine Bibliothek für eine optimierte Fehlerbehandlung und das Weiterleiten durch eine Warteschlange von Go-Funktionen erstellt.

Sie finden es hier: https://github.com/go-on/queue

Es hat eine kompakte und eine ausführliche syntaktische Variante. Hier ist ein Beispiel für die kurze Syntax:

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

Bitte beachten Sie, dass es einen geringen Leistungsaufwand gibt, da Reflexion verwendet wird.

Auch dies ist kein idiomatischer Go-Code, daher sollten Sie ihn in Ihren eigenen Projekten verwenden oder wenn Ihr Team der Verwendung zustimmt.


3
Nur weil Sie dies tun können, heißt das nicht, dass es eine gute Idee ist. Dies sieht aus wie das Muster der Verantwortungskette , ist aber möglicherweise schwerer zu lesen (Meinung). Ich würde vorschlagen, dass es nicht "idiomatisch Go" ist. Interessant.
Steven Soroka

2

Eine "Strategie" zur Behandlung von Fehlern in Golang und anderen Sprachen besteht darin, Fehler kontinuierlich im Aufrufstapel zu verbreiten, bis Sie im Aufrufstapel hoch genug sind, um diesen Fehler zu behandeln. Wenn Sie versucht haben, diesen Fehler zu früh zu beheben, wird der Code wahrscheinlich wiederholt. Wenn Sie zu spät damit umgehen, brechen Sie etwas in Ihrem Code. Golang macht diesen Prozess sehr einfach, da es sehr deutlich macht, ob Sie einen Fehler an einem bestimmten Ort behandeln oder weitergeben.

Wenn Sie den Fehler ignorieren, zeigt ein einfaches _ diese Tatsache sehr deutlich. Wenn Sie damit umgehen, ist klar, welcher Fehlerfall Sie behandeln, da Sie in der if-Anweisung nachsehen werden.

Wie oben erwähnt, ist ein Fehler eigentlich nur ein normaler Wert. Dies behandelt es als solches.


2

Die Go-Götter haben in Go 2 einen "Entwurfsentwurf" für die Fehlerbehandlung veröffentlicht. Ziel ist es, die Fehlersprache zu ändern:

Übersicht und Design

Sie wollen Feedback von Benutzern!

Feedback-Wiki

Kurz sieht es so aus:

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

UPDATE: Der Entwurfsentwurf wurde vielfach kritisiert, daher habe ich Anforderungen für die Fehlerbehandlung mit Go 2 mit einem Menü mit Möglichkeiten für eine mögliche Lösung entworfen.


1

Die meisten in der Industrie befolgen die in der Golang-Dokumentation genannten Standardregeln. Fehlerbehandlung und Go . Außerdem hilft es bei der Erstellung von Dokumenten für Projekte.


Dies ist im Wesentlichen eine Nur-Link-Antwort. Ich würde vorschlagen, dass Sie der Antwort einige Inhalte hinzufügen, damit Ihre Antwort weiterhin von Nutzen ist, wenn der Link ungültig wird.
Neo

Vielen Dank für wertvolle Kommentare.
Pschilakanti

0

Im Folgenden ist mein Ansatz zur Reduzierung der Fehlerbehandlung für Go aufgeführt. Ein Beispiel ist das Abrufen von HTTP-URL-Parametern:

(Entwurfsmuster abgeleitet von 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
}

Das Aufrufen für mehrere mögliche Parameter wäre wie folgt:

    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
    }

Dies ist keine Silberkugel. Der Nachteil ist, dass Sie nur den letzten Fehler erhalten können, wenn Sie mehrere Fehler hatten.

Aber in diesem Fall ist es relativ repetitiv und mit geringem Risiko, daher kann ich nur den letztmöglichen Fehler bekommen.


-1

Du kannst Ihren Fehlerbehandlungscode auf ähnliche Fehler bereinigen (da Fehler Werte sind, müssen Sie hier vorsichtig sein) und eine Funktion schreiben, die Sie mit dem übergebenen Fehler aufrufen, um den Fehler zu behandeln. Sie müssen dann nicht jedes Mal "if err! = Nil {}" schreiben. Auch dies führt nur zu einer Bereinigung des Codes, aber ich denke nicht, dass dies die idiomatische Art ist, Dinge zu tun.

Nur weil du kannst, heißt das nicht, dass du es solltest .


-1

goerr erlaubt es, Fehler mit Funktionen zu behandeln

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. Eine solche Fehlerbehandlungslogik zu komplizieren / zu verbergen, ist IMO keine gute Idee. Der resultierende Code (der eine Quellvorverarbeitung durch goerr benötigt) ist schwerer zu lesen / zu begründen als der idiomatische Go-Code.
Dave C

-4

Wenn Sie eine präzise Fehlerkontrolle wünschen, ist dies möglicherweise nicht die Lösung, aber für mich ist jeder Fehler meistens ein Show-Stopper.

Also benutze ich stattdessen Funktionen.

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

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

Solche Nachrichten sollten stderreher an als gehen stdout, also einfach log.Fatal(err)oder verwenden log.Fatalln("some message:", err). Da fast nichts anderes als maineine solche Entscheidung treffen sollte, um das gesamte Programm zu beenden (dh Fehler von Funktionen / Methoden zurückgeben, nicht abbrechen), ist dies in seltenen Fällen das, was Sie tun möchten, es ist sauberer und besser, es explizit zu tun (dh if err := someFunc(); err != nil { log.Fatal(err) }) anstatt über eine "Hilfs" -Funktion, die unklar ist, was sie tut (der Name "Err" ist nicht gut, gibt keinen Hinweis darauf, dass das Programm möglicherweise beendet wird).
Dave C

Neues gelernt! Vielen Dank, dass Sie @ DaveC
Gon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.