Go: Fluxo de controle em Go: as quatro principais…

O fluxo de controle faz com que os programas “pensem” — o Go abrange todos os cenários de fluxo de controle com apenas quatro palavras-chave simples, eliminando a redundância dos loops while e do-while.

A filosofia de design do fluxo de controle do Go: Menos é mais. As quatro palavras-chave if, switch, for e defer, combinadas com break, continue e label, são suficientes para lidar com 99% da lógica de negócios. Nesta lição, você vai dominar todos os conceitos fundamentais do controle de fluxo do Go.

1. Você aprenderá


2. Uma história real sobre uma plataforma de educação online

(1) Problema: 500 linhas de código em C++ para corrigir provas, mas os bugs continuam surgindo

Charlie é engenheiro de back-end em uma plataforma de educação online. Ele acaba de assumir o código de um sistema de correção automática:

“O primeiro-ministro disse que agora leva 8 horas para processar 1.000 provas, e toda vez que surge um bug, é ou um erro de ‘fora do intervalo’ ou a falta de {} em uma instrução if da linguagem C, o que faz com que as notas de toda a turma fiquem confusas...”

Ele abriu o código em C++ que seu antecessor havia deixado e deu uma olhada:

CPP
// C++ Style: Verbose + Prone to Errors
if (score >= 60) {
    grade = "Passing Grade";
}
else  // Note: C++ else on a different line is valid, but team style is inconsistent
    grade = "Fail";

// Omission of a loop condition = an infinite loop
while (true) {  // Wanted 3 iterations, but forgot the counter
    // ... Didn't realize until 500 lines later
}

Depois de assumir o projeto, Charlie decidiu reescrevê-lo em Go.

(2) Solução em Go

O Go abrange todo o processo com quatro palavras-chave:

GO
// score_evaluator.go
package main

import "fmt"

func evaluateScore(score int) string {
    // (1) if contains an initialization statement
    if grade := calculateGrade(score); grade != "" {
        return fmt.Sprintf("Score: %d, Level: %s", score, grade)
    }
    return "Invalid Score"
}

func calculateGrade(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    // (2) for 4 forms
    scores := []int{95, 82, 67, 45, 88}

    for i, s := range scores {  // range form
        fmt.Printf("Question %d: %d points -> %s\n", i+1, s, evaluateScore(s))
    }
}

Resultado:

TEXT 📖 Somente leitura
Question 1: 95 points -> Score: 95, Level A
Question 2: 82 points -> Score: 82, Level B
Question 3: 67 points -> Score: 67, Level C
Question 4: 45 points -> Score: 45, Level D
Question 5: 88 points -> Score: 88, Level B

(3) Benefícios: Comparação da simplicidade do controle de processos

Linguagem palavra-chave “if” palavra-chave de loop quebra automática em “switch” Volume de código para o sistema de pontuação
C/C++ if for/while/do-while Requer manual break ~500 linhas
Java if for/while/do-while Requer break manual ~400 linhas
Python if for/while N/A (sem switch) ~350 linhas
Ir if for (4 formas) quebra automática ~200 linhas
💡 Dica: O Go unifica todos os loops em for e remove break do comportamento padrão de switch — uma palavra-chave a menos, dez tipos de erros a menos.


3. Instrução if

(1) if / else básico

GO
package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("Excellent")
    } else if score >= 60 {
        fmt.Println("Passing Grade")
    } else {
        fmt.Println("Fail")
    }
}

Saída:

