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!