Nomeando pacotes utilitários


Um pacote utilitário é aquele que reúne funções, tipos e estruturas “genéricas” ou “de uso comum”. São recursos que, em teoria, podem ser aproveitados por diferentes partes de um projeto.

Exemplo de funções típicas:

func Contains(slice []string, s string) bool
func Min(a, b int) int
func Max(a, b int) int

Mas surge a pergunta: como dar nome a esses pacotes de forma significativa?

Nomes como utils, common, shared ou base não são boas práticas. Eles carregam pouco significado e não oferecem nenhum insight sobre o que o pacote realmente provê.

Em vez de criar um grande pacote utilitário, a recomendação é dividir em pacotes menores, com nomes expressivos e coesos.

💡 Dica: nomeie o pacote pelo que ele fornece, e não apenas pelo que ele contém. Isso aumenta a clareza e a expressividade do código.

No exemplo abaixo, o pacote utils concentra funções sem relação direta entre si. O problema é que, ao crescer, esse pacote se torna difícil de manter e confuso de usar.


Estrutura inicial

myapp/
├── main.go
└── utils/
    └── utils.go
package utils

func Contains(slice []int, value int) bool {
    for _, v := range slice {
        if v == value {
            return true
        }
    }
    return false
}

type StringSet map[string]struct{}

func NewStringSet() StringSet {
    return make(StringSet)
}

func (s StringSet) Add(item string) {
    s[item] = struct{}{}
}

func (s StringSet) Has(item string) bool {
    _, exists := s[item]
    return exists
}

Uso no main.go.

package main

import (
    "fmt"
    "myapp/utils"
)

func main() {
    nums := []int{1, 2, 3}
    fmt.Println(utils.Contains(nums, 2)) // true

    s := utils.NewStringSet()
    s.Add("go")
    fmt.Println(s.Has("go")) // true
}


Estrutura reorganizada

Ao dividir em pacotes menores e mais específicos, temos:

myapp/
├── main.go
├── sliceutil/
│ └── contains.go
└── set/
└── stringset.go
package sliceutil

func ContainsInt(slice []int, value int) bool {
    for _, v := range slice {
        if v == value {
            return true
        }
    }
    return false
}


package stringset

func New(...string) map[string]struct{} { ... }
func Sort(map[string]struct{}) []string { ... }

Uso no main.go.

package main

import (
    "fmt"
    "myapp/sliceutil"
    "myapp/set"
)

func main() {
    nums := []int{1, 2, 3}
    fmt.Println(sliceutil.ContainsInt(nums, 2))

    s := stringset.New()
    stringset.Sort(s)
}


Benefícios da reorganização

  • Maior clareza: a chamada das funções fica mais explícita.
  • Melhor leitura: o código revela sua intenção de forma imediata.
  • Alta coesão: cada pacote agrupa funções relacionadas, evitando o “pacote monstro” difícil de manter.

É verdade que os novos pacotes são pequenos, com poucos arquivos e funções. À primeira vista, isso pode parecer excesso de fragmentação. No entanto, se cada pacote tem alta coesão e seus objetos não pertencem a outro lugar específico, essa organização é não apenas aceitável, mas recomendada.


👉 Em resumo: evite pacotes utilitários genéricos. Prefira nomes expressivos e pacotes coesos. Essa prática melhora a manutenção, a legibilidade e a escalabilidade do seu projeto.


Referência: HARSANYI, Teiva. 100 Go mistakes and how to avoid them. Shelter Island: Manning, 2022.