← Go EnglishChapter 10 of 13

Error Handling

## Learning Objectives - Understand Go's error handling philosophy - Work with the error interface - Create custom errors - Handle errors effectively - Use panic and recover appropriately ## Go's Error Philosophy Go uses explicit error returns rather than exceptions. ```go result, err := riskyOperation() if err != nil { // Handle error } ``` ## The error Interface ```go type error interface { Error() string } ``` ### Basic Error ```go import "errors" func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } ``` ## Creating Errors ### errors.New ```go import "errors" err := errors.New("something went wrong") fmt.Println(err) // something went wrong ``` ### fmt.Errorf ```go import "fmt" err := fmt.Errorf("invalid input: %s", "missing value") ``` ### Custom Error Types ```go type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } func validate(name string) error { if name == "" { return &ValidationError{Field: "name", Message: "cannot be empty"} } return nil } ``` ### Error Wrapping (Go 1.13+) ```go import "fmt" func readConfig(path string) error { err := os.Open(path) if err != nil { return fmt.Errorf("failed to open config: %w", err) } return nil } ``` ### Unwrap ```go import "errors" err := readConfig("missing.txt") if err != nil { // Check original error if os.IsNotExist(err) { fmt.Println("File does not exist") } // Unwrap chain for err != nil { fmt.Println(err) err = errors.Unwrap(err) } } ``` ## Handling Errors ### Basic Pattern ```go result, err := divide(10, 0) if err != nil { fmt.Println("Error:", err) return } fmt.Println("Result:", result) ``` ### Early Return ```go func process(data string) error { file, err := os.Open(data) if err != nil { return err // Early return on error } defer file.Close() // Continue processing... return nil } ``` ### Multiple Errors ```go func processAll(files []string) error { for _, file := range files { if err := process(file); err != nil { return fmt.Errorf("processing %s: %w", file, err) } } return nil } ``` ## Error Checking Patterns ### Sentinel Errors ```go import "errors" import "os" var ErrNotFound = errors.New("resource not found") func find(id string) (string, error) { if id == "missing" { return "", ErrNotFound } return "found", nil } if err := find("missing"); err != nil { if errors.Is(err, ErrNotFound) { fmt.Println("Resource was not found") } } ``` ### errors.Is ```go import "errors" err := find("missing") if errors.Is(err, ErrNotFound) { // Handle not found } ``` ### errors.As ```go type CustomError struct { Code int Msg string } func (e *CustomError) Error() string { return e.Msg } var cerr *CustomError if errors.As(err, &cerr) { fmt.Printf("Custom error code: %d\n", cerr.Code) } ``` ## Common Error Functions ### os Errors ```go import "os" file, err := os.Open("missing.txt") if err != nil { os.IsNotExist(err) // true if file doesn't exist os.IsPermission(err) // true if permission denied os.IsExist(err) // true if file already exists } ``` ### net Errors ```go import "net" _, err := net.Dial("tcp", "localhost:9999") if err != nil { var opErr *net.OpError if errors.As(err, &opErr) { fmt.Println("Operation:", opErr.Op) } } ``` ## Custom Error Handling ### Error Handler ```go type Handler func(error) func handle(h Handler, err error) { if err != nil { h(err) } } ``` ### Retry Pattern ```go func withRetry(maxRetries int, fn func() error) error { var err error for i := 0; i < maxRetries; i++ { err = fn() if err == nil { return nil } time.Sleep(time.Duration(i+1) * time.Second) } return fmt.Errorf("after %d retries: %w", maxRetries, err) } ``` ## panic and recover ### When to Use panic - Reserved for truly unrecoverable situations - Programming errors (nil pointer, out of bounds) - Not for expected error conditions ### panic ```go func main() { panic("something bad happened!") fmt.Println("This won't run") } ``` ### recover ```go func safeCall() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered from:", r) } }() panic("Something went wrong!") fmt.Println("This won't run") } func main() { safeCall() fmt.Println("Program continues...") } ``` ### Recover in Defer ```go func mayPanic() { panic("panic!") } func main() { defer func() { if r := recover(); r != nil { fmt.Println("Caught:", r) } }() mayPanic() fmt.Println("After mayPanic") } ``` ### Best Practice: Defer Recovery at Boundary ```go func server() { defer func() { if r := recover(); r != nil { log.Printf("Server panic: %v", r) os.Exit(1) } }() // Server logic... } ``` ## Log Errors ### log Package ```go import "log" if err != nil { log.Printf("operation failed: %v", err) } if err != nil { log.Fatalf("critical error: %v", err) } log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) ``` ### Custom Logger ```go logger := log.New(os.Stderr, "APP: ", log.Ldate|log.Ltime) logger.Printf("Starting server on port %d", 8080) logger.Fatalf("Failed to start: %v", err) ``` ## Error Visibility ### Avoid Hidden Errors ```go // BAD: ignores error bytes, _ := json.Marshal(data) // GOOD: handle error bytes, err := json.Marshal(data) if err != nil { return fmt.Errorf("marshaling data: %w", err) } ``` ### No Error Suppression ```go // BAD: silently ignores errors if x > 0 { fmt.Println("positive") } else if x < 0 { fmt.Println("negative") } // else case ignored! // GOOD if x > 0 { fmt.Println("positive") } else if x < 0 { fmt.Println("negative") } else { fmt.Println("zero") } ``` ## Summary - Go uses explicit error handling via return values - The `error` interface has one method: `Error() string` - Create errors with `errors.New()` or `fmt.Errorf()` - Use `%w` verb to wrap errors - `errors.Is()` and `errors.As()` for error inspection - panic/recover reserved for truly exceptional situations - Log or handle every error explicitly - Never suppress errors silently

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →