# Go Fehlerbehandlung 2026: Patterns, Wrapping und technische Interviewfragen > Fehlerbehandlung in Go unterscheidet sich grundlegend von anderen Sprachen. Dieser Artikel behandelt Error Wrapping, Sentinel Errors und typische Interviewfragen. - Published: 2026-06-15 - Updated: 2026-06-15 - Author: SharpSkill - Tags: go, error-handling, best-practices, interview - Reading time: 8 min --- --- Go unterscheidet sich bei der Fehlerbehandlung grundlegend von den meisten anderen Programmiersprachen. Anstelle von Exceptions und try/catch-Blöcken behandelt Go Fehler als Werte, die explizit von Funktionen zurückgegeben und an jeder Aufrufstelle geprüft werden. Diese bewusste Designentscheidung rückt die Fehlerbehandlung in den Vordergrund und macht Fehlerpfade sichtbar sowie testbar. > **Fehlerwerte statt Exceptions** > > Go besitzt keinen Exception-Mechanismus. Jede Funktion, die fehlschlagen kann, gibt einen Fehler als letzten Rückgabewert zurück. Dieses Muster macht die Kosten des Ignorierens von Fehlern auf Quelltextebene sichtbar und ermöglicht triviales Testen von Fehlerabläufen. ## Das Error Interface und seine Bedeutung Das gesamte Go-Fehlersystem basiert auf einem einzigen Interface, das in der [Standardbibliothek](https://pkg.go.dev/builtin#error) definiert ist: ```go // builtin.go type error interface { Error() string } ``` Jeder Typ, der `Error() string` implementiert, erfüllt dieses Interface. Diese Einfachheit fördert die Komponierbarkeit: Fehler können Structs, Wrapper oder beliebige andere Typen sein, solange sie eine String-Repräsentation liefern. Ein einfacher benutzerdefinierter Fehlertyp demonstriert das Muster: ```go // apperror.go type NotFoundError struct { Resource string ID string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID) } ``` Dieser `NotFoundError` trägt strukturierte Daten. Aufrufer können den Ressourcennamen oder die ID programmatisch extrahieren, anstatt einen String zu parsen. Dies ist ein entscheidender Vorteil beim Erstellen von HTTP-APIs oder CLI-Tools, die Fehler auf spezifische Antworten abbilden. ## Sentinel Errors für bekannte Fehlerzustände Sentinel Errors sind Variablen auf Paketebene, die spezifische, wohlbekannte Fehlerzustände repräsentieren. Die Standardbibliothek nutzt dieses Muster intensiv: `io.EOF`, `sql.ErrNoRows`, `os.ErrNotExist`. ```go // errors.go var ( ErrNotFound = errors.New("record not found") ErrUnauthorized = errors.New("unauthorized access") ErrConflict = errors.New("resource conflict") ) ``` Sentinel Errors eignen sich am besten, wenn der Aufrufer nur wissen muss, *was* fehlgeschlagen ist, nicht *warum* im Detail. Sie signalisieren einen Zustand, ohne zusätzlichen Kontext zu tragen. Die Go-Konvention sieht vor, sie mit `Err` zu präfixieren und die Nachricht kleingeschrieben ohne Satzzeichen zu halten, gemäß den [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments#error-strings). > **Wenn Sentinels zum Problem werden** > > Das Exportieren von Sentinel Errors erzeugt einen öffentlichen API-Vertrag. Nachgelagerte Pakete werden sie mit `errors.Is` abgleichen, sodass das Umbenennen oder Entfernen eines Sentinel Errors eine Breaking Change darstellt. Sentinel Errors sollten sparsam eingesetzt werden, nur für Zustände, bei denen Aufrufer tatsächlich verzweigen müssen. ## Error Wrapping mit fmt.Errorf und dem %w-Verb Error Wrapping, eingeführt in Go 1.13, fügt einem Fehler Kontext hinzu, während der ursprüngliche Fehler in einer Kette erhalten bleibt. Das `%w`-Verb in `fmt.Errorf` erzeugt diese Kette: ```go // repository.go func (r *UserRepo) FindByID(ctx context.Context, id string) (*User, error) { user, err := r.db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", id) if err != nil { return nil, fmt.Errorf("UserRepo.FindByID(%s): %w", id, err) } return user, nil } ``` Der gewrappte Fehler behält die vollständige Kette. Ein Aufrufer drei Schichten höher kann immer noch den ursprünglichen Sentinel abgleichen oder den ursprünglichen Typ extrahieren. Jede Schicht fügt Kontext darüber hinzu, *wo* der Fehler aufgetreten ist, ohne zu verschleiern, *was* passiert ist. Eine kritische Unterscheidung: `%w` wrappt den Fehler und erhält die Kette, während `%v` den Fehler als String formatiert und die Kette unterbricht. `%v` sollte bewusst verwendet werden, wenn der ursprüngliche Fehler nicht durch die Abstraktionsgrenze durchdringen soll, beispielsweise wenn ein Repository einen Datenbanktreiber-Fehler wrappt, den der Service-Layer niemals inspizieren sollte. ## Fehler untersuchen mit errors.Is und errors.As Das `errors`-Paket stellt zwei Funktionen zur Untersuchung gewrappter Fehlerketten bereit, die direkten Vergleich und Type Assertion ersetzen. `errors.Is` durchläuft die Kette auf der Suche nach einem spezifischen Fehlerwert: ```go // handler.go func handleGetUser(w http.ResponseWriter, r *http.Request) { user, err := userService.GetByID(r.Context(), chi.URLParam(r, "id")) if errors.Is(err, ErrNotFound) { http.Error(w, "User not found", http.StatusNotFound) return } if err != nil { http.Error(w, "Internal error", http.StatusInternalServerError) return } json.NewEncoder(w).Encode(user) } ``` `errors.As` extrahiert einen spezifischen Fehlertyp aus der Kette: ```go // middleware.go func errorMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { http.Error(w, "Internal error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } func mapErrorToHTTP(err error) int { var notFound *NotFoundError if errors.As(err, ¬Found) { return http.StatusNotFound } var validationErr *ValidationError if errors.As(err, &validationErr) { return http.StatusBadRequest } return http.StatusInternalServerError } ``` Der entscheidende Vorteil gegenüber Type Assertions: Beide Funktionen durchlaufen die gesamte gewrappte Kette. Ein `NotFoundError`, der dreimal mit `fmt.Errorf` und `%w` gewrappt wurde, wird trotzdem erkannt. ## Strukturierte Fehlerbehandlung in geschichteten Anwendungen Produktionsreife Go-Anwendungen organisieren Fehler typischerweise über drei Schichten: Domain-Fehler definieren Zustände auf Geschäftslogikebene, Service-Fehler fügen operativen Kontext hinzu, und Handler- oder Transport-Fehler bilden sie auf externe Antworten ab. ```go // domain/errors.go type DomainError struct { Code string Message string Err error } func (e *DomainError) Error() string { if e.Err != nil { return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.Err) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } func (e *DomainError) Unwrap() error { return e.Err } ``` Die `Unwrap`-Methode ermöglicht es `errors.Is` und `errors.As`, durch die Kette zu navigieren. Jeder benutzerdefinierte Fehlertyp, der einen anderen Fehler wrappt, sollte diese Methode implementieren. Der Service-Layer wrappt Domain-Fehler mit operativem Kontext: ```go // service/user.go func (s *UserService) Deactivate(ctx context.Context, userID string) error { user, err := s.repo.FindByID(ctx, userID) if err != nil { return fmt.Errorf("deactivating user %s: %w", userID, err) } if user.Status == StatusInactive { return &DomainError{ Code: "ALREADY_INACTIVE", Message: fmt.Sprintf("user %s is already inactive", userID), } } return s.repo.UpdateStatus(ctx, userID, StatusInactive) } ``` ## Häufige Interviewfragen zur Go-Fehlerbehandlung Technische Interviews für Go-Positionen prüfen regelmäßig das Wissen über Fehlerbehandlung. Diese Fragen tauchen häufig in Screening-Runden auf und werden ausführlich in den [Go-Interviewfragen zur Fehlerbehandlung](/technologies/go/interview-questions/error-handling) behandelt. **Warum verwendet Go Fehlerwerte anstelle von Exceptions?** Exceptions erzeugen unsichtbaren Kontrollfluss. Eine Funktion, die wirft, überträgt die Kontrolle an eine unbekannte catch-Stelle, möglicherweise viele Stack-Frames entfernt. Go-Fehlerwerte machen jeden Fehlerpfad explizit in der Funktionssignatur sichtbar. Der Aufrufer entscheidet sofort, wie der Fehler behandelt wird, ob durch Retry, Wrapping, Logging oder Weitergabe. Dies eliminiert das Problem der überraschenden Throws und macht Fehlerabläufe ohne Tool-Unterstützung lesbar. **Was ist der Unterschied zwischen `%w` und `%v` in `fmt.Errorf`?** `%w` wrappt den Fehler und erhält die Kette für `errors.Is` und `errors.As`. `%v` formatiert den Fehler als String und erzeugt einen neuen Fehler ohne Kettenverbindung zum Original. `%w` sollte verwendet werden, wenn Aufrufer die Ursache inspizieren können sollen; `%v` ist angebracht, wenn die Ursache ein Implementierungsdetail ist, das nicht durch eine Abstraktionsgrenze durchdringen soll. **Wann sollte eine Funktion einen Fehler zurückgeben und wann panic aufrufen?** Panic ist für wirklich nicht behebbare Situationen reserviert: Programmierfehler wie Index außerhalb des Bereichs, Nil-Pointer-Dereferenzierungen oder verletzte Invarianten, die auf einen Bug hinweisen. Behebbare Fehler wie Netzwerk-Timeouts, fehlende Datensätze oder ungültige Eingaben geben Fehler zurück. Eine nützliche Regel: Wenn der Zustand während des normalen Betriebs auftreten kann, wird ein Fehler zurückgegeben. Wenn es bedeutet, dass das Programm einen Bug hat, wird panic ausgelöst. **Wie beeinflusst die `Unwrap`-Methode die Fehlerketteninspektion?** Wenn ein benutzerdefinierter Fehlertyp `Unwrap() error` implementiert, folgen die Funktionen `errors.Is` und `errors.As` dieser Methode, um die Kette zu durchlaufen. Ohne `Unwrap` endet die Kette bei diesem Fehler. Seit Go 1.20 können Fehler auch `Unwrap() []error` implementieren, um mehrere gewrappte Fehler zurückzugeben, was baumförmige Fehlerketten für Fälle wie das Aggregieren von Validierungsfehlern ermöglicht. ## Anti-Patterns in der Fehlerbehandlung Mehrere Anti-Patterns erscheinen häufig in Go-Codebasen und in Code-Review-Sitzungen: **Fehler stillschweigend ignorieren.** Der Blank Identifier macht dies syntaktisch einfach, aber semantisch gefährlich: ```go // anti-pattern: silent error swallowing result, _ := riskyOperation() ``` Jeder ignorierte Fehler sollte eine bewusste, dokumentierte Entscheidung sein, niemals eine Abkürzung. **Loggen und Zurückgeben.** Dieses Muster erzeugt doppelte Log-Einträge und verwirrt den Aufrufer, der den Fehler ebenfalls behandelt: ```go // anti-pattern: double handling if err != nil { log.Printf("operation failed: %v", err) return err } ``` Die Lösung: Entweder den Fehler behandeln (loggen, Standardwert zurückgeben, wiederholen) *oder* weitergeben. Nicht beides. **String-Matching zur Fehlerklassifizierung.** Das Prüfen der `err.Error()`-Ausgabe mit `strings.Contains` ist fragil. Fehlermeldungen sind keine API-Verträge und können sich zwischen Bibliotheksversionen ändern. `errors.Is` für Sentinel-Werte und `errors.As` für Typen verwenden. **Fehler wrappen, die Goroutine-Grenzen ohne Synchronisierung überschreiten.** Wenn mehrere Goroutines gleichzeitig Fehler erzeugen, sollte `errgroup.Group` aus dem Paket `golang.org/x/sync` oder ein ähnlicher Synchronisierungsmechanismus verwendet werden. ## Fehlerbehandlung mit errgroup für nebenläufige Operationen Das `errgroup`-Paket aus golang.org/x/sync bietet ein sauberes Muster zum Sammeln von Fehlern aus nebenläufigen Goroutines: ```go // batch.go func (s *OrderService) ProcessBatch(ctx context.Context, orderIDs []string) error { g, ctx := errgroup.WithContext(ctx) g.SetLimit(10) for _, id := range orderIDs { g.Go(func() error { if err := s.processOrder(ctx, id); err != nil { return fmt.Errorf("processing order %s: %w", id, err) } return nil }) } return g.Wait() } ``` `errgroup.WithContext` bricht den abgeleiteten Context ab, wenn eine Goroutine einen Fehler zurückgibt, und signalisiert den anderen, anzuhalten. Dies vermeidet verschwendete Arbeit und liefert einen einzelnen, gewrappten Fehler an den Aufrufer. > **Go 1.24 und Fehlerbehandlung** > > Go 1.24 (Februar 2025) hat das errors-Paket nicht verändert. Die Fehlerbehandlungs-API ist seit Go 1.13 stabil, mit der Go 1.20-Erweiterung des Multi-Error-Unwrapping über `Unwrap() []error`. Aktuelle Vorschläge für eine verbesserte Fehlerbehandlungssyntax werden weiterhin im Go Issue Tracker diskutiert. ## Fazit - Fehler als Werte behandeln: Sie explizit zurückgeben und an jeder Aufrufstelle verarbeiten, anstatt sich auf versteckte Exception-Mechanismen zu verlassen - Sentinel Errors (`var ErrX = errors.New(...)`) nur für Zustände verwenden, bei denen Aufrufer verzweigen müssen, und sie als öffentliche API behandeln - Fehler mit `fmt.Errorf` und `%w` wrappen, um Kontext hinzuzufügen und die Kette zu erhalten; `%v` verwenden, um die Kette an Abstraktionsgrenzen bewusst zu unterbrechen - `errors.Is` dem `==`-Operator und `errors.As` der Type Assertion vorziehen, da beide die gesamte gewrappte Kette durchlaufen - `Unwrap() error` bei benutzerdefinierten Fehlertypen implementieren, damit die Ketteninspektion korrekt funktioniert - Behandeln oder Weitergeben, niemals beides. Einen Fehler zu loggen und zurückzugeben erzeugt doppeltes Rauschen - `errgroup` für nebenläufige Fehlersammlung verwenden anstelle manueller Goroutine-Synchronisierung - Fehlermeldungen kleingeschrieben ohne Satzzeichen halten, gemäß Go-Konventionen --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/de/blog/go/go-error-handling-patterns-wrapping-best-practices