A linguagem Go é amplamente conhecida por sua simplicidade e pragmatismo. O design da linguagem sempre incentivou a criação de código claro e direto, evitando abstrações desnecessárias.
Com a introdução de generics no Go 1.18, desenvolvedores agora têm mais uma ferramenta para criar abstrações poderosas e reutilizáveis. Além disso, as interfaces já desempenham um papel fundamental na criação de designs flexíveis e desacoplados.
No entanto, é preciso cautela: abstrações excessivas podem facilmente adicionar complexidade sem trazer benefícios reais.
Neste post, vamos discutir quando utilizar generics e interfaces em Go e quando é melhor evitá-los.
O que é Generics?
Generics permitem que funções, tipos e estruturas de dados operem em qualquer tipo, sem a necessidade de duplicar código para cada tipo específico. Em vez de escrever a mesma função para diferentes tipos de dados, você pode escrever uma versão genérica que funciona para todos eles.
Por exemplo, uma função que encontra o maior número entre dois inteiros pode ser facilmente adaptada para suportar vários tipos usando generics:
func Max[T comparable](a, b T) T {
if a > b {
return a
}
return b
}
Essa função pode ser usada com qualquer tipo que seja “comparable” — como inteiros, floats e strings.
Se você quiser saber mais sobre generics, leia os posts:
- Quando utilizar generics
- Benchmark: Generics unique vs Unique
- Generics: maps e comparable
- Como utilizar generics em structs
- Trabalhando com generics
- O que é e como usar a nova constraint comparable
Utilizar ou não Generics
O uso de generics pode trazer grandes benefícios quando você tem repetição de código ou quando precisa lidar com diferentes tipos de forma consistente. Em packages que fornecem estruturas de dados reutilizáveis, como listas ou mapas customizados, generics podem ser uma excelente escolha, permitindo que você mantenha a DRY (Don’t Repeat Yourself) e evite erros de duplicação de código.
Porém, generics também podem adicionar uma camada de complexidade desnecessária. Como o próprio Rob Pike, co-criador do Go, mencionou em suas palestras que “Go foi projetado para resolver problemas comuns de forma simples“. Ou seja, você deve usar generics apenas quando eles simplificam o código e reduzem a repetição.
De modo geral, podemos basear a escolha por utilizar generics para:
- Reutilização de Código: Quando você tem funções ou tipos que operam de maneira idêntica em diferentes tipos de dados.
- Bibliotecas Genéricas: Ao escrever bibliotecas que serão usadas por diferentes projetos e necessitam de flexibilidade.
E para evitar sua utilização quando:
- Complexidade Desnecessária: Se o uso de generics torna o código mais difícil de entender.
- Performance: Em alguns casos, generics podem introduzir overhead de performance.
O que são Interfaces?
Interfaces em Go são contratos que descrevem um conjunto de métodos que um tipo deve implementar. Elas permitem que diferentes tipos compartilhem o mesmo comportamento sem estarem relacionados diretamente.
Por exemplo, a interface io.Writer define um único método Write:
type Writer interface {
Write(p []byte) (n int, err error)
}
Qualquer tipo que implemente esse método é considerado um Writer. Isso oferece flexibilidade para desenvolver aplicações desacopladas e modularizadas.
Se você quiser saber mais sobre interfaces, leia os posts:
- A importância de interfaces em arquiteturas de camadas
- Como utilizar “herança” em interfaces
- O que é e como Interface Segregation é aplicada no Go
- Trabalhando com interfaces
Utilizar ou não Interfaces
Interfaces são poderosas para criar abstrações que permitem flexibilidade no código, especialmente quando você deseja trabalhar com diferentes implementações de um comportamento comum. Elas permitem que você escreva código extensível e reutilizável, sem precisar conhecer a implementação específica do tipo.
No entanto, o uso excessivo de interfaces pode obscurecer a intenção do código, tornando-o mais difícil de entender e manter. Rob Pike também alerta para esse perigo, afirmando que “não é necessário criar uma interface para cada coisa” e “Não programe para interfaces até que elas sejam necessárias.”.
Se você está criando interfaces para tipos que não têm múltiplas implementações, é provável que a interface seja redundante. Use-as quando houver uma real necessidade de flexibilidade, como ao lidar com dependências externas ou diferentes implementações.
De modo geral, podemos pensar na utilização das interfaces quando:
- Abstração Necessária: Quando diferentes tipos compartilham comportamento e você quer tratá-los de forma uniforme.
- Polimorfismo: Para permitir que funções aceitem diferentes tipos, desde que eles satisfaçam a interface.
E sempre evitar sua utilização quando:
- Abstração Prematura: Introduzir interfaces antes de elas serem realmente necessárias pode complicar o código.
- Overengineering: Criar interfaces para tipos que não precisam de abstração adicional.
Conclusão
Generics e interfaces são ferramentas poderosas na linguagem Go que, quando usadas corretamente, podem aumentar significativamente a flexibilidade e reutilização do código. No entanto, é essencial usá-las com sabedoria para evitar a introdução de complexidade desnecessária. A chave está em encontrar o equilíbrio entre a simplicidade e a necessidade de abstração.
O uso apropriado de generics permite criar funções e tipos altamente reutilizáveis sem sacrificar a clareza. As interfaces, por sua vez, facilitam a implementação de designs flexíveis e polimórficos. Ao compreender quando e como utilizá-los, você poderá aproveitar todo o potencial que essas ferramentas oferecem, criando aplicações robustas e fáceis de manter.
Como Rob Pike nos lembra, “a simplicidade é uma virtude“, e a clareza do código deve sempre ser uma prioridade.
Até a próxima!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre GO.




