Die Kunst der Fehlererstellung: Verstehen von errors.New und fmt.Errorf in Go
Emily Parker
Product Engineer · Leapcell

Go's Ansatz zur Fehlerbehandlung ist idiomatisch: Fehler sind nur Werte. Diese Einfachheit verbirgt ein mächtiges und flexibles System, aber wie jedes mächtige Werkzeug erfordert es Verständnis, um es effektiv einzusetzen. Zwei grundlegende Funktionen zur Erstellung von Fehlern sind errors.New
und fmt.Errorf
. Obwohl beide eine error
-Schnittstelle zurückgeben, unterscheiden sich ihre beabsichtigten Anwendungsfälle und zugrunde liegenden Fähigkeiten erheblich. Das Verständnis dieser Unterschiede ist entscheidend für das Schreiben robuster, wartbarer und debugfähiger Go-Anwendungen.
errors.New
: Der einfache, undurchsichtige Fehler
Die Funktion errors.New
ist der einfachste Weg, einen grundlegenden Fehler zu erstellen. Sie nimmt ein einziges String-Argument entgegen und gibt einen error
-Wert zurück, der die Error()
-Methode implementiert, indem sie diesen String zurückgibt.
package main import ( "errors" "fmt" ) func validateInput(input string) error { if input == "" { return errors.New("input cannot be empty") } if len(input) > 20 { return errors.New("input exceeds max length of 20 characters") } return nil } func main() { if err := validateInput(""); err != nil { fmt.Println("Error:", err) } if err := validateInput("This is a very long input string that will definitely exceed the limit."); err != nil { fmt.Println("Error:", err) } if err := validateInput("valid input"); err == nil { fmt.Println("Input is valid.") } }
Wann errors.New
verwendet werden sollte:
-
Sentinel Errors: Sein primärer Anwendungsfall ist die Definition von "Sentinel Errors" – spezifische, vordefinierte Fehlerwerte, die Aufrufer mithilfe direkter Gleichheit (
==
) überprüfen können. Diese Fehler vermitteln eine sehr spezifische Art von Fehler.package main import ( "errors" "fmt" ) var ErrNotFound = errors.New("item not found") var ErrPermissionDenied = errors.New("permission denied") func findItem(id int) (string, error) { if id == 1001 { return "", ErrNotFound } if id == 2002 { return "", ErrPermissionDenied } return fmt.Sprintf("Item-%d", id), nil } func main() { if _, err := findItem(1001); err != nil { if errors.Is(err, ErrNotFound) { fmt.Println("Client error: The requested item was not found.") } else if errors.Is(err, ErrPermissionDenied) { fmt.Println("Authentication error: You do not have permission.") } else { fmt.Println("Unexpected error:", err) } } }
In diesem Beispiel sind
ErrNotFound
undErrPermissionDenied
unterschiedliche Sentinel-Fehler, die eine präzise Fehlerbehandlung ermöglichen. Die Funktionerrors.Is
, die in Go 1.13 eingeführt wurde, ist der kanonische Weg zur Überprüfung von Sentinel-Fehlern, der auch umwickelte Fehler korrekt behandelt (mehr dazu mitfmt.Errorf
). -
Einfache, statische Fehler: Wenn die Fehlermeldung fest ist und keine dynamischen Informationen benötigt.
Einschränkungen von errors.New
:
- Mangel an Kontext: Es stellt nur einen statischen String bereit. Sie können keine dynamischen Informationen einbeziehen, die für das Auftreten des Fehlers spezifisch sind (z. B. ein bestimmter Wert, der den Fehler verursacht hat, ein Dateipfad, der nicht geöffnet werden konnte, oder der zugrunde liegende Fehler, der zu diesem geführt hat).
- Kein Error Wrapping: Sie können keinen zugrunde liegenden Fehler mit
errors.New
umwickeln, was bedeutet, dass Sie den Aufrufstack und den Kontext des ursprünglichen Fehlers verlieren. Dies erschwert die Fehlersuche erheblich.
fmt.Errorf
: Dynamische, kontextbezogene und umwickelbare Fehler
Die Funktion fmt.Errorf
ist eine mächtigere und vielseitigere Methode zur Erstellung von Fehlern. Sie verhält sich ähnlich wie fmt.Sprintf
und ermöglicht es Ihnen, eine Fehlermeldung mithilfe verschiedener Verben zu formatieren. Entscheidend ist, dass sie das %w
-Verb für Error Wrapping unterstützt.
package main import ( "errors" "fmt" "os" ) func readFile(filepath string) ([]byte, error) { data, err := os.ReadFile(filepath) if err != nil { // Hier wickeln wir den zugrunde liegenden Fehler, der von os.ReadFile zurückgegeben wird, ein. // Das %w-Verb signalisiert, dass 'err' der umwickelte Fehler ist. return nil, fmt.Errorf("failed to read file '%s': %w", filepath, err) } return data, nil } func processFile(filename string) error { _, err := readFile(filename) if err != nil { // Wir können erneut umwickeln und auf dieser Ebene weiteren Kontext hinzufügen. return fmt.Errorf("error processing data from %s: %w", filename, err) } return nil } func main() { // Simulieren eines Fehlers "Datei nicht gefunden" if err := processFile("non_existent_file.txt"); err != nil { fmt.Println("Main handler received error:", err) // Gibt die gesamte Fehlerkette aus. // Prüfen, ob der ursprüngliche Fehler ein "nicht gefunden"-Fehler aus dem os-Paket war var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Printf("Specifically, it was a PathError for path: %s, operation: %s\n", pathErr.Path, pathErr.Op) } // Prüfen, ob ein bestimmter Sentinel-Fehler in der Kette vorhanden ist (auch wenn tief verschachtelt) if errors.Is(err, os.ErrNotExist) { fmt.Println("The ultimate cause was that the file did not exist.") } } // Weiteres Beispiel, das dynamische Fehlermeldungen zeigt userID := 123 dbErr := errors.New("database connection failed") err := fmt.Errorf("failed to fetch user %d data: %w", userID, dbErr) fmt.Println(err) }
Wann fmt.Errorf
verwendet werden sollte:
-
Dynamische Fehlermeldungen: Wenn die Fehlermeldung spezifische Werte aus dem Kontext des Fehlers enthalten muss (z. B. ungültige Eingabewerte, fehlgeschlagene Ressourcen-IDs).
fmt.Errorf("invalid age %d; must be positive", age)
-
Error Wrapping (
%w
): Dies ist der wichtigste Unterschied. Wenn eine Funktion einen Fehler von einer Abhängigkeit (ein anderer Funktionsaufruf, eine Bibliothek, das Betriebssystem) feststellt, sollte sie diesen Fehler typischerweise umwickeln, um zusätzlichen Kontext bereitzustellen. Das Umwickeln ermöglicht es Ihnen, die ursprüngliche Fehlerkette zu erhalten, was für die Fehlersuche von unschätzbarem Wert ist.errors.Is(err, target)
: Prüft, oberr
oder ein beliebiger Fehler in seiner Kettetarget
ist. Ideal zum Überprüfen von Sentinel-Fehlern.errors.As(err, &target)
: Entpackterr
, bis es einen Fehler findet, dertarget
zugewiesen werden kann. Nützlich zum Überprüfen spezifischer Fehlertypen (z. B.*os.PathError
,*net.OpError
) und zum Extrahieren von Informationen daraus.errors.Unwrap(err)
: Gibt den vonerr
umwickelten Fehler zurück, odernil
, wennerr
keinen Fehler umwickelt. Dies wird hauptsächlich intern vonerrors.Is
underrors.As
verwendet.
Vorteile des Error Wrapping:
- Bewahrt die Grundursache: Sie können die gesamte Fehlersequenz nachvollziehen, die zum endgültigen Fehler geführt hat.
- Kontextbezogene Informationen: Jede Schicht im Aufrufstack kann ihre eigenen kontextbezogenen Informationen hinzufügen, wodurch die Fehlermeldung angereichert wird, ohne die ursprünglichen Details zu verlieren.
- Programmatische Inspektion: Code auf höherer Ebene kann immer noch nach spezifischen Fehlern auf niedriger Ebene suchen (mithilfe von
errors.Is
odererrors.As
), ohne Fehlermeldungen parsen zu müssen. So ist eine robustere und weniger fehleranfällige Fehlerbehandlungslogik möglich.
Wahl zwischen errors.New
und fmt.Errorf
Hier ist ein vereinfachter Entscheidungsbaum:
- **Müssen Sie auf diesen exakten Fehlerwert mit
errors.Is()
prüfen?- Ja: Definieren Sie eine
var
miterrors.New
(z. B.var ErrInvalidInput = errors.New("invalid input")
).
- Ja: Definieren Sie eine
- Muss die Fehlermeldung dynamische Informationen enthalten (z. B. spezifische Werte, IDs)?
- Ja: Verwenden Sie
fmt.Errorf
.
- Ja: Verwenden Sie
- **Propagieren Sie einen Fehler von einer Funktion/Abhängigkeit auf niedrigerer Ebene?
- Ja: Verwenden Sie
fmt.Errorf
mit%w
, um den zugrunde liegenden Fehler zu umwickeln.
- Ja: Verwenden Sie
- Erstellen Sie einen völlig neuen Fehler, der keinen bestehenden umwickelt, und die Nachricht ist statisch?
- Ja: Sie könnten
errors.New
zur Einfachheit verwenden, aberfmt.Errorf("static error message")
wird oft aus Gewohnheit verwendet, und das ist vollkommen in Ordnung. Wenn irgendeine Chance besteht, später dynamischen Inhalt hinzuzufügen oder ihn zu umwickeln, istfmt.Errorf
für zukünftige Refactorings sicherer.
- Ja: Sie könnten
Allgemeiner Leitfaden:
Tendieren Sie in den meisten Fällen zu fmt.Errorf
, insbesondere wenn Fehler mehrere Ebenen Ihrer Anwendung durchlaufen. Die Möglichkeit, Kontext hinzuzufügen und zugrunde liegende Fehler zu umwickeln, ist ein mächtiges Hilfsmittel zur Fehlerbehebung, das den geringen Mehraufwand von fmt.Errorf
gegenüber errors.New
bei weitem überwiegt. Reservieren Sie errors.New
hauptsächlich für die Definition von var
Sentinel-Fehlern, die explizit in Ihrer Anwendungslogik geprüft werden.
Best Practices für Fehlermeldungen
- Beginnen Sie mit Kleinbuchstaben: Fehlermeldungen werden normalerweise nicht großgeschrieben und enden nicht mit Satzzeichen, da
fmt.Println
einen Zeilenumbruch und möglicherweise weitere Formatierungen hinzufügt.- Gut:
return fmt.Errorf("failed to open file '%s'", filename)
- Schlecht:
return errors.New("Failed to open file.")
- Gut:
- Seien Sie prägnant, aber informativ: Geben Sie genügend Informationen, um zu verstehen, was schiefgelaufen ist, ohne zu wortreich zu sein.
- Vermeiden Sie Redundanz: Wenn Sie einen Fehler umwickeln, sollte die äußere Fehlermeldung neuen Kontext hinzufügen, nicht nur den inneren Fehler wiederholen.
- Gut:
fmt.Errorf("failed to process request for user %d: %w", userID, err)
- Schlecht:
fmt.Errorf("error: %s", err)
(dies wiederholt lediglich den zugrunde liegenden Fehler, ohne Kontext hinzuzufügen)
- Gut:
- Fehler sind primär für Maschinen, nicht für Menschen: Obwohl sie menschenlesbar sind, ist der primäre Konsument eines Fehlerobjekts oft Code weiter oben im Stack, der Entscheidungen basierend auf dem Fehler trifft. Detaillierte Meldungen sind zur Fehlersuche gedacht. Benutzerspezifische Meldungen sollten separat generiert werden, oft indem interne Fehler auf freundlichere Meldungen abgebildet werden.
Fazit
Sowohl errors.New
als auch fmt.Errorf
sind unverzichtbare Werkzeuge in der Go-Fehlerbehandlung. Während errors.New
sich hervorragend zur Definition einfacher, statischer Sentinel-Fehler für direkten Vergleich eignet, ist fmt.Errorf
mit seinen Formatierungsfähigkeiten und vor allem seiner Unterstützung für Error Wrapping (%w
) das Arbeitspferd für die Erstellung aussagekräftiger, kontextbezogener und debugfähiger Fehlerketten. Durch die entsprechende Nutzung dieser Funktionen und die Einhaltung von Best Practices können Go-Entwickler Anwendungen erstellen, die nicht nur robust in ihrer Fehlerbehandlung sind, sondern auch erheblich einfacher zu debuggen und zu warten sind. Die Kunst der Erstellung aussagekräftiger Fehler liegt darin, gerade genug Informationen bereitzustellen, damit sowohl Maschinen reagieren als auch Menschen die Grundursache eines Problems verstehen können.