EMZETT.
Login

Kurz: import “fmt”

Teil des Kurses Go

Kapitel 4 von 11 im Kurs Go. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

package main
 
import "fmt"
 
func addiere(a, b int) int { return a + b }
 
// Mehrere Rückgabewerte
func teile(a, b int) (int, int) { return a / b, a % b }
 
// Benannte Rückgabewerte
func minMax(zahlen []int) (min, max int) {
    min, max = zahlen[0], zahlen[0]
    for _, z := range zahlen[1:] {
        if z < min {
            min = z
        }
        if z > max {
            max = z
        }
    }
    return // gibt min und max zurück
}
 
// Variable Anzahl Argumente
func summe(zahlen ...int) int {
    s := 0
    for _, z := range zahlen {
        s += z
    }
    return s
}
 
func main() {
    fmt.Println(addiere(3, 4))
    q, r := teile(17, 5)
    fmt.Println(q, r)
    lo, hi := minMax([]int{5, 3, 9, 1})
    fmt.Println(lo, hi)
    fmt.Println(summe(), summe(1, 2, 3), summe([]int{4, 5}...))
}

Ausgabe:

7
3 2
1 9
0 6 9

Funktionen sind Werte

package main
 
import "fmt"
 
func wende(f func(int) int, x int) int { return f(x) }
 
func zaehlerErstellen() func() int { // Closure
    n := 0
    return func() int {
        n++
        return n
    }
}
 
func main() {
    quadrat := func(x int) int { return x * x }
    fmt.Println(wende(quadrat, 7), wende(func(x int) int { return x + 1 }, 7))
    z := zaehlerErstellen()
    fmt.Println(z(), z(), z())
    func() { fmt.Println("sofort aufgerufen") }()
}

Ausgabe:

49 8
1 2 3
sofort aufgerufen

Fehler sind Werte

Go hat keine Ausnahmen. Funktionen, die scheitern können, geben als letzten Rückgabewert einen error zurück. Der Aufrufer prüft ihn sofort:

package main
 
import (
    "errors"
    "fmt"
    "strconv"
)
 
var ErrNegativ = errors.New("negative Zahl") // Sentinel-Fehler
 
func wurzelInt(s string) (int, error) {
    n, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("wurzelInt: ungültige Eingabe %q: %w", s, err) // %w: Ursache einpacken
    }
    if n < 0 {
        return 0, ErrNegativ
    }
    r := 0
    for (r+1)*(r+1) <= n {
        r++
    }
    return r, nil
}
 
func main() {
    for _, eingabe := range []string{"49", "-4", "abc"} {
        r, err := wurzelInt(eingabe)
        if err != nil {
            fmt.Println("Fehler:", err)
            if errors.Is(err, ErrNegativ) {
                fmt.Println("  -> war negativ")
            }
            var numErr *strconv.NumError
            if errors.As(err, &numErr) {
                fmt.Println("  -> Zahlenfehler bei", numErr.Num)
            }
            continue
        }
        fmt.Println("Wurzel:", r)
    }
}

Ausgabe:

Wurzel: 7
Fehler: negative Zahl
  -> war negativ
Fehler: wurzelInt: ungültige Eingabe "abc": strconv.Atoi: parsing "abc": invalid syntax
  -> Zahlenfehler bei abc

Das Muster if err != nil { return ..., err } ist allgegenwärtig in Go-Code. Es ist ausführlich, aber der Fehlerfluss ist immer sichtbar.

WerkzeugZweck
errors.New("text")einfachen Fehler erzeugen
fmt.Errorf("... %w", err)Fehler mit Kontext einpacken
errors.Is(err, Ziel)prüft, ob Ziel in der Kette steckt
errors.As(err, &typ)holt einen Fehler eines bestimmten Typs aus der Kette
eigener Typ mit Error() stringFehler mit zusätzlichen Daten
package main
 
import (
    "errors"
    "fmt"
)
 
type GuthabenFehler struct {
    Fehlbetrag float64
}
 
func (e *GuthabenFehler) Error() string {
    return fmt.Sprintf("zu wenig Guthaben, es fehlen %.2f", e.Fehlbetrag)
}
 
func abheben(stand, betrag float64) (float64, error) {
    if betrag > stand {
        return stand, &GuthabenFehler{betrag - stand}
    }
    return stand - betrag, nil
}
 
func main() {
    if _, err := abheben(100, 130); err != nil {
        var gf *GuthabenFehler
        if errors.As(err, &gf) {
            fmt.Println(err, "|", gf.Fehlbetrag)
        }
    }
}

Ausgabe:

zu wenig Guthaben, es fehlen 30.00 | 30

defer, panic und recover

panic ist für Programmierfehler gedacht (nicht für normale Fehler): Es beendet das Programm, führt vorher aber alle defer-Aufrufe aus. recover fängt eine Panic innerhalb eines defer:

package main
 
import "fmt"
 
func sicher(f func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("abgefangen: %v", r)
        }
    }()
    f()
    return nil
}
 
func main() {
    fmt.Println(sicher(func() { fmt.Println("alles gut") }))
    fmt.Println(sicher(func() {
        var feld []int
        _ = feld[5] // index out of range -> panic
    }))
    fmt.Println(sicher(func() { panic("selbst ausgelöst") }))
}

Ausgabe:

alles gut
<nil>
abgefangen: runtime error: index out of range [5] with length 0
abgefangen: selbst ausgelöst

Nutze panic nur bei nicht behebbaren Zuständen (zum Beispiel beim Start, wenn eine Pflicht-Konfiguration fehlt). Bibliotheken sollen Fehler zurückgeben.

Merke

  • Mehrere Rückgabewerte; der letzte ist bei fehlbaren Funktionen ein error
  • Fehler sind normale Werte: if err != nil { return ..., err }
  • fmt.Errorf("... %w", err) packt Kontext ein; errors.Is/errors.As werten aus
  • Funktionen sind Werte und können Closures sein
  • panic und recover nur für Ausnahmesituationen

Übungsaufgabe

Schreibe teile(a, b float64) (float64, error), die bei b == 0 einen eigenen Fehler liefert, und teste es mit mehreren Eingaben.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Bedingungen, Schleifen und switch

Weiter: Arrays, Slices und Maps

Alle Kapitel: Go im Überblick