Go Foutafhandeling in 2026: Patronen, Wrapping en Technische Interviewvragen

Uitgebreide gids over Go error handling: sentinel errors, error wrapping met fmt.Errorf, errors.Is/As en best practices voor technische interviews.

Go Error Handling Patterns

Go foutafhandeling onderscheidt de taal van de meeste gangbare alternatieven. In plaats van exceptions en try/catch-blokken behandelt Go fouten als waarden—expliciet geretourneerd door functies en gecontroleerd bij elke aanroep. Deze weloverwogen ontwerpkeuze plaatst foutafhandeling op de voorgrond, waardoor foutpaden zichtbaar en testbaar worden.

Foutwaarden, Geen Exceptions

Go kent geen exception-mechanisme. Elke functie die kan falen retourneert een error als laatste returnwaarde. Dit patroon maakt de kosten van het negeren van fouten zichtbaar op bronniveau—en maakt foutstromen triviaal testbaar.

De Error Interface en Waarom Deze Belangrijk Is

Het volledige Go error-systeem rust op een enkele interface gedefinieerd in de standaardbibliotheek:

builtin.gogo
type error interface {
    Error() string
}

Elk type dat Error() string implementeert voldoet aan deze interface. Deze eenvoud stimuleert composabiliteit: fouten kunnen structs, wrappers of iets anders zijn, zolang ze een stringrepresentatie produceren.

Een eenvoudig custom error type demonstreert het patroon:

apperror.gogo
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)
}

Deze NotFoundError bevat gestructureerde data. Aanroepers kunnen de resourcenaam of ID programmatisch extraheren in plaats van een string te parsen—een cruciaal voordeel bij het bouwen van HTTP API's of CLI-tools die fouten mappen naar specifieke responses.

Sentinel Errors voor Bekende Condities

Sentinel errors zijn package-level variabelen die specifieke, bekende foutcondities representeren. De standaardbibliotheek gebruikt dit patroon uitgebreid: io.EOF, sql.ErrNoRows, os.ErrNotExist.

errors.gogo
var (
    ErrNotFound     = errors.New("record not found")
    ErrUnauthorized = errors.New("unauthorized access")
    ErrConflict     = errors.New("resource conflict")
)

Sentinel errors werken het beste wanneer de aanroeper alleen moet weten wat er is mislukt, niet waarom in detail. Ze signaleren een conditie zonder aanvullende context mee te geven. De Go-conventie is om ze te prefixen met Err en het bericht in kleine letters te houden zonder interpunctie, volgens de Go Code Review Comments richtlijn.

Wanneer Sentinels Problematisch Worden

Het exporteren van sentinel errors creëert een publiek API-contract. Downstream packages zullen ze matchen met errors.Is, dus het hernoemen of verwijderen van een sentinel error is een breaking change. Gebruik ze spaarzaam—alleen voor condities waarop aanroepers daadwerkelijk moeten branchen.

Error Wrapping met fmt.Errorf en de %w Verb

Error wrapping, geïntroduceerd in Go 1.13, voegt context toe aan een error terwijl het origineel behouden blijft in een keten. De %w verb in fmt.Errorf creëert deze keten:

repository.gogo
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
}

De gewrapte error behoudt de volledige keten. Een aanroeper drie lagen hoger kan nog steeds de originele sentinel matchen of het originele type extraheren. Elke laag voegt context toe over waar de fout optrad zonder te verhullen wat er gebeurde.

Een cruciaal onderscheid: %w wrapt (behoudt de keten), terwijl %v de error als string formatteert en de keten verbreekt. Gebruik %v bewust wanneer de originele error niet door de abstractiegrens heen mag lekken—bijvoorbeeld wanneer een repository een database driver error wrapt die service-laag code nooit zou moeten inspecteren.

Fouten Inspecteren met errors.Is en errors.As

Het errors package biedt twee functies voor het inspecteren van gewrapte error-ketens, ter vervanging van directe vergelijking en type assertion.

errors.Is doorloopt de keten op zoek naar een specifieke error-waarde:

handler.gogo
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 extraheert een specifiek error type uit de keten:

middleware.gogo
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, &notFound) {
        return http.StatusNotFound
    }
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        return http.StatusBadRequest
    }
    return http.StatusInternalServerError
}

Het belangrijkste voordeel ten opzichte van type assertions: beide functies doorlopen de volledige gewrapte keten. Een NotFoundError die drie keer is gewrapt met fmt.Errorf en %w matcht nog steeds.

Gestructureerde Foutafhandeling in Gelaagde Applicaties

Productie Go-applicaties organiseren fouten typisch over drie lagen: domain errors definiëren business-level condities, service errors voegen operationele context toe, en handler of transport errors mappen ze naar externe responses.

domain/errors.gogo
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
}

De Unwrap methode is wat errors.Is en errors.As door de keten laat werken. Elk custom error type dat een andere error wrapt dient deze te implementeren.

De service-laag wrapt domain errors met operationele context:

service/user.gogo
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)
}

Klaar om je Go gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Veelvoorkomende Interviewvragen over Go Foutafhandeling

Technische interviews voor Go-posities testen regelmatig kennis van foutafhandeling. Deze vragen verschijnen frequent in screeningsrondes en worden uitgebreid behandeld in Go error handling interviewvragen.

Waarom gebruikt Go error-waarden in plaats van exceptions?

Exceptions creëren onzichtbare control flow. Een functie die throwd draagt controle over naar een onbekende catch-locatie, mogelijk vele stack frames verderop. Go error-waarden maken elk foutpad expliciet in de functiesignatuur. De aanroeper beslist direct hoe de error af te handelen—retry, wrap, log of propageer. Dit elimineert het "verrassings-throw" probleem en maakt foutstromen leesbaar zonder tooling-ondersteuning.

Wat is het verschil tussen %w en %v in fmt.Errorf?

%w wrapt de error, waardoor de keten behouden blijft voor errors.Is en errors.As. %v formatteert de error als string, wat een nieuwe error produceert zonder ketenlink naar het origineel. Gebruik %w wanneer aanroepers de oorzaak moeten kunnen inspecteren; gebruik %v wanneer de oorzaak een implementatiedetail is dat niet door een abstractiegrens mag lekken.

Wanneer moet een functie een error retourneren vs. panic?

Panic is gereserveerd voor werkelijk onherstelbare situaties: programmeerfouten zoals index out of bounds, nil pointer dereferences, of geschonden invarianten die op een bug wijzen. Herstelbare fouten—netwerk timeouts, ontbrekende records, ongeldige input—retourneren errors. Een nuttige regel: als de conditie kan optreden tijdens normale operatie, retourneert het een error. Als het betekent dat het programma een bug heeft, panict het.

Hoe beïnvloedt de Unwrap methode error-keteninspectie?

Wanneer een custom error type Unwrap() error implementeert, volgen de errors.Is en errors.As functies die methode om de keten te doorlopen. Zonder Unwrap stopt de keten bij die error. Sinds Go 1.20 kunnen errors ook Unwrap() []error implementeren om meerdere gewrapte errors te retourneren, wat boomvormige error-ketens mogelijk maakt voor cases zoals het aggregeren van validatiefouten.

Foutafhandelingspatronen om te Vermijden

Verschillende anti-patronen verschijnen frequent in Go-codebases en tijdens code review sessies:

Fouten stilzwijgend negeren. De blank identifier maakt dit syntactisch eenvoudig maar semantisch gevaarlijk:

go
// anti-pattern: silent error swallowing
result, _ := riskyOperation()

Elke genegeerde error moet een weloverwogen, gedocumenteerde keuze zijn—nooit een shortcut.

Loggen en retourneren. Dit patroon produceert dubbele log entries en verwart de aanroeper die de error ook afhandelt:

go
// anti-pattern: double handling
if err != nil {
    log.Printf("operation failed: %v", err)
    return err
}

De oplossing: of de error afhandelen (loggen, default retourneren, retry) of propageren. Niet beide.

String matching voor error classificatie. Het controleren van err.Error() output met strings.Contains is fragiel. Error messages zijn geen API-contracten—ze kunnen veranderen tussen library versies. Gebruik errors.Is voor sentinel waarden en errors.As voor types.

Errors wrappen die goroutine-grenzen overschrijden zonder synchronisatie. Wanneer meerdere goroutines concurrent errors produceren, gebruik errgroup.Group uit het golang.org/x/sync package of een vergelijkbaar synchronisatiemechanisme.

Foutafhandeling met errgroup voor Concurrente Operaties

Het errgroup package van golang.org/x/sync biedt een clean patroon voor het verzamelen van errors uit concurrente goroutines:

batch.gogo
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 annuleert de afgeleide context wanneer een goroutine een error retourneert, wat de anderen signaleert om te stoppen. Dit voorkomt verspild werk en levert een enkele, gewrapte error aan de aanroeper.

Go 1.24 en Foutafhandeling

Go 1.24 (februari 2025) heeft het errors package niet gewijzigd. De error handling API is stabiel sinds Go 1.13, met de Go 1.20 toevoeging van multi-error unwrapping via Unwrap() []error. Huidige voorstellen voor verbeterde error handling syntax blijven in discussie in de Go issue tracker.

Conclusie

  • Behandel errors als waarden: retourneer ze expliciet en handel ze af bij elke aanroep in plaats van te vertrouwen op verborgen exception-mechanismen
  • Gebruik sentinel errors (var ErrX = errors.New(...)) alleen voor condities waarop aanroepers moeten branchen, en behandel ze als publieke API
  • Wrap errors met fmt.Errorf en %w om context toe te voegen terwijl de keten behouden blijft; gebruik %v om de keten bewust te verbreken bij abstractiegrenzen
  • Geef de voorkeur aan errors.Is boven == en errors.As boven type assertions—beide doorlopen de volledige gewrapte keten
  • Implementeer Unwrap() error op custom error types zodat keteninspectie correct werkt
  • Handel af of propageer—nooit beide. Een error loggen en retourneren produceert dubbele ruis
  • Gebruik errgroup voor concurrent error verzamelen in plaats van handmatige goroutine synchronisatie
  • Houd error messages in kleine letters zonder interpunctie, volgens Go-conventies

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

#go
#error-handling
#best-practices
#interview

Delen

Gerelateerde artikelen