La palabra clave else ejecuta un bloque cuando la condición del if anterior es falsa. Escribe else if para comprobar otra condición y encadena tantas como necesites. El else tiene que ir en la misma línea que la llave de cierre del bloque if, o el código no compila. El Go idiomático usa else menos que la mayoría de los lenguajes. Cuando el bloque if termina en return, el código que sigue al if ya es el caso else, así que el código Go retorna pronto y deja el camino feliz sin sangría (especificación de Go).
Resumen rápido
if cond { ... } else { ... }necesita llaves en los dos bloques y nada de paréntesis alrededor de la condición.} else {tiene que ir en una sola línea. Un salto de línea después de}termina la sentenciaif, y el compilador informasyntax error: unexpected keyword else, expected }.- Las cadenas
else ifse evalúan de arriba abajo y gana la primera condición verdadera. Las cadenas largas se leen mejor como unswitchsin expresión. - Una variable declarada en la sentencia corta del
if(if v, err := f(); err != nil) es visible en todas las ramaselse ifyelse, y desaparece tras la última}. - Cuando el bloque
ifretorna, quita elelse. El linterrevivemarca la forma sobrante conif block ends with a return statement, so drop this else and outdent its block. - Go no tiene el operador
?:. Usaif/else, ocmp.Orcuando solo necesites un valor por defecto para un valor cero.
¿Cómo se escribe if/else en Go?
Un if recibe una condición booleana y un bloque. Un bloque else a continuación se ejecuta cuando la condición es falsa. Esta es la decisión que toma un bucle de reintentos después de una petición fallida:
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
Hay tres reglas que difieren de C, Java y JavaScript:
- Sin paréntesis alrededor de la condición.
if (attempt < maxAttempts) {compila, perogofmtquita los paréntesis. - Las llaves son obligatorias, incluso para un cuerpo de una sola línea.
if code >= 500 fmt.Println("server error")falla consyntax error: unexpected name fmt, expected {. - La condición tiene que ser un
bool. Go no tiene valores truthy, así queif len(items)eif userson errores de tipo. Escribeif len(items) > 0eif user != nil.
La misma regla de las llaves se aplica a else. Una sentencia suelta después de else falla con syntax error: else must be followed by if or statement block. Solo puede seguirle otro if o un bloque { ... }. La palabra clave if en Go explica las condiciones y las sentencias de inicialización desde el lado del if.
¿Por qué else debe ir en la misma línea que }?
La gramática de Go usa punto y coma para terminar las sentencias, pero casi nunca los escribes. El analizador léxico inserta uno al final de una línea cuando el último token es un identificador, un literal, una de las palabras clave break, continue, fallthrough o return, un operador como ++, o un ), ] o } de cierre (especificación de Go, Semicolons).
Por esa regla falla esto:
example.gogoif retries > 0 { fmt.Println("retrying") } else { fmt.Println("giving up") }
example.texttext./main.go:10:2: syntax error: unexpected keyword else, expected }
La línea que termina en } recibe un punto y coma, que termina la sentencia if. La línea siguiente empieza entonces una nueva sentencia con else, y ninguna sentencia puede empezar con else. La solución es poner } else { en una sola línea, que es también lo que produce gofmt. Por la misma regla, Go pone la llave de apertura en la misma línea que el propio if.
Los programadores de C y Java que prefieren el estilo Allman (la llave en su propia línea) se topan con esto en su primera semana. La ventaja es que todos los proyectos en Go formatean if/else igual, así que el estilo nunca sale en una revisión de código.
¿Cómo funciona else if en Go?
else if no es una palabra clave aparte. Es un else seguido de otra sentencia if, algo que la gramática permite directamente: "else" ( IfStmt | Block ). Encadena tantos como necesites. Go los evalúa de arriba abajo y ejecuta la primera rama cuya condición sea verdadera.
Este bucle clasifica latencias de peticiones en buckets de métricas:
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
El orden importa. Si intercambias las dos primeras condiciones para que d < time.Second vaya primero, la salida pasa a ser fast=0 slow=3 timed_out=1. Toda petición por debajo de 100ms también está por debajo de un segundo, así que la comprobación más amplia la captura antes y la rama fast nunca se ejecuta. Pon primero el rango más estrecho.
Cuando una cadena supera las tres ramas, un switch sin expresión suele leerse mejor. Cada case es una condición, los casos se evalúan en el mismo orden de arriba abajo y default sustituye al else final:
example.gogofor _, d := range latencies { switch { case d < 100*time.Millisecond: fast++ case d < time.Second: slow++ default: timedOut++ } }
La salida es la misma. Consulta la sección de switch de la guía de palabras clave de Go para ver en qué se diferencia el switch de Go del de C. switch y select usan default para el caso «no coincidió nada más», así que else solo aparece después de un if.
¿Cuál es el alcance de una variable declarada en un if?
Un if puede empezar con una sentencia corta, normalmente una declaración con :=. Las variables que declara tienen como alcance toda la sentencia if, lo que incluye todas las ramas else if y else. La especificación coloca cada if en su propio bloque implícito, y las ramas else quedan dentro de él (especificación de Go, Blocks).
Aquí es donde else resulta difícil de sustituir. Comprobar un valor PORT leído del entorno necesita el número ya convertido en todas las ramas:
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 y err están disponibles en las tres ramas. Después de la llave de cierre ya no existen. Si añades fmt.Println("listening on", port) después del if, la compilación falla con undefined: port. Si necesitas el valor más adelante, declara la variable en su propia línea antes del if.
La trampa del shadowing
Como la sentencia corta declara variables nuevas, puede ocultar una variable externa con el mismo nombre. Esto compila y pasa 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=""
El cfg de dentro del if es una variable nueva. El cfg externo se queda con su valor cero, y el servidor arranca con una dirección vacía. Escribe cfg, err := loadConfig(data) en su propia línea y comprueba err con un if normal. El artículo sobre la palabra clave var en Go trata el shadowing y el analizador shadow que lo detecta.
¿Por qué el Go idiomático evita else?
La mayoría de las funciones en Go son una serie de pasos que pueden fallar uno a uno. Si cada paso se anida dentro del else del anterior, el camino exitoso se desplaza a la derecha y el manejo de errores acaba lejos de la comprobación que lo provocó. Un handler de pedidos escrito así:
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) } }
Para ver qué pasa con un JSON inválido, tienes que leer hasta el final de la función. Invierte cada condición para que el caso de error vaya primero y retorne:
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}) }
Una ejecución con httptest de las dos versiones contra cuatro cuerpos (JSON roto, cantidad cero, un SKU sin stock y un pedido válido) da códigos de estado y cuerpos idénticos. La segunda versión no tiene ningún else. Cada comprobación está junto a su respuesta de error, y las dos últimas líneas son el camino exitoso pegado al margen izquierdo. Añadir un cuarto paso de validación significa añadir un bloque if más, no otro nivel de anidamiento.
El equipo de Go describe este estilo en Effective Go: «Como los casos de error suelen terminar en sentencias return, el código resultante no necesita sentencias else». La página Go Code Review Comments lo llama Indent Error Flow: «Intenta mantener el camino normal del código con la mínima sangría, y aplica sangría al manejo de errores, ocupándote de él primero».
Qué dicen los linters sobre else
El linter revive (el sucesor mantenido de golint) activa por defecto dos reglas sobre else. Al ejecutarlo sobre este código:
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 }
informa:
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
El primero viene de indent-error-flow, que salta cuando el bloque if termina en return. El segundo viene de superfluous-else, que cubre continue, break y goto, además de llamadas que nunca retornan, como panic, os.Exit y log.Fatal. revive también tiene una regla opcional early-return para la forma invertida, en la que el bloque else es el que retorna. golangci-lint expone las mismas reglas a través de su linter revive.
Ninguna de las reglas por defecto marca el handler anidado de arriba, porque ninguno de sus bloques retorna. El linter solo detecta el caso mecánico, y aplanar una pirámide de comprobaciones de éxito es una refactorización que tienes que hacer tú.
¿Cuándo conviene usar else en Go?
Quitar el else es una regla para bloques que salen. Cuando las dos ramas hacen trabajo real y ninguna sale de la función, else es la forma más clara de decir «una de estas dos». Eso ocurre en tres situaciones.
La primera son dos resultados que siguen adelante. Una consulta a la caché cuenta un acierto o un fallo y luego continúa, y como ninguna rama retorna, no hay nada que quitar de la sangría:
example.gogoif entry, ok := cache[key]; ok { hits++ resp = entry } else { misses++ resp = fetchFromOrigin(key) cache[key] = resp }
La segunda es elegir entre dos valores. Para un valor barato, asigna el valor por defecto y sobrescríbelo:
example.gogolevel := slog.LevelInfo if debug { level = slog.LevelDebug }
Cuando construir el valor por defecto tiene un coste o un efecto secundario, como abrir una conexión, usa if/else para que solo se ejecute una rama:
example.gogovar store Store if redisURL != "" { store = RedisStore{url: redisURL} } else { store = MemoryStore{} }
Asignar el valor por defecto y luego sobrescribirlo significaría var store Store = MemoryStore{} seguido de un reemplazo. Eso solo funciona aquí porque MemoryStore{} no cuesta nada. Si el valor por defecto abriera un archivo o un pool de conexiones, la sobrescritura descartaría un recurso que nunca llegaste a usar.
La tercera es una variable de la sentencia corta. Cuando todas las ramas la necesitan, como en el ejemplo de PORT, el else la mantiene limitada a la comprobación. Declararla antes del if también funciona, pero entonces sigue en el alcance durante el resto de la función.
¿Existe el operador ternario en Go?
No. Go no tiene cond ? a : b. Las preguntas frecuentes de Go explican por qué: «La razón por la que ?: no está en Go es que los diseñadores del lenguaje habían visto la operación usada con demasiada frecuencia para crear expresiones impenetrablemente complejas. La forma if-else, aunque más larga, es indiscutiblemente más clara. Un lenguaje solo necesita una construcción condicional de control de flujo».
El sustituto es if/else o la forma de valor por defecto y sobrescritura de la sección anterior. Para el caso habitual de «usa este valor salvo que esté vacío», Go 1.22 añadió cmp.Or, que devuelve el primero de sus argumentos que no sea el valor cero:
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 es una función, no un operador, así que Go evalúa todos los argumentos antes de la llamada. cmp.Or(cachedToken, fetchToken()) llama a fetchToken siempre, incluso cuando cachedToken tiene valor. Un helper genérico ternary(cond, a, b) tiene el mismo problema, y esa es una de las razones por las que los proyectos en Go rara vez definen uno. Cuando una rama tiene un coste, escribe el if.
Dónde encaja LevelUpGo
LevelUpGo enseña Go con ejercicios que ejecutan código Go real en el navegador. Go Basics cubre if, else if, switch y el retorno temprano ante errores como parte de aprender el lenguaje desde cero. Simplification tiene lecciones sobre aplanar código anidado, simplificar lógica booleana y sustituir cadenas else if por switch. Para las otras 24 palabras reservadas, consulta Palabras clave de Go: las 25 explicadas.
Preguntas frecuentes
¿Qué hace else en Go?
else ejecuta un bloque cuando la condición del if anterior es falsa. Puede ir seguido de un bloque (else { ... }) o de otro if (else if cond { ... }). Es una de las 25 palabras clave reservadas de Go y solo puede aparecer después de un bloque if.
¿Por qué Go da "syntax error: unexpected keyword else"?
El else está en una línea nueva después de la } de cierre del bloque if. Go inserta un punto y coma después de una } al final de una línea, lo que termina la sentencia if, así que el else de la línea siguiente no tiene a qué unirse. Escribe } else { en una sola línea, o ejecuta gofmt.
¿Tiene Go elif o elseif?
No. Go lo escribe como dos palabras, else if, que es un else seguido de una nueva sentencia if. Para cadenas largas, un switch sin expresión después de la palabra clave se lee con más claridad.
¿Debo usar else después de return en Go?
No. Si el bloque if termina con return, el código que sigue al if solo se ejecuta cuando la condición era falsa, así que el else no aporta nada salvo sangría. revive lo marca con su regla indent-error-flow, y Go Code Review Comments recomienda manejar primero el error y mantener el camino normal sin sangría.
¿Puedo usar en el bloque else una variable declarada en el if?
Sí. Una variable declarada en la sentencia corta, como en if n, err := strconv.Atoi(s); err != nil, está en el alcance del bloque if y de todas las ramas else if y else. Sale del alcance después de la última llave de cierre.
¿Cómo se escribe un if/else en una sola línea en Go?
No en código formateado. Un if/else en una sola línea compila, pero gofmt lo reparte en varias líneas, y Go no tiene operador ternario. Usa un if/else normal, asigna un valor por defecto y sobrescríbelo en un if, o usa cmp.Or cuando necesites un valor de respaldo para un valor cero.
Fuentes
- 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
