Em Go, pacotes são a unidade fundamental de organização e reutilização de código. Todo arquivo .go pertence a um pacote, e a estrutura de diretórios reflete a hierarquia de pacotes. Compreender como declarar, importar e controlar a visibilidade de identificadores é essencial para escrever código limpo, modular e de fácil manutenção.

Nesta aula, exploraremos os conceitos de package, visibilidade baseada em letras maiúsculas/minúsculas, a declaração import e as boas práticas de organização de código em Go. Ao final, você será capaz de criar pacotes próprios e utilizá-los em seus projetos.

package

Todo código Go começa com a declaração package nome, que define a qual pacote o arquivo pertence. O pacote main é especial: ele indica que o programa é executável e deve conter uma função main(). Pacotes com outros nomes são bibliotecas ou partes de um programa maior.

A declaração package deve ser a primeira linha do arquivo (apenas comentários podem vir antes). O nome do pacote geralmente é o mesmo nome do diretório onde o arquivo está, mas não é obrigatório – é uma convenção forte. Por exemplo, arquivos no diretório math/ costumam ter package math.

// arquivo: calculadora/soma.go
package calculadora

func Soma(a, b int) int {
    return a + b
}

No exemplo acima, o pacote é calculadora e a função Soma é exportada (visível fora do pacote) porque começa com letra maiúscula.

Visibilidade (maiúscula/minúscula)

Go não possui as palavras-chave public, protected ou private. Em vez disso, a visibilidade de um identificador (função, variável, constante, tipo, struct, interface, etc.) é determinada pela primeira letra do seu nome:

  • Letra maiúscula: o identificador é exportado (público), visível para outros pacotes.
  • Letra minúscula: o identificador é não exportado (privado), visível apenas dentro do mesmo pacote.

Essa regra se aplica a todos os níveis: pacote, struct, método, campo de struct, etc. Por exemplo, em um pacote banco:

package banco

// Exportado
type Conta struct {
    Saldo float64 // exportado (maiúsculo)
    titular string // não exportado (minúsculo)
}

// Exportado
func NovaConta(saldoInicial float64) *Conta {
    return &Conta{Saldo: saldoInicial, titular: "Anônimo"}
}

// Não exportado
func validarSaldo(valor float64) bool {
    return valor >= 0
}

Fora do pacote banco, só podemos acessar Conta, NovaConta e o campo Saldo. O campo titular e a função validarSaldo são inacessíveis.

import

Para usar código de outro pacote, utilizamos a declaração import. Ela pode importar um ou mais pacotes, e cada caminho é uma string entre aspas duplas. O caminho é relativo ao módulo (definido no go.mod).

package main

import (
    "fmt"
    "meuprojeto/calculadora"
)

func main() {
    resultado := calculadora.Soma(3, 4)
    fmt.Println("Resultado:", resultado)
}

Se o nome do pacote importado conflitar com outro, podemos usar um alias:

import (
    cr "crypto/rand"
    mr "math/rand"
)

Também é possível importar apenas para efeitos colaterais (inicialização) usando _ como alias:

import _ "image/png" // registra o decodificador PNG

Pacotes da biblioteca padrão não precisam de caminho relativo ao módulo; são referenciados pelo nome simples (ex: "fmt", "net/http").

Organização

Go incentiva uma organização simples: um diretório para cada pacote, e o nome do diretório coincide com o nome do pacote. Dentro de um diretório, múltiplos arquivos .go podem pertencer ao mesmo pacote, e eles compartilham o mesmo namespace (não pode haver conflito de nomes exportados ou não exportados entre arquivos do mesmo pacote).

Exemplo de estrutura de projeto:

meuprojeto/
├── go.mod
├── main.go
└── calculadora/
    ├── soma.go
    └── subtracao.go

main.go pertence ao pacote main; soma.go e subtracao.go pertencem ao pacote calculadora (declarado em cada arquivo como package calculadora).

Boas práticas:

  • Evite pacotes com muitos arquivos; se um pacote ficar grande, considere dividi-lo em subpacotes.
  • Nomeie pacotes com nomes curtos, em minúsculas, sem underscores ou mixedCaps (ex: http, json, ioutil).
  • Não crie pacotes com nomes genéricos como utils ou common; prefira nomes descritivos.

Boas práticas adicionais

Sempre use go fmt para formatar o código automaticamente. Evite dependências cíclicas entre pacotes. Utilize o comando go vet para verificar problemas comuns. Para testes, crie arquivos *_test.go no mesmo diretório do pacote.

Referências

Exercícios

  1. Crie um pacote chamado conversoes com funções exportadas para converter Celsius para Fahrenheit e vice-versa. Escreva um programa main que use essas funções.

    ✓ Resposta:
    // conversoes/conversoes.go
    package conversoes
    
    func CelsiusParaFahrenheit(c float64) float64 {
        return c*9/5 + 32
    }
    
    func FahrenheitParaCelsius(f float64) float64 {
        return (f - 32) * 5 / 9
    }
    
    // main.go
    package main
    
    import (
        "fmt"
        "meuprojeto/conversoes"
    )
    
    func main() {
        fmt.Println("30°C em Fahrenheit:", conversoes.CelsiusParaFahrenheit(30))
        fmt.Println("86°F em Celsius:", conversoes.FahrenheitParaCelsius(86))
    }
    
  2. No pacote conversoes do exercício anterior, adicione uma função não exportada arredondar que arredonda um float para duas casas decimais. Use-a dentro das funções exportadas.

    ✓ Resposta:
    package conversoes
    
    import "math"
    
    func arredondar(valor float64) float64 {
        return math.Round(valor*100) / 100
    }
    
    func CelsiusParaFahrenheit(c float64) float64 {
        return arredondar(c*9/5 + 32)
    }
    
    func FahrenheitParaCelsius(f float64) float64 {
        return arredondar((f - 32) * 5 / 9)
    }
    
  3. Explique por que o seguinte código não compila e corrija-o: package main; import "fmt"; func main() { fmt.Println(MinhaFuncao()) }; func MinhaFuncao() int { return 42 }

    ✓ Resposta: O código compila normalmente, pois a função MinhaFuncao começa com maiúscula e está no mesmo pacote main. Não há erro. Se houver erro, pode ser devido a falta de chaves ou ponto e vírgula. O código está correto.
  4. Crie um pacote validacao com uma função exportada EmailValido(email string) bool que verifica se o email contém '@' e '.'. Em seguida, importe e use no main.

    ✓ Resposta:
    // validacao/validacao.go
    package validacao
    
    import "strings"
    
    func EmailValido(email string) bool {
        return strings.Contains(email, "@") && strings.Contains(email, ".")
    }
    
    // main.go
    package main
    
    import (
        "fmt"
        "meuprojeto/validacao"
    )
    
    func main() {
        fmt.Println(validacao.EmailValido("teste@exemplo.com")) // true
        fmt.Println(validacao.EmailValido("invalido"))          // false
    }
    
  5. Reorganize o projeto abaixo para seguir as convenções Go: projeto/ contém main.go (pacote main) e um arquivo utils/helpers.go com package auxiliar. Corrija os nomes de pacote e imports.

    ✓ Resposta: O diretório utils/ deve conter arquivos com package utils (mesmo nome do diretório). Renomeie o pacote para utils e ajuste o import em main.go para "projeto/utils". Exemplo:
    // utils/helpers.go
    package utils
    
    func Ajuda() string {
        return "Ajuda!"
    }
    
    // main.go
    package main
    
    import (
        "fmt"
        "projeto/utils"
    )
    
    func main() {
        fmt.Println(utils.Ajuda())
    }