Refatoração de código legado em Go: guia passo a passo

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Refatoração de código legado em Go: um guia passo a passo



Trabalhar com código legado em Go é uma parte inevitável da vida do desenvolvedor. Muitas vezes, esse código foi escrito às pressas, sem testes, com variáveis globais e funções monolíticas. No entanto, a refatoração de código legado em Go não é apenas reescrever, mas um processo estratégico que requer cautela e uma abordagem sistêmica. Neste artigo, vamos analisar como melhorar o código existente de forma segura e eficiente, sem quebrar o produto.



Por que a refatoração de código legado em Go é difícil?



O código legado em Go geralmente apresenta problemas característicos:

  • Variáveis globais — dificultam os testes e a execução paralela.
  • Funções monolíticas — funções com centenas de linhas que fazem tudo de uma vez.
  • Ausência de interfaces — forte acoplamento a implementações específicas.
  • Sem testes — refatorar sem testes é como andar em um campo minado.


Mas o Go fornece ferramentas poderosas para a melhoria gradual do código. O principal é avançar em pequenos passos, mantendo a funcionalidade do sistema em cada etapa.



Passo 1: Preparação e testes



Antes de alterar o código, é necessário criar uma "rede de segurança" — os testes. Mesmo que o código não tenha sido testado, você pode escrever testes de caracterização (characterization tests). Eles fixam o comportamento atual do sistema para que você possa garantir que nada quebrou após a refatoração.



package main

import "testing"

func TestCalculateTotal(t *testing.T) { // Suponha que temos uma função legada CalculateTotal result := CalculateTotal(100, 20) expected := 120.0

if result != expected { t.Errorf("CalculateTotal(100, 20) = %f; want %f", result, expected) }}


Execute os testes e verifique se eles passam. Agora você tem uma base para alterações seguras.



Passo 2: Extração de interfaces



Um dos principais problemas do código legado em Go é o forte acoplamento entre componentes. A solução é a extração de interfaces. Isso permite substituir dependências reais por mocks durante os testes, além de facilitar a troca de implementações no futuro.



Vamos considerar um exemplo. Suponha que temos uma função que salva dados diretamente no banco de dados:



package main

import "database/sql"

type User struct { ID int Name string}

func SaveUser(db *sql.DB, user User) error { _, err := db.Exec("INSERT INTO users (name) VALUES ($1)", user.Name) return err}


Problema: não podemos testar esta função sem um banco de dados real. Solução — extrair uma interface:



package main

// UserStorage define o contrato para armazenamento de usuáriostype UserStorage interface { Save(user User) error}

type UserSaver struct { storage UserStorage}

func NewUserSaver(storage UserStorage) *UserSaver { return &UserSaver{storage: storage}}

func (s *UserSaver) SaveUser(user User) error { return s.storage.Save(user)}


Agora podemos criar um mock para testes:



package main

import "testing"

type mockStorage struct{}

func (m *mockStorage) Save(user User) error { return nil // sempre bem-sucedido}

func TestUserSaver_SaveUser(t *testing.T) { saver := NewUserSaver(&mockStorage{}) err := saver.SaveUser(User{ID: 1, Name: "Alice"}) if err != nil { t.Errorf("unexpected error: %v", err) }}


Passo 3: Injeção de dependências



Após a extração de interfaces, o próximo passo é a injeção de dependências (Dependency Injection, DI). Em vez de criar dependências dentro da função (por exemplo, abrir uma conexão com o banco de dados), nós as passamos de fora.



Vamos considerar um código legado típico:



package                

Blogs

Book Recommendations