Código da aula: Github

Introdução

Na aula anterior, exploramos o uso do panic para tratamento de erros em Go. Hoje, vamos abordar outro conceito fundamental da linguagem: o `defer`. Esta palavra-chave permite adiar a execução de uma função, até que a função em execução no momento termine, sendo extremamente útil para garantir que recursos sejam liberados adequadamente, independente do fluxo de execução do programa. O `defer` é frequentemente utilizado para limpeza de recursos, fechamento de arquivos, de conexões e garantia de que certas operações sejam executadas mesmo em caso de erros. Vamos explorar alguns casos práticos do uso de `defer`.

Exemplos Práticos

1. Manipulação de Arquivos

Um dos usos mais comuns do `defer` é garantir que arquivos sejam fechados corretamente após sua utilização.

45 - defer/file-manipulation/file_manipulation.go

package main

import (
    "fmt"
    "os"
)

func processarArquivo(nomeArquivo string) error {
    // Abre o arquivo
    arquivo, err := os.Open(nomeArquivo)
    if err != nil {
        return fmt.Errorf("erro ao abrir arquivo: %v", err)
    }
    // Garante que o arquivo será fechado ao final da função
    defer func() {
        fmt.Println("Fechando arquivo...")
        arquivo.Close()
    }()

    // Processa o arquivo
    fmt.Println("Processando arquivo...")
    // Lê o conteúdo do arquivo e exibe
    buffer := make([]byte, 100)
    for {
        n, err := arquivo.Read(buffer)
        if err != nil {
            if err.Error() == "EOF" {
                break
            }
            return fmt.Errorf("erro ao ler arquivo: %v", err)
        }
        fmt.Print(string(buffer[:n]))
    }

    return nil
}

func main() {
    err := processarArquivo("exemplo.txt")
    if err != nil {
        fmt.Printf("Ocorreu um erro: %v\n", err)
        return
    }
    fmt.Println("\nArquivo processado com sucesso!")
}
Descrição:
  • `defer arquivo.Close()`: Garante que o arquivo será fechado mesmo se ocorrer um erro durante o processamento
  • O `defer` é executado na ordem LIFO (Last In, First Out)
  • Mesmo se houver um retorno antecipado devido a erro, o arquivo será fechado

2. Gerenciamento de Conexões com Banco de Dados

O `defer` é muito útil no gerenciamento de conexões com banco de dados, garantindo que as conexões sejam fechadas adequadamente. Para rodar o código a seguir, vamos criar um banco mysql local, ou se quiser, pode criar o banco mysql online: https://freedb.tech/. Crie o banco com o seguinte comando SQL:

CREATE DATABASE defer_example;
Selecione o banco criado:

USE defer_example;
Agora crie a tabela usuarios.

