<h2>Estruturas de Controle em Go: if, for, switch e defer</h2>
<p>Go é uma linguagem que prioriza simplicidade e clareza. Suas estruturas de controle refletem essa filosofia: são diretas, sem sintaxe desnecessária e com comportamentos bem definidos. Neste artigo, você aprenderá a dominar os quatro pilares do controle de fluxo em Go de forma prática e fundamentada. Cada estrutura tem seu propósito específico, e entendê-las profundamente é essencial para escrever código Go idiomático e eficiente.</p>
<p>A abordagem aqui é progressiva: começamos com as decisões simples (if), passamos por iterações (for), depois ramificações múltiplas (switch) e finalizamos com um conceito único de Go (defer). Você não apenas aprenderá a sintaxe, mas compreenderá o "porquê" por trás de cada decisão de design.</p>
<h2>Condicional if: Decisões Simples e Compostas</h2>
<p>O if em Go é tão minimalista quanto parece. Não há parênteses obrigatórios ao redor da condição, mas as chaves são <strong>obrigatórias</strong>, mesmo que o bloco tenha uma única linha. Isso força uma consistência visual no código que Go cultiva deliberadamente.</p>
<p>A forma básica é direta: você avalia uma expressão booleana e executa um bloco caso seja verdadeira. Mas há nuances importantes que vão além do óbvio.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
idade := 25
// if simples
if idade >= 18 {
fmt.Println("Você é maior de idade")
}
// if com else
if idade < 13 {
fmt.Println("Criança")
} else if idade < 18 {
fmt.Println("Adolescente")
} else {
fmt.Println("Adulto")
}
}</code></pre>
<p>Um aspecto poderoso do if em Go é a possibilidade de declarar e avaliar uma variável na mesma linha. Isso é particularmente útil ao trabalhar com funções que retornam um valor e um erro. A variável declarada nesse contexto é escopo-limitada ao bloco if (e seus else), o que reduz a poluição de variáveis globais.</p>
<pre><code class="language-go">package main
import (
"fmt"
"strconv"
)
func main() {
numero := "42"
// Declarar e usar uma variável no if
if valor, err := strconv.Atoi(numero); err == nil {
fmt.Printf("Número convertido com sucesso: %d\n", valor)
} else {
fmt.Printf("Erro na conversão: %v\n", err)
}
// valor não existe aqui — escopo limitado ao if
}</code></pre>
<p>Isso é mais do que conveniência sintática: é uma filosofia de Go sobre manter variáveis próximas ao seu ponto de uso e evitar estado global desnecessário.</p>
<h2>Iteração for: O Único Loop de Go</h2>
<p>Ao contrário de linguagens como Python ou Java, Go possui apenas uma palavra-chave para iteração: <strong>for</strong>. Não há while, do-while ou foreach separados. Tudo é for, mas com múltiplas formas que cobrem todos os casos de uso.</p>
<p>A primeira forma é a clássica, com inicialização, condição e incremento. A inicialização é opcional, a condição determina quando parar, e o incremento (ou qualquer comando) executa após cada iteração.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
// for clássico
for i := 0; i < 5; i++ {
fmt.Printf("Iteração %d\n", i)
}
// for como while (apenas condição)
contador := 0
for contador < 3 {
fmt.Printf("Contador: %d\n", contador)
contador++
}
// for infinito
// for {
// fmt.Println("Executa para sempre até break")
// break
// }
}</code></pre>
<p>A segunda forma crucial é o <strong>range</strong>, que itera sobre coleções. Com range, você obtém o índice e o valor (ou apenas um deles). Isso é extremamente útil e elimina classes inteiras de bugs relacionados a gerenciamento manual de índices.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
nomes := []string{"Alice", "Bob", "Charlie"}
// índice e valor
for i, nome := range nomes {
fmt.Printf("%d: %s\n", i, nome)
}
// apenas valor (descarta índice com _)
for _, nome := range nomes {
fmt.Printf("Nome: %s\n", nome)
}
// apenas índice
for i := range nomes {
fmt.Printf("Índice: %d\n", i)
}
// iterando sobre mapa
mapa := map[string]int{"x": 10, "y": 20}
for chave, valor := range mapa {
fmt.Printf("%s: %d\n", chave, valor)
}
}</code></pre>
<p>Um detalhe crítico: ao iterar sobre um mapa com range, a ordem <strong>não é garantida</strong>. Isso é uma decisão deliberada de Go para evitar que código dependa de uma ordem que não está documentada. Se você precisa de ordem, deve ordenar explicitamente antes de iterar.</p>
<h2>Switch: Ramificação Limpa para Múltiplas Condições</h2>
<p>O switch em Go é elegante e poderoso. Diferentemente de muitas linguagens, Go <strong>não</strong> requer break entre cases — a execução automaticamente "sai" após um case correspondente, a menos que você use <strong>fallthrough</strong> explicitamente.</p>
<p>Isso elimina uma das fontes mais comuns de bugs em linguagens como C ou JavaScript, onde esquecer o break causa comportamento inesperado.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
dia := 3
// switch básico
switch dia {
case 1:
fmt.Println("Segunda-feira")
case 2:
fmt.Println("Terça-feira")
case 3:
fmt.Println("Quarta-feira")
default:
fmt.Println("Dia inválido")
}
}</code></pre>
<p>Você pode ter múltiplos valores em um único case, separados por vírgulas. Isso reduz duplicação quando vários casos devem executar o mesmo código.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
caractere := 'A'
switch caractere {
case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U':
fmt.Println("Vogal")
default:
fmt.Println("Consoante ou não-letra")
}
}</code></pre>
<p>Go também permite switch sem uma expressão inicial, onde cada case é uma condição booleana completa. Isso é essencialmente syntactic sugar para uma série de if-else if, mas é frequentemente mais legível.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
idade := 25
switch {
case idade < 13:
fmt.Println("Criança")
case idade < 18:
fmt.Println("Adolescente")
case idade < 65:
fmt.Println("Adulto")
default:
fmt.Println("Idoso")
}
}</code></pre>
<p>O fallthrough é explícito e raro. Quando você o usa, fica claro na leitura do código que a intenção é continuar para o próximo case — não é um acidente, é uma decisão documentada.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
numero := 2
switch numero {
case 1:
fmt.Println("Um")
fallthrough
case 2:
fmt.Println("Um ou Dois")
fallthrough
case 3:
fmt.Println("Um, Dois ou Três")
default:
fmt.Println("Outro")
}
// Saída: Um ou Dois / Um, Dois ou Três
}</code></pre>
<h2>Defer: Adiando Execução com Garantia</h2>
<p>O defer é um recurso único de Go que não existe em muitas outras linguagens. Ele permite que você agende uma função para ser executada após a função atual retornar. Parece simples, mas suas aplicações são profundas.</p>
<p>A principal utilidade do defer é garantir que certos códigos de limpeza sejam <strong>sempre</strong> executados, mesmo que exceções ocorram (ou, em Go, mesmo que você retorne prematuramente). Isso é particularmente valioso ao trabalhar com arquivos, conexões de banco de dados ou locks.</p>
<pre><code class="language-go">package main
import (
"fmt"
"os"
)
func main() {
arquivo, err := os.Create("teste.txt")
if err != nil {
fmt.Println("Erro ao criar arquivo:", err)
return
}
// defer garante que Close será chamado
defer arquivo.Close()
arquivo.WriteString("Olá, Go!\n")
fmt.Println("Arquivo escrito com sucesso")
// arquivo.Close() é chamado automaticamente ao sair da função
}</code></pre>
<p>Um comportamento crucial a entender: o defer adiam a <strong>execução</strong>, mas os <strong>argumentos são avaliados imediatamente</strong>. Se você passar o valor de uma variável, aquele valor é "congelado" no momento do defer.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
contador := 1
defer fmt.Println("Defer 1:", contador) // valor 1 é "congelado"
contador = 2
defer fmt.Println("Defer 2:", contador) // valor 2 é "congelado"
contador = 3
fmt.Println("Durante execução:", contador)
// Saída:
// Durante execução: 3
// Defer 2: 2
// Defer 1: 1
}</code></pre>
<p>Note que defers são executados em ordem LIFO (Last In, First Out) — o último defer registrado é o primeiro a executar. Isso é intencional: funciona como uma pilha, permitindo que você estabeleça dependências de limpeza na ordem correta.</p>
<pre><code class="language-go">package main
import (
"fmt"
"sync"
)
func main() {
var mutex sync.Mutex
// Adquire lock
mutex.Lock()
defer mutex.Unlock() // Garante que unlock sempre ocorrerá
fmt.Println("Seção crítica protegida")
// Mesmo que você retorne aqui, Unlock será chamado
// Mesmo que panic ocorra, Unlock será chamado
}</code></pre>
<p>Uma aplicação avançada é usar defer com funções anônimas que capturam variáveis por referência, permitindo lógica de limpeza mais sofisticada.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
valor := "inicial"
defer func() {
fmt.Println("Limpeza final, valor é:", valor)
}()
valor = "modificado"
// Saída: Limpeza final, valor é: modificado
}</code></pre>
<h2>Combinando Estruturas: Padrões Práticos</h2>
<p>Na prática, você raramente usa essas estruturas isoladamente. Elas trabalham juntas para resolver problemas reais. Um padrão comum é combinar for com if para filtrar dados, ou usar switch dentro de um for para diferentes casos.</p>
<pre><code class="language-go">package main
import "fmt"
func main() {
numeros := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("Números pares maiores que 4:")
for _, numero := range numeros {
if numero > 4 && numero%2 == 0 {
fmt.Println(numero)
}
}
}</code></pre>
<p>Outro padrão essencial é usar defer para garantir limpeza dentro de loops ou funções complexas que fazem múltiplas alocações.</p>
<pre><code class="language-go">package main
import (
"fmt"
"os"
)
func processarArquivos(nomes []string) {
for _, nome := range nomes {
arquivo, err := os.Open(nome)
if err != nil {
fmt.Printf("Erro ao abrir %s: %v\n", nome, err)
continue
}
defer arquivo.Close() // Garante fechamento mesmo com continue
// processa arquivo
fmt.Printf("Processando %s\n", nome)
}
}
func main() {
processarArquivos([]string{"arquivo1.txt", "arquivo2.txt"})
}</code></pre>
<p>Entender quando cada estrutura é apropriada é a marca de um programador Go competente. Use if para lógica simples, for para iterações, switch para múltiplas ramificações sobre um valor específico, e defer para garantir limpeza. Essa combinação cobre praticamente todos os cenários de controle de fluxo que você encontrará.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://go.dev/tour/flowcontrol" target="_blank" rel="noopener noreferrer">A Tour of Go - Flow Control Statements</a></li>
<li><a href="https://go.dev/doc/effective_go#control-structures" target="_blank" rel="noopener noreferrer">Effective Go - Control structures</a></li>
<li><a href="https://www.gopl.io/" target="_blank" rel="noopener noreferrer">The Go Programming Language (Donovan & Kernighan)</a> — Capítulos 1-5 cobrem estruturas de controle em profundidade</li>
<li><a href="https://github.com/golang/go/wiki/CodeReviewComments#defer" target="_blank" rel="noopener noreferrer">Go Code Review Comments - defer</a></li>
<li><a href="https://go.dev/blog/defer-panic-and-recover" target="_blank" rel="noopener noreferrer">Official Go Blog - Defer, Panic, and Recover</a></li>
</ul>
<p><!-- FIM --></p>