Golang 大小写可见性规范

Golang 大小写可见性规范

一、核心规则

在 Go 中,首字母大小写决定标识符能否被包外访问,这是语言内置的可见性规则。

首字母 含义 可见范围
大写 导出(exported / public) 包外可访问
小写 未导出(unexported / private) 仅本包内可访问

Go 没有 publicprivateprotected 关键字,全靠首字母控制。

go 复制代码
package user

type User struct {    // ✅ 包外可用:user.User
    ID   int64        // ✅ 包外可访问
    name string       // ❌ 包外不可访问
}

func GetUser() { }     // ✅ 包外可调用
func validate() { }    // ❌ 仅包内可调用

二、适用场景

大小写规则适用于以下所有标识符:

1. 变量

go 复制代码
package config

var AppName = "MyApp"   // 导出,其他包可用 config.AppName
var debug = true        // 未导出,仅本包可用
go 复制代码
import "myproject/config"

fmt.Println(config.AppName)  // ✅
fmt.Println(config.debug)    // ❌ 编译错误

2. 常量

go 复制代码
const MaxRetry = 3        // 导出
const defaultTimeout = 5  // 未导出

3. 函数 / 方法

go 复制代码
func NewUser() *User { }   // 导出,常作为构造函数
func parseConfig() { }     // 未导出,内部辅助函数

func (u *User) GetName() string { }  // 导出方法
func (u *User) setName(s string) { } // 未导出方法

4. 结构体及其字段

go 复制代码
type User struct {
    ID    int64   // 导出字段
    Name  string  // 导出字段
    email string  // 未导出字段
}

其他包只能访问导出字段:

go 复制代码
u := user.User{
    ID:   1,
    Name: "张三",
    // email: "..."  // ❌ 不能访问
}

未导出字段只能通过本包提供的导出方法间接访问:

go 复制代码
// user 包内
func (u *User) Email() string {
    return u.email
}

5. 接口

go 复制代码
type Reader interface {    // 导出接口
    Read(p []byte) (n int, err error)  // 方法也必须导出
}

type validator interface {  // 未导出接口,仅包内用
    validate() error
}

注意:接口要导出,其中的方法名也必须是大写,否则其他包无法实现该接口。

6. 类型别名、自定义类型

go 复制代码
type Status int           // 导出类型
type statusCode int        // 未导出类型

type HTTPClient struct{}   // 导出
type httpClient struct{}   // 未导出

7. 包名(特殊规则)

包名必须小写,不能使用大写:

go 复制代码
package user        // ✅
package User        // ❌ 不规范
package userService // ❌ 不要用驼峰

包是否可被 import,由目录路径决定,而非包名大小写。


三、常见使用场景

1. 对外 API vs 内部实现

go 复制代码
// internal/service/user.go
package service

// 导出:给 handler 层调用
func CreateUser(req CreateUserReq) (*User, error) {
    if err := validate(req); err != nil {
        return nil, err
    }
    return saveUser(req)
}

// 未导出:内部实现细节
func validate(req CreateUserReq) error { ... }
func saveUser(req CreateUserReq) (*User, error) { ... }

设计原则:导出的少而精,未导出的实现细节多。

2. 构造函数:New + 大写类型名

go 复制代码
type Client struct {
    baseURL string
}

// 导出构造函数
func NewClient(url string) *Client {
    return &Client{baseURL: url}
}

其他包使用:client := http.NewClient("https://api.example.com")

3. 错误变量:Err 前缀

go 复制代码
var ErrNotFound = errors.New("not found")
var ErrInvalidInput = errors.New("invalid input")

var errTimeout = errors.New("timeout")  // 内部错误,不导出

4. 接口命名:单方法常用 -er 后缀

go 复制代码
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Closer interface { Close() error }

5. 缩写词:保持一致的大小写

go 复制代码
var userID int      // ✅ ID 大写
var userId int      // ❌ 不规范

type HTTPServer struct{}  // ✅
type HttpServer struct{}  // ❌

var apiURL string   // ✅ URL 全大写

常见缩写词:IDURLHTTPJSONAPISQLUUID


四、封装机制说明

Go 的封装边界是「包」,不是「类型」

语言 封装单位
Java/C++ 类(class)
Go 包(package)

同一包内:成员互相可访问

go 复制代码
package service

type user struct {
    name string
}

func foo() {
    u := user{name: "张三"}  // ✅ 同包可直接写字段
    u.name = "李四"            // ✅ 同包可改
}

跨包:编译器强制限制

go 复制代码
import "myapp/internal/user"

u := user.User{ID: 1}
u.ID = 2           // ✅ 可以
// u.name = "张三"  // ❌ 编译错误:name 未导出
// u.setName("x")   // ❌ 编译错误:方法未导出

name := u.GetName() // ✅ 通过导出方法访问

推荐封装写法:字段小写 + 导出方法

go 复制代码
package user

type User struct {
    id    int64
    name  string
    email string
}

func NewUser(name, email string) *User {
    return &User{name: name, email: email}
}

func (u *User) Name() string  { return u.name }
func (u *User) Email() string { return u.email }

func (u *User) SetEmail(email string) error {
    if !isValidEmail(email) {
        return ErrInvalidEmail
    }
    u.email = email
    return nil
}

只导出接口,不导出实现