TEXT 📖 Somente leitura
Passing Grade
🔥 Erro comum: No Go, a palavra-chave else deve estar na mesma linha que } (é obrigatório o uso de chaves). A sintaxe }else{ resulta em um erro de compilação.

(2) ▶ Exemplo: Determinação da nota

GO
package main

import "fmt"

func judgeGrade(score int) string {
    if score >= 90 {
        return "A"
    } else if score >= 80 {
        return "B"
    } else if score >= 70 {
        return "C"
    } else if score >= 60 {
        return "D"
    } else {
        return "F"
    }
}

func main() {
    fmt.Println(judgeGrade(95))  // A
    fmt.Println(judgeGrade(72))  // C
    fmt.Println(judgeGrade(50))  // F
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
A
C
F

(3) se houver instruções de inicialização (um recurso do Go)

if pode executar primeiro uma instrução de inicialização, seguida por uma verificação condicional — limitando o escopo das variáveis:

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Syntax: if initialization statement; condition { ... }
    // The variable `err` is scoped only within the `if-else` block.
    if n, err := strconv.Atoi("123"); err == nil {
        fmt.Printf("Conversion successful: %d\n", n)
    } else {
        fmt.Printf("Conversion failed: %v\n", err)
    }
}

Saída:

TEXT 📖 Somente leitura
Conversion successful: 123

(4) ▶ Exemplo: Lendo um arquivo + fechando com defer

GO
package main

import (
    "fmt"
    "os"
)

func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // automatically close file before function returns

    data := make([]byte, 1024)
    n, _ := file.Read(data)
    fmt.Printf("%d bytes read\n", n)

    return nil
}

func main() {
    readFile("config.txt")
}
▶ Experimente

(5) Práticas comuns para o tratamento de erros em instruções if (uma característica marcante do Go)

O tratamento de erros em Go é feito por meio de instruções if. Uso convencional:

GO
if err := doSomething(); err != nil {
    return err  // early return
}
// Normal Logic
💡 Dica: A lição 8 apresentará uma explicação detalhada sobre o tipo error e as melhores práticas para esse padrão.


4. Mudança de ramificação (interrupção automática)

(1) Noções básicas sobre interruptores: interrupção automática

Por padrão, a instrução switch do Go não se propaga — ela encerra automaticamente após encontrar uma correspondência:

GO
package main

import "fmt"

func main() {
    day := "Monday"

    switch day {
    case "Monday":
        fmt.Println("Monday: Meeting Day")
    case "Tuesday":
        fmt.Println("Tuesday: Development Day")
    case "Friday":
        fmt.Println("Friday: Demo Day")
    default:
        fmt.Println("Weekend: Day off")
    }
}

Saída:

TEXT 📖 Somente leitura
Monday: Meeting Day

(2) em comparação com C/Java: Não há necessidade de um break

Linguagem Sintaxe Quebra automática
C/C++ case 1: ...; case 2: ... Requer um manual break;
Java case 1: ...; case 2: ... Requer um manual break;
Ir case "Monday": ... Quebra automática

Se você quiser usar o fall-through, utilize a palavra-chave fallthrough:

GO
switch n {
case 1:
    fmt.Println("One")
    fallthrough  // force execution of the next case
case 2:
    fmt.Println("Two")
}

Saída:

TEXT 📖 Somente leitura
One
Two

(3) Alterar o formato da expressão (sem tag)

Não coloque uma variável logo após switch; escreva a condição para cada case separadamente:

GO
package main

import "fmt"

func gradeLevel(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    fmt.Println(gradeLevel(95))
    fmt.Println(gradeLevel(72))
}

Saída:

TEXT 📖 Somente leitura
A
C

(4) ▶ Exemplo: Switch com correspondência de múltiplos valores + roteamento por código de status HTTP

GO
package main

import "fmt"

func routeStatus(code int) string {
    switch code {
    case 200, 201, 204:
        return "Success"
    case 301, 302:
        return "Redirect"
    case 400, 401, 403, 404:
        return "Client Error"
    case 500, 502, 503:
        return "Server Error"
    default:
        return "Unknown"
    }
}

func main() {
    fmt.Println(routeStatus(200))  // Success
    fmt.Println(routeStatus(404))  // Client Error
    fmt.Println(routeStatus(500))  // Server Error
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Success
Client Error
Server Error

5. As 4 formas do loop “for”

O Go unifica todos os loops sob a construção for, utilizando sintaxes diferentes para implementar while, do-while e loops infinitos.

(1) O clássico loop “for” de três partes

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 3; i++ {
        fmt.Printf("The %dth time\n", i)
    }
}

Saída:

TEXT 📖 Somente leitura
The 1th time
The 2th time
The 3th time

(2) ▶ Exemplo: estilo “while” (loop condicional)

GO
package main

import "fmt"

