Princípios SOLID em Go: teoria e prática de A a Z
SOLID é um acrônimo de cinco princípios fundamentais de design orientado a objetos, formulados por Robert Martin (Uncle Bob). Apesar de Go não ser uma linguagem clássica orientada a objetos (não possui classes e herança no sentido tradicional), esses princípios são aplicáveis e criticamente importantes para criar código de qualidade, sustentável e escalável. Neste artigo, vamos analisar cada princípio SOLID com exemplos da prática real em Go.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
Essência: Cada módulo, estrutura ou função deve ter apenas um motivo para mudar. Em outras palavras, eles devem ser responsáveis por uma tarefa claramente definida.
Problema: Frequentemente, os desenvolvedores colocam toda a lógica em uma única estrutura: validação, salvamento no banco de dados, envio de e-mail, formatação de resposta. Isso leva a "objetos divinos" (God Objects), que são difíceis de testar e modificar.
Solução em Go: Divida a responsabilidade entre diferentes estruturas e pacotes.
// ❌ Ruim: Uma estrutura faz tudotype UserService struct { db *sql.DB mailer *Mailer}
func (s *UserService) CreateUser(name, email string) error { // 1. Validação if !strings.Contains(email, "@") { return errors.New("e-mail inválido") } // 2. Salvamento no BD _, err := s.db.Exec("INSERT INTO users (name, email) VALUES ($1, $2)", name, email) if err != nil { return err } // 3. Envio de e-mail de boas-vindas return s.mailer.Send(email, "Bem-vindo!", "Obrigado por se registrar")}
// ✅ Bom: Separação de responsabilidadestype UserValidator struct{}func (v *UserValidator) Validate(email string) error { if !strings.Contains(email, "@") { return errors.New("e-mail inválido") } return nil}
type UserRepository struct { db *sql.DB}func (r *UserRepository) Save(name, email string) error { _, err := r.db.Exec("INSERT INTO users (name, email) VALUES ($1, $2)", name, email) return err}
type WelcomeMailer struct { mailer *Mailer}func (m *WelcomeMailer) Send(email string) error { return m.mailer.Send(email, "Bem-vindo!", "Obrigado por se registrar")}
type UserService struct { validator *UserValidator repo *UserRepository mailer *WelcomeMailer}
func (s *UserService) CreateUser(name, email string) error { if err := s.validator.Validate(email); err != nil { return err } if err := s.repo.Save(name, email); err != nil { return err } return s.mailer.Send(email)}Dica prática: Se você não consegue descrever brevemente o que sua função ou método faz sem usar a conjunção "e", provavelmente o SRP foi violado.
2. Open-Closed Principle (OCP) — Princípio do Aberto/Fechado
Essência: Entidades de software (estruturas, funções, pacotes) devem estar abertas para extensão, mas fechadas para modificação. Isso significa que você deve ser capaz de adicionar novos comportamentos sem alterar o código existente.
Solução em Go: Use interfaces para abstrair comportamentos.
// ❌ Ruim: Ao adicionar um novo tipo de pagamento, é necessário alterar a função ProcessPaymenttype PaymentProcessor struct{}
func (p *PaymentProcessor) Process(paymentType string, amount float64) { if paymentType == "credit_card" { // lógica para cartão de crédito } else if paymentType == "paypal" { // lógica para PayPal } // Ao adicionar "crypto" — é necessário adicionar um novo else if}
// ✅ Bom: A interface permite estender a funcionalidade sem alterar o códigotype PaymentMethod interface { Pay(amount float64) error}
type CreditCard struct{}func