go 复制代码
// 导出接口
type Repository interface {
    GetByID(ctx context.Context, id int64) (*User, error)
}

// 实现未导出
type userRepo struct {
    db *sql.DB
}

func NewUserRepo(db *sql.DB) Repository {
    return &userRepo{db: db}
}

五、与其他语言对比

对比项 Java Go
私有字段 private 首字母小写
公有 API public 首字母大写
封装范围
包内访问私有成员 不行 整个包都可以
强制 getter/setter private 约束 靠设计约定
语言 可见性控制方式
Java public / private / protected
Python _ 前缀约定(非强制)
Go 首字母大小写(语言强制)

六、容易忽略的点

1. internal 目录(比大小写更严格)

复制代码
project/
└── internal/
    └── service/   # 只有 project 树内的代码能 import

即使类型是大写导出的,internal 外的包也 无法 import。这是目录级别的封装,与首字母规则叠加使用。

2. 嵌入(embedding)不突破字段可见性

go 复制代码
type Base struct {
    name string  // 未导出
}

type User struct {
    Base
}

// 其他包
u := User{}
// u.name  // ❌ 仍然不能访问

3. JSON 序列化要求字段导出

go 复制代码
type User struct {
    Name string `json:"name"`  // 字段导出才能被 encoding/json 序列化
    age  int    `json:"age"`   // 未导出 → json 包访问不到
}

json 标签只控制 JSON 字段名,不改变 Go 的可见性规则。要序列化,字段本身必须导出(大写)。

4. 接口方法的可见性

go 复制代码
type Writer interface {
    write([]byte) error  // 未导出方法 → 接口实际上只能在本包内使用
}

七、设计建议

应该导出(大写)的场景

  • 库的公开 API
  • 需要被其他包调用的函数、类型
  • 结构体中需要被外部读写的字段
  • 对外暴露的错误:ErrXxx

不应该导出(小写)的场景

  • 内部辅助函数
  • 实现细节类型
  • 不想暴露的字段(密码、token 等)
  • 包内私有常量、变量

实践经验

go 复制代码
// ❌ 过度暴露
type User struct {
    ID       int64
    Name     string
    Password string  // 危险:外部可直接读写
}

// ✅ 推荐写法
type User struct {
    id       int64
    name     string
    password string
}

func (u *User) ID() int64     { return u.id }
func (u *User) Name() string  { return u.name }
// password 不提供 Getter

设计原则

  1. 默认小写,确认需要对外暴露时再改为大写
  2. 敏感字段(密码、token)绝不导出
  3. 业务逻辑放在未导出的方法中
  4. 使用 internal/ 目录防止外部模块随意依赖
go 复制代码
// 默认小写,需要对外再改大写
func process() { }        // 内部
func Process() { }        // 确认需要对外再导出

type userRepo struct{}    // 内部实现
type UserService struct{} // 对外服务

八、快速对照表

标识符 大写(导出) 小写(未导出)
变量 Count / count 包外可读 仅本包
函数 Get() / get() 包外可调用 仅本包
类型 User / user 包外可用 仅本包
结构体字段 Name / name 包外可访问 仅本包
接口方法 Read() / read() 可被外包包实现 仅本包
常量 MaxSize / maxSize 包外可用 仅本包
包名 --- 必须小写

九、常见误解纠正

误解 实际情况
Go 没有封装 有,通过大小写 + 包边界实现
结构体字段都能随便改 仅包内可随意访问;包外只能改导出字段
方法都能随便调用 未导出的方法在包外无法调用
和 Java 一样要全部 private Go 风格是:该导出的导出,不该导出的用小写
导出字段没问题 能用,但等于公开实现细节,需谨慎设计

十、总结

大写 = 对外公开;小写 = 包内私有

  • 大小写规则适用于:变量、常量、函数、方法、类型、结构体字段、接口及接口方法
  • 包名单独要求小写
  • internal/ 目录提供额外的 import 限制
  • Go 的封装以为边界,而非以类型为边界
  • 编译器会强制执行可见性规则,不是可选的编码风格
相关推荐
神奇小汤圆2 小时前
分布式事务没有银弹:从CAP定理到AT与TCC模式的选择指南
后端
YuePeng2 小时前
不写一行接口,让 DBeaver 直连你的指标层——背后只用了一个端口
后端·架构·github
AI科技星3 小时前
曲率‑挠率与 $\boldsymbol{\omega/c}$ 的关系、精算验证及其物理意义
c语言·开发语言·线性代数·算法·决策树·机器学习·ai科技星
鹿角片ljp3 小时前
Java框架篇:Spring + SpringMVC + SpringBoot + MyBatis深度复习
java·开发语言
董员外4 小时前
RAG 系统进化论(七):Multimodal RAG(多模态 RAG),当知识存在于表格、图片和页面中
人工智能·后端·设计模式
用户667675093794 小时前
Java 是如何操作Redis的?从 Spring Data Redis中RedisTemplate 源码分析 ZSet 调用链
后端
凌虚4 小时前
Kubernetes 编年史:从 Borg 到云原生操作系统
后端·程序员·kubernetes
xlxxy_5 小时前
外部系统调用SAP接口遇到的一些报错
开发语言·数据库·sap·abap
八角.。5 小时前
方法参数与Debug按键
java·开发语言·jvm