func main() {
    n := 1
    for n < 5 {  // equivalent to while (n < 5)
        fmt.Printf("n=%d\n", n)
        n++
    }
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
n=1
n=2
n=3
n=4

(3) Loop infinito

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // Classic Infinite Loop (In real-world scenarios, it would use `break` or `return`)
    for {
        fmt.Println("Print once per second...")
        time.Sleep(1 * time.Second)
        break  // prevent actual infinite loop, for demo only
    }
}

Saída:

TEXT 📖 Somente leitura
Print once per second...

(4) ▶ Exemplo: Iteração sobre uma fatia, um mapa ou uma string usando range

GO
package main

import "fmt"

func main() {
    scores := []int{95, 82, 67}

    // Iterate through the slice: index, value
    for i, score := range scores {
        fmt.Printf("Problem %d: %d points\n", i+1, score)
    }

    // If only value is present: use _ to ignore index
    for _, score := range scores {
        fmt.Printf("Score: %d\n", score)
    }

    // Iterate through a map (key-value)
    ages := map[string]int{"Alice": 28, "Bob": 32}
    for name, age := range ages {
        fmt.Printf("%s is %d years old\n", name, age)
    }

    // Iterate over a string (by rune, not by byte)
    for i, r := range "Go" {
        fmt.Printf("Position %d: %c (code point %d)\n", i, r, r)
    }
}
▶ Experimente

Saída (a ordem no mapa é aleatória):

TEXT 📖 Somente leitura
Problem 1: 95 points
Problem 2: 82 points
Problem 3: 67 points
Score: 95
Score: 82
Score: 67
Alice is 28 years old
Bob is 32 years old
Position 0: G (code point 71)
Position 1: o (code point 111)
🔥 Erro comum: A ordem de range map não é garantida — ela pode variar a cada execução. Se você precisar de uma ordem consistente, classifique as chaves antes de iniciar a iteração.

(5) Referência rápida às quatro formas de “for”

Forma Sintaxe Alternativa
Padrão para for i := 0; i < n; i++ {} C/Java for
Estilo “while” for condition {} C/Java while
Infinite Loop for {} while(true)
iteração de intervalo for i, v := range slice {} Python for...in
100%
flowchart LR
    A[for init; condition; post] --> B{condition true?}
    B -->|yes| C[execute loop body]
    C --> D[execute post statement]
    D --> B
    B -->|no| E[exit loop]
    style A stroke-width:2px
    style E stroke:#f44,stroke-width:2px

6. defer: Execução diferida (ordem LIFO)

defer adia a execução de uma instrução até o momento imediatamente anterior ao retorno da função atual. É comumente usado para a limpeza de recursos (fechar arquivos, liberar recursos e encerrar conexões).

(1) Noções básicas sobre o defer

GO
package main

import "fmt"

func main() {
    fmt.Println("1: Start of function")

    defer fmt.Println("3: defer execution (before the function returns)")

    fmt.Println("2: Function Body")
}

Saída:

TEXT 📖 Somente leitura
1: Start of function
2: Function Body
3: defer execution (before the function returns)

(2) Adiar a ordem LIFO (último a entrar, primeiro a sair)

GO
package main

import "fmt"

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")

    fmt.Println("Main logic complete")
}

Saída:

TEXT 📖 Somente leitura
Main logic complete
3
2
1

Por que LIFO? Porque defer simula o “gerenciamento de recursos baseado em pilha” — os recursos abertos por último são fechados primeiro.

(3) ▶ Exemplo: Uso prático de defer — Fechamento de uma conexão com o banco de dados

GO
package main

import (
    "database/sql"
    "fmt"
    "log"
)

func queryUser(db *sql.DB, userID int) {
    // Get the connection
    rows, err := db.Query("SELECT name FROM users WHERE id = ?", userID)
    if err != nil {
        log.Fatal(err)
    }

    // Key Point: `defer` releases resources (LIFO)
    defer rows.Close()  // Close result set when function returns

    // Business logic...
    for rows.Next() {
        var name string
        rows.Scan(&name)
        fmt.Printf("User: %s\n", name)
    }
}