CREATE TABLE usuarios (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(100),
    email VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Para ver a estrutura da tabela.

DESCRIBE usuarios;
Para testar se está funcionando.

INSERT INTO usuarios (nome, email) VALUES ('Teste', 'teste@exemplo.com');
Consulte.

SELECT * FROM usuarios;

Código do Exemplo 02

45 - defer/database-connection-management/database_connection_management.go

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func realizarConsulta() error {
    // Abre conexão com o banco
    db, err := sql.Open("mysql", "root:sua_senha@/defer_example")
    if err != nil {
        return fmt.Errorf("erro ao conectar ao banco: %v", err)
    }
    defer db.Close()

    // Inicia uma transação
    tx, err := db.Begin()
    if err != nil {
        return fmt.Errorf("erro ao iniciar transação: %v", err)
    }

    // Garante que a transação será finalizada
    defer func() {
        if err != nil {
            fmt.Println("Revertendo transação...")
            tx.Rollback()
            return
        }
        fmt.Println("Commitando transação...")
        tx.Commit()
    }()

    // Insere um usuário
    _, err = tx.Exec(`
        INSERT INTO usuarios (nome, email) 
        VALUES (?, ?)
    `, "João", "joao@exemplo.com")
    if err != nil {
        return fmt.Errorf("erro ao inserir usuário: %v", err)
    }

    // Tenta uma operação que pode falhar
    _, err = tx.Exec(`
        INSERT INTO usuarios (nome, email) 
        VALUES (?, ?)
    `, "Maria", "maria@exemplo.com")
    if err != nil {
        return fmt.Errorf("erro ao inserir segundo usuário: %v", err)
    }

    // Lista os usuários inseridos
    rows, err := tx.Query("SELECT id, nome, email FROM usuarios")
    if err != nil {
        return fmt.Errorf("erro ao consultar usuários: %v", err)
    }
    defer rows.Close()

    fmt.Println("\nUsuários inseridos:")
    for rows.Next() {
        var id int
        var nome, email string
        err := rows.Scan(&id, &nome, &email)
        if err != nil {
            return fmt.Errorf("erro ao ler usuário: %v", err)
        }
        fmt.Printf("ID: %d, Nome: %s, Email: %s\n", id, nome, email)
    }

    return nil
}

func main() {
    if err := realizarConsulta(); err != nil {
        fmt.Printf("Erro: %v\n", err)
        return
    }
    fmt.Println("Operação concluída com sucesso!")
}

Para rodar este exemplo 02

Primeiro defina o nome do módulo como exemplo-defer

go mod init exemplo-defer
Agora o tidy, esse comando abaixo remove dependências desnecessárias e garante que todas as dependências necessárias estão presentes.

go mod tidy
Agora instale o driver MySQL

go get -u github.com/go-sql-driver/mysql
No código do exemplo 02, a parte onde tem a definição da string de conexão com o banco: db, err := sql.Open("mysql", "root:sua_senha@/defer_example"), mude para usar suas credenciais. Só falta executar o código do exemplo 02.

go run database_connection_management.go
Descrição:
  • Múltiplos `defer` são executados na ordem inversa de sua declaração
  • O `defer` com função anônima permite tomar decisões baseadas no estado final da função, a função anônima permite que possamos tomar uma decisão (Rollback ou Commit) baseada no estado da variável err no momento que o defer é executado, não no momento que ele é declarado.
  • Garante que recursos sejam liberados mesmo em caso de erro.

3. Medição de Tempo de Execução

O `defer` também é útil para fins de instrumentação e logging.

45 - defer/execution-time-measurement/execution_time_measurement.go

package main

import (
    "fmt"
    "time"
)

func registrarTempoExecucao(operacao string) func() {
    inicio := time.Now()
    return func() {
        duracao := time.Since(inicio)
        fmt.Printf("Operação %s levou %v para executar\n", operacao, duracao)
    }
}

func operacaoLonga() {
    defer registrarTempoExecucao("operacaoLonga")()

    // Simula uma operação que leva tempo
    fmt.Println("Iniciando operação longa...")
    time.Sleep(2 * time.Second)
    fmt.Println("Operação longa finalizada!")
}

func main() {
    operacaoLonga()
}
Descrição:
  • `defer` com função que retorna outra função
  • Útil para medição de performance e logging
  • Não interfere no fluxo principal do código

Conclusão

Nesta aula, exploramos o uso do `defer` em Go, uma ferramenta poderosa para garantir que recursos sejam liberados adequadamente e que certas operações sejam executadas ao final de uma função. Vimos exemplos práticos de seu uso no gerenciamento de arquivos, conexões com banco de dados e instrumentação de código. O uso adequado do `defer` torna o código mais seguro e confiável, evitando vazamentos de recursos e garantindo que operações de limpeza sejam sempre executadas, mesmo em casos de erros ou panics. Na próxima aula, exploraremos o Recover, um mecanismo que trabalha em conjunto com Defer para recuperar o controle de um programa após um panic. Veremos como essa combinação permite criar aplicações mais resilientes, capazes de se recuperar graciosamente de erros fatais. Até a próxima aula!