Visibilidade: exportado vs não exportado

1. Conceitos Fundamentais de Visibilidade em Go

Em Go, o sistema de visibilidade é elegantemente simples: a primeira letra do identificador determina tudo. Se começa com maiúscula, o identificador é exportado (público). Se começa com minúscula, é não exportado (privado ao pacote).

package matematica

// Exportado: pode ser usado fora do pacote
func Soma(a, b int) int {
    return a + b
}

// Não exportado: visível apenas dentro do pacote
func subtrai(a, b int) int {
    return a - b
}

Diferente de linguagens como Java ou C++ (que possuem public, private, protected), Go opera apenas com dois níveis: exportado (visível fora do pacote) e não exportado (visível dentro do pacote). Não existe "protected" – um identificador não exportado em um pacote pai não é acessível em subpacotes.

2. Exportação de Identificadores

Funções exportadas

Para criar APIs públicas, basta usar letra maiúscula:

package calculadora

func Somar(a, b float64) float64 {
    return a + b
}

func Dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, erroDivisaoZero()
    }
    return a / b, nil
}

func erroDivisaoZero() error {
    return fmt.Errorf("divisão por zero não permitida")
}

Tipos e structs exportados

Um struct exportado pode ter campos exportados e não exportados:

package usuario

type Usuario struct {
    Nome  string // Exportado
    Email string // Exportado
    senha string // Não exportado
}

func NovoUsuario(nome, email, senha string) *Usuario {
    return &Usuario{
        Nome:  nome,
        Email: email,
        senha: hashSenha(senha),
    }
}

Constantes e variáveis exportadas

package config

const Versao = "2.0.0"        // Exportada
const versaoInterna = "2.0.0-beta" // Não exportada

var ConfiguracaoPadrao = Config{ // Exportada
    Timeout: 30,
}

3. Identificadores Não Exportados e Encapsulamento

Métodos e funções internas

package validador

func validarCPF(cpf string) bool {
    // Lógica interna de validação
    return len(cpf) == 11
}

type Validador struct{}

func (v Validador) ValidarDocumento(doc string) bool {
    return validarCPF(doc) // Usa função interna
}

Campos não exportados em structs

Protegem estado interno contra modificações diretas:

package conta

type ContaBancaria struct {
    titular string
    saldo   float64 // Não exportado
}

func NovaConta(titular string) *ContaBancaria {
    return &ContaBancaria{titular: titular, saldo: 0}
}

func (c *ContaBancaria) Depositar(valor float64) {
    if valor > 0 {
        c.saldo += valor
    }
}

func (c *ContaBancaria) Saldo() float64 {
    return c.saldo
}

4. Interfaces e Visibilidade

Interfaces exportadas com métodos não exportados

package armazenamento

type Armazenador interface {
    Salvar(dados []byte) error
    Carregar(id string) ([]byte, error)
    validarChave(id string) bool // Não exportado, mas faz parte da interface
}

Tipos não exportados satisfazendo interfaces exportadas

package repositorio

type Repositorio interface {
    Buscar(id int) string
}

type repositorioMySQL struct{}

func (r repositorioMySQL) Buscar(id int) string {
    return "dados do MySQL"
}

Padrão de fábrica

package servico

type Servico interface {
    Executar() string
}

type servicoImpl struct{}

func (s servicoImpl) Executar() string {
    return "serviço executado"
}

func NovoServico() Servico {
    return servicoImpl{} // Retorna tipo não exportado
}

5. Pacotes e Hierarquia de Visibilidade

Importação entre pacotes

// pacote principal
package main

import (
    "meupacote/calculadora"
    "meupacote/config"
)

func main() {
    resultado := calculadora.Somar(2, 3) // OK
    // calculadora.subtrai(5, 2)         // ERRO: não exportado
    fmt.Println(config.Versao)           // OK
}

Subpacotes e falta de "protected"

Go não possui visibilidade hierárquica. Um identificador não exportado em pacote/a não é acessível em pacote/a/b:

// pacote/a/helper.go
var helperInterno = "secreto"

// pacote/a/b/helper.go
package b

import "meupacote/a"

func Teste() {
    // a.helperInterno // ERRO: não exportado
}

6. Testes e Visibilidade

Testes no mesmo pacote (caixa branca)

Acessam identificadores não exportados:

package matematica

import "testing"

func TestSubtrai(t *testing.T) {
    resultado := subtrai(10, 3) // Acessa função não exportada
    if resultado != 7 {
        t.Errorf("esperado 7, obtido %d", resultado)
    }
}

Testes externos (caixa preta)

Usam _test no nome do pacote e só acessam exportados:

package matematica_test

import (
    "testing"
    "meupacote/matematica"
)

func TestSoma(t *testing.T) {
    resultado := matematica.Soma(2, 3)
    // matematica.subtrai(2, 3) // ERRO: não exportado
}

Exported testing helpers

package validador

// Exportado para testes
func ValidarCPFParaTeste(cpf string) bool {
    return validarCPF(cpf)
}

7. Padrões e Boas Práticas

Exportar o mínimo necessário

package processador

// Exporte apenas o que for usado externamente
type Processador struct{}

func (p Processador) Processar(dados string) string {
    return p.processarInterno(dados)
}

func (p Processador) processarInterno(dados string) string {
    return "processado: " + dados
}

Renomeação em imports e visibilidade

import (
    calc "meupacote/calculadora"
)

func main() {
    calc.Somar(1, 2) // OK
}

Documentação de identificadores exportados

// Soma retorna a soma de dois inteiros.
// Exemplo de uso:
//
//     resultado := Soma(3, 4)
//     fmt.Println(resultado) // 7
func Soma(a, b int) int {
    return a + b
}

Execute go doc para gerar documentação automaticamente.

8. Casos Especiais e Armadilhas Comuns

Erro comum: campo não exportado de struct exportado

package main

import "meupacote/usuario"

func main() {
    u := usuario.NovoUsuario("João", "joao@email.com", "123")
    fmt.Println(u.Nome)  // OK
    // fmt.Println(u.senha) // ERRO: campo não exportado
}

Exportação acidental

func processarDados() {
    var DadosImportantes string // Maiúscula em variável local
    _ = DadosImportantes
}

Reflexão (reflect) e visibilidade

package main

import (
    "reflect"
    "meupacote/conta"
)

func main() {
    c := conta.NovaConta("João")
    v := reflect.ValueOf(c)
    campo := v.Elem().FieldByName("saldo")
    // campo.IsValid() // true, mas não pode ser modificado
    // campo.SetFloat(100) // PANIC: campo não exportado
}

Reflexão permite ler campos não exportados, mas não modificá-los. Evite contornar a visibilidade – isso quebra o encapsulamento e pode causar problemas de manutenção.


Resumo

O sistema de visibilidade em Go é simples, porém poderoso. A regra da primeira letra maiúscula/minúscula determina tudo, incentivando APIs limpas e encapsulamento eficaz. Lembre-se:

  • Exporte o mínimo necessário para manter o contrato público enxuto
  • Use tipos não exportados para esconder detalhes de implementação
  • Prefira interfaces exportadas com implementações concretas não exportadas
  • Respeite a visibilidade mesmo em testes – use caixa preta quando possível

Com essas práticas, seu código Go será mais modular, testável e fácil de manter.

Referências