func main() {
    fmt.Println("Example of Resource Management with `defer`")
    // Real-world scenario: db, _ := sql.Open("mysql", "...")
    // queryUser(db, 1)
}
▶ Experimente
💡 Dica: defer file.Close() será executado mesmo que return seja chamado no meio — esse é um caso de uso emblemático do gerenciamento de recursos do Go. Observação: use apenas defer para liberar os recursos que você abriu nesta função — não use defer db.Close() aqui, pois db foi passado como parâmetro e seu ciclo de vida é gerenciado pelo chamador.


7. break / continue / label: Saltos precisos

(1) break: Sair do loop

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 10; i++ {
        if i == 5 {
            break  // exit the entire loop
        }
        fmt.Printf("%d ", i)
    }
    fmt.Println("End of loop")
}

Saída:

TEXT 📖 Somente leitura
1 2 3 4 End of loop

(2) ▶ Exemplo: continuar pulando + incrementar os números ímpares

GO
package main

import "fmt"

func sumOdd(n int) int {
    sum := 0
    for i := 1; i <= n; i++ {
        if i%2 == 0 {
            continue  // skip even numbers
        }
        sum += i
    }
    return sum
}

func main() {
    fmt.Printf("1-10: Sum of odd numbers: %d\n", sumOdd(10))
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
1-10: Sum of odd numbers: 25

(3) Use um label para sair de um loop aninhado

label nomeia o loop; break label sai do loop especificado:

GO
package main

import "fmt"

func main() {
outer:
    for i := 1; i <= 3; i++ {
        for j := 1; j <= 3; j++ {
            if i*j == 6 {
                fmt.Printf("Hit: i=%d, j=%d\n", i, j)
                break outer  // directly exit the outer loop
            }
            fmt.Printf("(%d, %d) ", i, j)
        }
    }
    fmt.Println("Done")
}

Saída:

TEXT 📖 Somente leitura
(1, 1) (1, 2) (1, 3) (2, 1) (2, 2) (2, 3) (3, 1) (3, 2) Hit: i=3, j=2
Done

(4) Comparação dos três tipos de redirecionamentos

Palavra-chave Função Fora do escopo
break Encerra o loop ou a instrução switch atual Nível único
continue Pular o restante desta iteração Nível único
break label Sair do loop no rótulo especificado Aninhado

8. Exemplo completo: Sistema de correção de provas on-line

Interconecte todos os fluxos de controle desta lição para implementar um sistema de correção de provas multidimensional:

GO
// exam_evaluator.go
package main

import "fmt"

// Structure of the Problem
type Question struct {
    Type    string  // single/multi/judge
    Correct string  // correct answer
    Student string  // student answer
    Score   float64 // score value
}

// Scoring Function: Initialized with an `if` statement + `switch` expression + `for range`
func evaluateExam(name string, questions []Question) (total float64, details []string) {
    defer func() {
        // defer: Collect results (ensures that results are collected even if a return statement is encountered midway)
        details = append(details, fmt.Sprintf("[%s] Total score: %.1f", name, total))
    }()

    for i, q := range questions {
        var earned float64

        // switch expression format: multi-condition branching
        switch q.Type {
        case "single":
            if q.Student == q.Correct {
                earned = q.Score
            }
        case "judge":
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        case "multi":
            // Multiple-choice question: Points are awarded only for exact matches
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        default:
            continue  // skip unknown question type
        }

        total += earned
        details = append(details,
            fmt.Sprintf("Problem %d (%s): Scored %.1f points", i+1, q.Type, earned))
    }

    return total, details
}

func main() {
    questions := []Question{
        {Type: "single", Correct: "B", Student: "B", Score: 2},
        {Type: "judge", Correct: "true", Student: "true", Score: 1},
        {Type: "multi", Correct: "A,B,C", Student: "A,B", Score: 3},
        {Type: "single", Correct: "D", Student: "D", Score: 2},
    }

    // Use a `for range` loop to iterate through the student list
    students := []string{"Alice", "Bob", "Charlie"}
    for _, name := range students {
        total, details := evaluateExam(name, questions)

        fmt.Println("========================================")
        for _, d := range details {
            fmt.Println(d)
        }
    }
}

Resultado esperado (notas detalhadas de cada aluno + resumo do adiamento):

TEXT 📖 Somente leitura
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Alice] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Bob] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Charlie] Total score: 5.0
🔥 Erro comum: É preciso modificar o valor de retorno nomeado em defer para que ele fique visível no escopo externo — no exemplo acima, details usa o valor de retorno nomeado (total float64, details []string), e é por isso que append dentro de defer entra em vigor.


