Das Keyword else führt einen Block aus, wenn die Bedingung des vorangehenden if falsch ist. Mit else if prüfst du eine weitere Bedingung, und du kannst beliebig viele davon verketten. Das else muss in derselben Zeile stehen wie die schließende Klammer des if-Blocks, sonst kompiliert der Code nicht. Idiomatisches Go verwendet else seltener als die meisten Sprachen. Endet der if-Block mit return, ist der Code nach dem if bereits der else-Fall. Go-Code kehrt deshalb früh zurück und lässt den Happy Path ohne Einrückung (Go spec).
Kurzfassung
if cond { ... } else { ... }braucht geschweifte Klammern um beide Blöcke und keine runden Klammern um die Bedingung.} else {muss in einer Zeile stehen. Ein Zeilenumbruch nach}beendet dieif-Anweisung, und der Compiler meldetsyntax error: unexpected keyword else, expected }.else if-Ketten werden von oben nach unten geprüft, und die erste wahre Bedingung gewinnt. Lange Ketten lesen sich alsswitchohne Ausdruck besser.- Eine Variable, die in der Kurzanweisung des
ifdeklariert wird (if v, err := f(); err != nil), ist in jedemelse if- undelse-Zweig sichtbar und nach der letzten}verschwunden. - Wenn der
if-Block zurückkehrt, lass daselseweg. Der Linterrevivemeldet die übrig gebliebene Form mitif block ends with a return statement, so drop this else and outdent its block. - Go hat keinen Operator
?:. Verwendeif/elseodercmp.Or, wenn du nur einen Standardwert für einen Nullwert brauchst.
Wie schreibt man if/else in Go?
Ein if nimmt eine boolesche Bedingung und einen Block. Ein else-Block danach läuft, wenn die Bedingung falsch ist. Hier ist die Entscheidung, die eine Retry-Schleife nach einem fehlgeschlagenen Request trifft:
example.gogopackage main import "fmt" func main() { attempt := 3 maxAttempts := 5 if attempt < maxAttempts { fmt.Printf("attempt %d failed, retrying\n", attempt) } else { fmt.Printf("attempt %d failed, giving up\n", attempt) } }
example.texttextattempt 3 failed, retrying
Drei Regeln unterscheiden sich von C, Java und JavaScript:
- Keine runden Klammern um die Bedingung.
if (attempt < maxAttempts) {kompiliert, abergofmtentfernt die Klammern. - Geschweifte Klammern sind Pflicht, auch bei einem einzeiligen Body.
if code >= 500 fmt.Println("server error")scheitert mitsyntax error: unexpected name fmt, expected {. - Die Bedingung muss ein
boolsein. Go kennt keine Truthy-Werte, also sindif len(items)undif userTypfehler. Schreibif len(items) > 0undif user != nil.
Dieselbe Klammerregel gilt für else. Eine nackte Anweisung nach else scheitert mit syntax error: else must be followed by if or statement block. Auf else darf nur ein weiteres if oder ein { ... }-Block folgen. Das Go-Keyword if behandelt Bedingungen und Init-Anweisungen aus Sicht des if.
Warum muss else in derselben Zeile wie } stehen?
Die Grammatik von Go beendet Anweisungen mit Semikolons, aber du tippst sie fast nie. Der Lexer fügt am Zeilenende eines ein, wenn das letzte Token ein Bezeichner ist, ein Literal, eines der Keywords break, continue, fallthrough oder return, ein Operator wie ++ oder eine schließende ), ] oder } (Go spec, Semicolons).
Wegen dieser Regel scheitert das hier:
example.gogoif retries > 0 { fmt.Println("retrying") } else { fmt.Println("giving up") }
example.texttext./main.go:10:2: syntax error: unexpected keyword else, expected }
Die Zeile, die mit } endet, bekommt ein Semikolon, und das beendet die if-Anweisung. Die nächste Zeile beginnt dann eine neue Anweisung mit else, und keine Anweisung darf mit else beginnen. Die Lösung ist, } else { in eine Zeile zu schreiben. Genau das erzeugt auch gofmt. Aus demselben Grund steht in Go die öffnende Klammer in derselben Zeile wie das if selbst.
Wer aus C oder Java kommt und den Allman-Stil bevorzugt (Klammer in einer eigenen Zeile), stößt in der ersten Woche darauf. Dafür formatiert jede Go-Codebasis if/else gleich, und über den Stil wird im Code Review nie diskutiert.
Wie funktioniert else if in Go?
else if ist kein eigenes Keyword. Es ist ein else, gefolgt von einer weiteren if-Anweisung, und die Grammatik erlaubt das direkt: "else" ( IfStmt | Block ). Verkette so viele, wie du brauchst. Go prüft sie von oben nach unten und führt den ersten Zweig aus, dessen Bedingung wahr ist.
Diese Schleife sortiert Request-Latenzen in Metrik-Buckets:
example.gogopackage main import ( "fmt" "time" ) func main() { latencies := []time.Duration{ 40 * time.Millisecond, 250 * time.Millisecond, 3 * time.Second, 90 * time.Millisecond, } var fast, slow, timedOut int for _, d := range latencies { if d < 100*time.Millisecond { fast++ } else if d < time.Second { slow++ } else { timedOut++ } } fmt.Printf("fast=%d slow=%d timed_out=%d\n", fast, slow, timedOut) }
example.texttextfast=2 slow=1 timed_out=1
Die Reihenfolge ist wichtig. Vertauschst du die ersten beiden Bedingungen, sodass d < time.Second zuerst kommt, wird die Ausgabe zu fast=0 slow=3 timed_out=1. Jeder Request unter 100ms liegt auch unter einer Sekunde, also fängt die breitere Prüfung ihn zuerst ab, und der Zweig fast läuft nie. Setz den engsten Bereich an den Anfang.
Sobald eine Kette mehr als drei Zweige hat, liest sich ein switch ohne Ausdruck meist besser. Jeder case ist eine Bedingung, die Cases werden in derselben Reihenfolge von oben nach unten geprüft, und default ersetzt das letzte else:
example.gogofor _, d := range latencies { switch { case d < 100*time.Millisecond: fast++ case d < time.Second: slow++ default: timedOut++ } }
Die Ausgabe ist dieselbe. Wie sich der switch von Go von dem in C unterscheidet, steht im Abschnitt zu switch im Überblick über die Go-Keywords. switch und select verwenden default für den Fall, dass nichts anderes passt. else kommt also nur nach einem if vor.
Welchen Gültigkeitsbereich hat eine Variable, die in einer if-Anweisung deklariert wird?
Ein if kann mit einer Kurzanweisung beginnen, meist einer :=-Deklaration. Die dort deklarierten Variablen gelten für die gesamte if-Anweisung, also auch für jeden else if- und else-Zweig. Die Spezifikation legt jedes if in einen eigenen impliziten Block, und die else-Zweige liegen darin (Go spec, Blocks).
An dieser Stelle lässt sich else schwer ersetzen. Wer einen PORT-Wert aus der Umgebung prüft, braucht die geparste Zahl in jedem Zweig:
example.gogopackage main import ( "fmt" "strconv" ) func main() { for _, raw := range []string{"8080", "80", "http"} { if port, err := strconv.Atoi(raw); err != nil { fmt.Printf("PORT=%q is not a number: %v\n", raw, err) } else if port < 1024 { fmt.Printf("PORT=%d is privileged (below 1024)\n", port) } else { fmt.Printf("PORT=%d ok\n", port) } } }
example.texttextPORT=8080 ok PORT=80 is privileged (below 1024) PORT="http" is not a number: strconv.Atoi: parsing "http": invalid syntax
port und err stehen in allen drei Zweigen zur Verfügung. Nach der schließenden Klammer existieren sie nicht mehr. Fügst du nach dem if ein fmt.Println("listening on", port) ein, scheitert der Build mit undefined: port. Brauchst du den Wert danach noch, deklariere ihn vor dem if in einer eigenen Zeile.
Die Shadowing-Falle
Weil die Kurzanweisung neue Variablen deklariert, kann sie eine äußere Variable mit demselben Namen verdecken. Das hier kompiliert und besteht go vet:
example.gogovar cfg Config if cfg, err := loadConfig(data); err != nil { log.Fatal(err) } else { fmt.Printf("loaded config, addr=%q\n", cfg.Addr) } fmt.Printf("starting server, addr=%q\n", cfg.Addr)
example.texttextloaded config, addr=":8443" starting server, addr=""
Das cfg im if ist eine neue Variable. Das äußere cfg bleibt auf seinem Nullwert, und der Server startet mit einer leeren Adresse. Schreib cfg, err := loadConfig(data) in eine eigene Zeile und prüf err mit einem einfachen if. Der Beitrag zum Go-Keyword var behandelt Shadowing und den Analyzer shadow, der es findet.
Warum vermeidet idiomatisches Go else?
Die meisten Go-Funktionen sind eine Folge von Schritten, von denen jeder scheitern kann. Steckt jeder Schritt im else des vorherigen, wandert der erfolgreiche Pfad nach rechts, und die Fehlerbehandlung landet weit weg von der Prüfung, die sie ausgelöst hat. So sieht ein Order-Handler in diesem Stil aus:
example.gogofunc handleCreateOrder(w http.ResponseWriter, r *http.Request) { var req CreateOrderRequest if err := json.NewDecoder(r.Body).Decode(&req); err == nil { if req.Quantity > 0 { if id, err := saveOrder(req); err == nil { w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"id": id}) } else { http.Error(w, "could not save order", http.StatusInternalServerError) } } else { http.Error(w, "quantity must be positive", http.StatusBadRequest) } } else { http.Error(w, "invalid JSON body", http.StatusBadRequest) } }
Um zu sehen, was bei ungültigem JSON passiert, musst du bis ans Ende der Funktion lesen. Dreh jede Bedingung um, sodass der Fehlerfall zuerst kommt und zurückkehrt:
example.gogofunc handleCreateOrder(w http.ResponseWriter, r *http.Request) { var req CreateOrderRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON body", http.StatusBadRequest) return } if req.Quantity <= 0 { http.Error(w, "quantity must be positive", http.StatusBadRequest) return } id, err := saveOrder(req) if err != nil { http.Error(w, "could not save order", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"id": id}) }
Ein httptest-Lauf beider Versionen mit vier Bodies (kaputtes JSON, Menge null, eine nicht vorrätige SKU und eine gültige Bestellung) liefert identische Statuscodes und Bodies. Die zweite Version hat überhaupt kein else. Jede Prüfung steht direkt neben ihrer Fehlerantwort, und die letzten beiden Zeilen sind der Erfolgspfad am linken Rand. Ein vierter Validierungsschritt ist ein weiterer if-Block, keine weitere Verschachtelungsebene.
Das Go-Team beschreibt diesen Stil in Effective Go: „Da Fehlerfälle meist mit return-Anweisungen enden, braucht der resultierende Code keine else-Anweisungen.“ Die Seite Go Code Review Comments nennt es Indent Error Flow: „Versuche, den normalen Codepfad mit minimaler Einrückung zu halten, und rücke die Fehlerbehandlung ein, indem du sie zuerst erledigst.“
Was Linter zu else sagen
Der Linter revive (der gepflegte Nachfolger von golint) aktiviert standardmäßig zwei Regeln für else. Auf diesen Code angewendet:
example.gogofunc readTimeout() (int, error) { secs, err := strconv.Atoi(os.Getenv("TIMEOUT_SECONDS")) if err != nil { return 0, fmt.Errorf("parse TIMEOUT_SECONDS: %w", err) } else { return secs, nil } } func countValid(lines []string) int { n := 0 for _, line := range lines { if line == "" { continue } else { n++ } } return n }
meldet er:
example.texttextlint.go:13:9: if block ends with a return statement, so drop this else and outdent its block lint.go:23:10: if block ends with a continue statement, so drop this else and outdent its block
Die erste Meldung kommt von indent-error-flow, die anschlägt, wenn der if-Block mit return endet. Die zweite kommt von superfluous-else. Diese Regel deckt continue, break und goto ab sowie Aufrufe, die nie zurückkehren, etwa panic, os.Exit und log.Fatal. revive hat außerdem eine optionale Regel early-return für die umgekehrte Form, bei der der else-Block derjenige ist, der zurückkehrt. golangci-lint stellt dieselben Regeln über seinen Linter revive bereit.
Keine der beiden Standardregeln meldet den verschachtelten Handler von oben, weil keiner seiner Blöcke zurückkehrt. Der Linter fängt nur den mechanischen Fall ab, und eine Pyramide aus Erfolgsprüfungen flachzuklopfen ist ein Refactoring, das du selbst vornehmen musst.
Wann ist else in Go die richtige Wahl?
Das Weglassen von else ist eine Regel für Blöcke, die den Ablauf verlassen. Wenn beide Zweige echte Arbeit leisten und keiner die Funktion verlässt, drückt else am klarsten „eines von beiden“ aus. Das kommt in drei Situationen vor.
Der erste Fall sind zwei Ergebnisse, bei denen es weitergeht. Ein Cache-Lookup zählt einen Hit oder einen Miss und macht dann weiter. Weil keiner der Zweige zurückkehrt, gibt es nichts auszurücken:
example.gogoif entry, ok := cache[key]; ok { hits++ resp = entry } else { misses++ resp = fetchFromOrigin(key) cache[key] = resp }
Der zweite Fall ist die Wahl zwischen zwei Werten. Bei einem günstigen Wert setzt du den Standardwert und überschreibst ihn:
example.gogolevel := slog.LevelInfo if debug { level = slog.LevelDebug }
Hat das Erzeugen des Standardwerts Kosten oder einen Seiteneffekt, etwa das Öffnen einer Verbindung, verwende if/else, damit nur ein Zweig läuft:
example.gogovar store Store if redisURL != "" { store = RedisStore{url: redisURL} } else { store = MemoryStore{} }
Mit Standardwert und späterem Überschreiben hieße das hier var store Store = MemoryStore{}, gefolgt von einer Ersetzung. Das funktioniert nur, weil MemoryStore{} nichts kostet. Würde der Standardwert eine Datei oder einen Connection Pool öffnen, würde das Überschreiben eine Ressource wegwerfen, die du nie benutzt hast.
Der dritte Fall ist eine Variable aus der Kurzanweisung. Wenn jeder Zweig sie braucht, wie im PORT-Beispiel, hält else sie auf die Prüfung beschränkt. Du kannst sie auch vor dem if deklarieren, aber dann bleibt sie für den Rest der Funktion gültig.
Gibt es in Go einen ternären Operator?
Nein. Go hat kein cond ? a : b. Die Go FAQ erklärt, warum: „Der Grund, warum ?: in Go fehlt, ist, dass die Designer der Sprache gesehen hatten, wie oft die Operation benutzt wurde, um undurchschaubar komplexe Ausdrücke zu bilden. Die if-else-Form ist zwar länger, aber zweifellos klarer. Eine Sprache braucht nur ein einziges Konstrukt für bedingten Kontrollfluss.“
Stattdessen schreibst du if/else oder setzt wie im vorherigen Abschnitt einen Standardwert und überschreibst ihn. Für den häufigen Fall „nimm diesen Wert, außer er ist leer“ kam mit Go 1.22 cmp.Or hinzu. Es gibt das erste seiner Argumente zurück, das nicht der Nullwert ist:
example.gogopackage main import ( "cmp" "fmt" "os" ) func main() { addr := cmp.Or(os.Getenv("LISTEN_ADDR"), ":8080") fmt.Println("listening on", addr) }
example.texttextlistening on :8080
cmp.Or ist eine Funktion, kein Operator. Go wertet also vor dem Aufruf jedes Argument aus. cmp.Or(cachedToken, fetchToken()) ruft fetchToken jedes Mal auf, auch wenn cachedToken gesetzt ist. Ein generischer Helfer ternary(cond, a, b) hat dasselbe Problem, und das ist ein Grund, warum Go-Codebasen selten einen definieren. Wenn ein Zweig Kosten hat, schreib das if.
Wo LevelUpGo ins Spiel kommt
LevelUpGo bringt dir Go mit Übungen bei, die echten Go-Code im Browser ausführen. Go Basics behandelt if, else if, switch und das frühe Zurückkehren bei Fehlern, während du die Sprache von Grund auf lernst. Simplification hat Lektionen dazu, verschachtelten Code flachzuklopfen, boolesche Logik zu vereinfachen und else if-Ketten durch switch zu ersetzen. Die anderen 24 reservierten Wörter findest du in Go Keywords: Alle 25 erklärt.
Häufig gestellte Fragen
Was macht else in Go?
else führt einen Block aus, wenn die Bedingung des vorangehenden if falsch ist. Darauf kann ein Block folgen (else { ... }) oder ein weiteres if (else if cond { ... }). Es ist eines der 25 reservierten Keywords von Go und darf nur nach einem if-Block stehen.
Warum meldet Go „syntax error: unexpected keyword else“?
Das else steht in einer neuen Zeile nach der schließenden } des if-Blocks. Go fügt nach einer } am Zeilenende ein Semikolon ein. Das beendet die if-Anweisung, und das else in der nächsten Zeile hat nichts mehr, woran es anschließen kann. Schreib } else { in eine Zeile oder lass gofmt laufen.
Gibt es in Go elif oder elseif?
Nein. Go schreibt es als zwei Wörter, else if. Das ist ein else, gefolgt von einer neuen if-Anweisung. Bei langen Ketten liest sich ein switch ohne Ausdruck nach dem Keyword sauberer.
Sollte ich in Go else nach return verwenden?
Nein. Endet der if-Block mit return, läuft der Code nach dem if nur, wenn die Bedingung falsch war. Das else bringt dann nichts außer Einrückung. revive meldet es mit seiner Regel indent-error-flow, und Go Code Review Comments empfiehlt, den Fehler zuerst zu behandeln und den normalen Pfad ohne Einrückung zu lassen.
Kann ich eine Variable aus der if-Anweisung im else-Block verwenden?
Ja. Eine Variable, die in der Kurzanweisung deklariert wird, wie in if n, err := strconv.Atoi(s); err != nil, ist im if-Block und in jedem else if- und else-Zweig gültig. Nach der letzten schließenden Klammer verlässt sie den Gültigkeitsbereich.
Wie schreibe ich ein einzeiliges if/else in Go?
Nicht in formatiertem Code. Ein einzeiliges if/else kompiliert, aber gofmt verteilt es auf mehrere Zeilen, und Go hat keinen ternären Operator. Verwende ein normales if/else, weise einen Standardwert zu und überschreib ihn in einem if, oder nimm cmp.Or, wenn du einen Fallback für einen Nullwert brauchst.
Quellen
- The Go Programming Language Specification, If statements: https://go.dev/ref/spec#If_statements
- The Go Programming Language Specification, Semicolons: https://go.dev/ref/spec#Semicolons
- The Go Programming Language Specification, Blocks: https://go.dev/ref/spec#Blocks
- Effective Go, If: https://go.dev/doc/effective_go#if
- Go Code Review Comments, Indent Error Flow: https://go.dev/wiki/CodeReviewComments#indent-error-flow
- Go FAQ, Does Go have the ?: operator?: https://go.dev/doc/faq#Does_Go_have_a_ternary_form
- revive rules (indent-error-flow, superfluous-else, early-return): https://github.com/revive-lint/revive/blob/master/RULES_DESCRIPTIONS.md
- cmp package, Or: https://pkg.go.dev/cmp#Or
