M01·05Exemplos Práticos Progressivos

CAPÍTULO 05

Exemplos Práticos Progressivos

Do iniciante ao avançado — três programas completos com explicação linha por linha.

Por Thiago Souza10 min de leituraAtualizado em 2026-05

Vamos do mais simples ao mais sofisticado, sempre com explicação linha por linha.

Calculadora simples (nível: iniciante)

go
package main
 
import (
    "bufio"     // leitura eficiente do terminal
    "fmt"
    "os"
    "strconv"   // conversão de string para número
    "strings"   // manipulação de strings
)
 
func main() {
    // bufio.NewReader cria um leitor com buffer (mais eficiente)
    leitor := bufio.NewReader(os.Stdin)
 
    fmt.Print("Digite a primeira operação (ex: 10 + 5): ")
 
    // Lê até encontrar quebra de linha
    linha, _ := leitor.ReadString('\n')
 
    // TrimSpace remove espaços e \n das pontas
    linha = strings.TrimSpace(linha)
 
    // Split divide em pedaços usando " " como separador
    partes := strings.Split(linha, " ")
 
    if len(partes) != 3 {
        fmt.Println("Formato inválido. Use: número operador número")
        return
    }
 
    // Atoi = ASCII to Integer
    a, err := strconv.Atoi(partes[0])
    if err != nil {
        fmt.Println("Primeiro número inválido")
        return
    }
 
    b, err := strconv.Atoi(partes[2])
    if err != nil {
        fmt.Println("Segundo número inválido")
        return
    }
 
    // O switch decide qual operação aplicar
    switch partes[1] {
    case "+":
        fmt.Println("Resultado:", a+b)
    case "-":
        fmt.Println("Resultado:", a-b)
    case "*":
        fmt.Println("Resultado:", a*b)
    case "/":
        if b == 0 {
            fmt.Println("Não dá pra dividir por zero!")
            return
        }
        fmt.Println("Resultado:", a/b)
    default:
        fmt.Println("Operador desconhecido")
    }
}

Buscador de palavras em arquivo (nível: intermediário)

go
package main
 
import (
    "bufio"
    "fmt"
    "os"
    "strings"
)
 
// countOccurrences percorre o arquivo linha por linha
// e conta quantas vezes a palavra aparece.
func countOccurrences(caminho, palavra string) (int, error) {
    arquivo, err := os.Open(caminho)
    if err != nil {
        // %w "embrulha" o erro original, preservando a causa
        return 0, fmt.Errorf("abrir arquivo: %w", err)
    }
    defer arquivo.Close() // garante fechamento ao sair da função
 
    // Scanner lê o arquivo sem carregar tudo na memória.
    // Crucial para arquivos grandes (logs de GB).
    scanner := bufio.NewScanner(arquivo)
 
    count := 0
    palavraLower := strings.ToLower(palavra)
 
    for scanner.Scan() {
        linha := strings.ToLower(scanner.Text())
        // strings.Count conta ocorrências não sobrepostas
        count += strings.Count(linha, palavraLower)
    }
 
    // Verifica se houve erro durante a leitura
    if err := scanner.Err(); err != nil {
        return 0, fmt.Errorf("scanner: %w", err)
    }
 
    return count, nil
}
 
func main() {
    if len(os.Args) != 3 {
        fmt.Println("Uso: programa <arquivo> <palavra>")
        os.Exit(1)
    }
 
    caminho := os.Args[1]
    palavra := os.Args[2]
 
    n, err := countOccurrences(caminho, palavra)
    if err != nil {
        fmt.Println("Erro:", err)
        os.Exit(1)
    }
 
    fmt.Printf("A palavra %q aparece %d vez(es) em %s\n", palavra, n, caminho)
}

Worker pool concorrente (nível: avançado)

Um cenário real: você tem 1000 URLs para baixar e quer fazer isso em paralelo, mas limitando a 10 conexões simultâneas para não derrubar a rede.

go
package main
 
import (
    "fmt"
    "net/http"
    "sync"
    "time"
)
 
// Result é o que cada worker produz
type Result struct {
    URL    string
    Status int
    Erro   error
}
 
// worker pega URLs do channel "trabalhos" e devolve em "resultados"
func worker(id int, trabalhos <-chan string, resultados chan<- Result, wg *sync.WaitGroup) {
    // Avisa que esse worker terminou quando a função retornar
    defer wg.Done()
 
    client := &http.Client{Timeout: 5 * time.Second}
 
    for url := range trabalhos {
        fmt.Printf("[Worker %d] Buscando %s\n", id, url)
 
        resp, err := client.Get(url)
        if err != nil {
            resultados <- Result{URL: url, Erro: err}
            continue
        }
        resp.Body.Close()
 
        resultados <- Result{URL: url, Status: resp.StatusCode}
    }
}
 
func main() {
    urls := []string{
        "https://go.dev",
        "https://google.com",
        "https://github.com",
        "https://wikipedia.org",
        "https://stackoverflow.com",
    }
 
    trabalhos := make(chan string, len(urls))
    resultados := make(chan Result, len(urls))
 
    // WaitGroup permite esperar várias goroutines terminarem
    var wg sync.WaitGroup
 
    // Cria 3 workers
    numWorkers := 3
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1) // incrementa contador antes de iniciar
        go worker(w, trabalhos, resultados, &wg)
    }
 
    // Envia trabalhos
    for _, url := range urls {
        trabalhos <- url
    }
    close(trabalhos) // sinaliza fim para os workers
 
    // Goroutine que fecha resultados quando todos os workers terminarem
    go func() {
        wg.Wait()
        close(resultados)
    }()
 
    // Lê todos os resultados (bloqueia até resultados ser fechado)
    for res := range resultados {
        if res.Erro != nil {
            fmt.Printf("ERRO %s -> erro: %v\n", res.URL, res.Erro)
        } else {
            fmt.Printf("OK %s -> status %d\n", res.URL, res.Status)
        }
    }
}

O que aprendemos aqui:

  1. Worker pool: padrão clássico para limitar paralelismo.
  2. WaitGroup: sincroniza fim de várias goroutines.
  3. Channels direcionais: <-chan (só recebe) e chan<- (só envia) deixam o código mais seguro.
  4. defer wg.Done(): garante que o contador é decrementado mesmo se houver panic.