❓ Perguntas Frequentes

P: Por que o Go não tem uma palavra-chave while? R: O Go usa for para todos os loops: for condition {} é equivalente a while, e for {} é equivalente a while(true). Com uma palavra-chave a menos, as equipes não precisam discutir se devem usar while ou do-while.

P: Não é um incômodo que switch não passe automaticamente por padrão? R: Pelo contrário, a equipe do Go acredita que 90% dos casos de switch devem ser independentes — o problema de “esquecer de escrever break, causando a passagem automática” em C e Java é um dos bugs clássicos. O Go usa fallthrough para indicar explicitamente a intenção de avançar, o que é mais seguro.

P: Por que a ordem do mapa é aleatória ao iterar com for range? R: O Go foi projetado intencionalmente para não ter ordem — a fim de evitar que os programadores dependam da ordem do mapa. Se você precisar de uma ordem consistente, classifique as chaves antes de iterar: keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys).

P: Quando a instrução defer é executada? Qual é a ordem de execução para múltiplas instruções defer? R: A instrução defer é executada antes que a função atual retorne (ou seja, após a execução da instrução return, mas antes que a função realmente seja encerrada). Várias instruções defer são executadas na ordem LIFO (último a entrar, primeiro a sair), simulando o gerenciamento de recursos baseado em pilha: os recursos empurrados para a pilha por último são liberados primeiro.

P: Qual é o escopo de uma instrução de inicialização if? R: As variáveis declaradas em uma instrução de inicialização if são visíveis apenas dentro do bloco if-else; elas se tornam inválidas assim que se sai do bloco if-else. Isso evita o erro comum de “variáveis vazando para o escopo externo”.

P: Os label são usados com frequência em projetos reais? R: Raramente. label é usado principalmente para sair de loops aninhados; em 99% dos casos, break combinado com return é suficiente. O uso excessivo de label reduz a legibilidade do código — uma lição aprendida na era goto.

P: A declaração switch do Go oferece verificação de tipos? R: Sim. switch x := y.(type) é usado para a verificação de tipos de interface; abordaremos isso com mais detalhes na Lição 7. No entanto, essa sintaxe se aplica apenas a variáveis do tipo interface{}.

P: É possível usar continue e break em uma instrução switch? R: Sim, mas eles se comportam de maneira diferente. break encerra a instrução switch; em um loop for, ele encerra o loop for. continue só pode ser usado em um loop for; ele não pode ser usado em uma instrução switch (já que um switch não é considerado um loop).


📖 Resumo


📝 Exercícios

  1. Problema básico (Dificuldade ⭐): Escreva um programa FizzBuzz usando for e switch: Imprima os números de 1 a 100. Quando encontrar um múltiplo de 3, imprima “Fizz”; quando encontrar um múltiplo de 5, imprima “Buzz”; e quando encontrar um múltiplo de 15, imprima “FizzBuzz”.

  2. Problema Avançado (Dificuldade ⭐⭐): Implemente a função countPrimes(n int) int, que retorna o número de números primos entre 1 e n. Requisitos: (1) Use um loop for; (2) Use uma instrução if para verificar se um número é primo; (3) Use defer para imprimir o tempo de execução (usando time.Now()) antes que a função retorne.

  3. Problema de Desafio (Dificuldade ⭐⭐⭐): Implemente um analisador de notas dos alunos: Dado um conjunto de notas ([]float64), exiba (1) a nota média; (2) a taxa de aprovação (≥60 pontos); (3) as notas mais alta e mais baixa; (4) uma divisão por nota (A/B/C/D/F). Requisitos: (1) Use for range; (2) Use uma instrução switch para determinar as notas; (3) Use defer para imprimir as informações resumidas antes que a função retorne.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%