Go 的包组织、可见性规则与模块系统深入


一、包(Package)基础

1.1 什么是包

// 每个 Go 文件都必须声明自己属于哪个包
package main  // main 包是可执行程序的入口

// 同一目录下的所有 .go 文件必须属于同一个包
// 例外:_test.go 文件可以声明 _test 包(用于黑盒测试)

1.2 包的命名规则

// 包名:小写单词,不要下划线或驼峰
package mypackage     // ✅
package my_package    // ❌
package MyPackage     // ❌

// 目录名 = 包名
// src/
// └── utils/          // 目录名 utils
//     ├── string.go   // package utils
//     └── math.go     // package utils

1.3 main 包的特殊性

// main 包 + main() 函数 = 可执行程序
package main

import "fmt"

func main() {
    fmt.Println("Hello!")
}

二、导入(Import)

2.1 基本导入

import "fmt"               // 标准库
import "github.com/gin-gonic/gin"  // 第三方包

// 批量导入
import (
    "fmt"
    "os"
    "strings"
)

2.2 导入别名

// 解决包名冲突
import (
    "fmt"
    myfmt "github.com/other/fmt"  // 别名
)

2.3 点导入与下划线导入

// 点导入:将包的导出标识符引入当前命名空间
import . "fmt"
Println("Hello")  // 不需要 fmt.Println

// ⚠️ 不推荐使用——降低代码可读性

// 下划线导入:只执行包的 init() 函数
import _ "github.com/go-sql-driver/mysql"  // 注册 MySQL 驱动
// 不使用包中的任何标识符,只是让 init() 被调用

三、可见性规则(大小写)

3.1 导出规则

Go 没有 public/private 关键字,通过首字母大小写控制可见性:

package mypackage

// 首字母大写 = 导出(Exported)= 对外可见
type User struct {
    Name  string   // 导出字段
    Email string   // 导出字段
    age   int      // 未导出字段(包外不可见)
}

func (u *User) GetName() string {  // 导出方法
    return u.Name
}

func (u *User) validate() error {  // 未导出方法(包外不可访问)
    if u.Name == "" {
        return errors.New("name is required")
    }
    return nil
}

// 未导出的类型
type internalConfig struct {  // 小写开头,包外不可见
    debug bool
}

3.2 可见性速查

标识符首字母可见范围
Name大写导出 — 任何导入此包的代码都可访问
age小写未导出 — 仅包内代码可访问
GetUser大写导出 — 可被外部调用
validate小写未导出 — 仅包内调用

四、包级变量与 init()

4.1 包级变量

package mypackage

// 包级变量:在包内任何地方声明,在 main() 或 import 之前初始化
var config = loadConfig()  // 程序启动时执行

// 常量
const Version = "1.0.0"

// init 函数级别的包级变量
var logger = log.New(os.Stderr, "[mypkg] ", log.LstdFlags)

4.2 init() 函数

package mypackage

// init() 在包被导入时自动执行
// 每个包可以有多个 init(),同一个文件内也可以有多个
func init() {
    fmt.Println("mypackage initialized")
    // 常用于:注册驱动、初始化连接池、验证配置等
}

// init() 的执行顺序
// 1. 首先导入依赖包(递归)
// 2. 依赖包的 init() 先执行
// 3. 最后执行当前包的 init()

4.3 执行顺序

// 完整的程序启动顺序:
// 1. main 包导入的所有包(递归)
// 2. 所有依赖包的包级变量初始化
// 3. 所有依赖包的 init() 执行
// 4. main 包的包级变量初始化
// 5. main 包的 init() 执行
// 6. main() 函数执行

五、Go Modules 深入

5.1 模块路径规范

// go.mod 中声明的模块路径
module github.com/starer-code/myproject

// 规则:
// - 使用代码托管平台的完整路径
// - GitHub: github.com/user/repo
// - GitLab: gitlab.com/user/repo
// - 私有: github.com/company/internal-tool

5.2 内部包(internal)

// Go 强制执行的可见性规则:
// internal/ 目录下的包只能被其父目录及父目录的子目录导入

// 目录结构
myproject/
├── go.mod
├── cmd/
│   └── server/
│       └── main.go        // 可以 import "myproject/internal/auth"
├── internal/
│   ├── auth/              // 只能被 myproject 下的代码导入
│   │   └── auth.go
│   └── db/                // 外部项目不能 import "myproject/internal/db"
│       └── db.go
└── pkg/
    └── utils/             // 外部可以导入
        └── utils.go

5.3 推荐的项目结构

myproject/
├── go.mod
├── go.sum
├── README.md
├── cmd/                    # 可执行文件入口
│   └── server/
│       └── main.go
├── internal/               # 私有代码(不可被外部导入)
│   ├── auth/
│   ├── model/
│   └── service/
├── pkg/                    # 可被外部导入的公共代码
│   └── utils/
├── api/                    # API 定义(protobuf、OpenAPI 等)
├── configs/                # 配置文件
├── scripts/                # 构建/部署脚本
└── test/                   # 集成测试

六、循环依赖

6.1 Go 禁止循环导入

// ❌ 编译错误:import cycle not allowed
// package A imports package B
// package B imports package A

// 解决方案:
// 1. 提取公共代码到第三个包 C
// 2. 使用接口解耦(在消费方定义接口)
// 3. 重新设计包结构

6.2 避免循环依赖的技巧

// 技巧1:接口解耦
// package auth 需要调用 package user 的方法
// 在 auth 包中定义接口,在 user 包中实现
// auth/interface.go
type UserFinder interface {
    FindByID(id int) (*User, error)
}

// user/user.go(正常导入 auth 不会有循环)
type Service struct {
    finder auth.UserFinder  // 依赖接口,不依赖具体包
}

// 技巧2:提取公共类型到独立包
// package a 和 package b 都需要 User 结构体
// 提取到 package model 中

七、本节要点速查

概念要点
包名小写单词,目录名 = 包名
可见性大写开头 = 导出,小写开头 = 未导出
main 包可执行程序入口,必须有 main()
init()包导入时自动执行,可多个,递归调用
下划线导入import _ "pkg" 仅执行 init()
internal强制私有,只能被父目录及子目录导入
循环依赖禁止,用接口解耦或提取公共包
项目结构cmd/ + internal/ + pkg/ 是常见布局
模块路径使用代码托管平台的完整路径

上一篇:07-接口与多态 下一篇:09-并发编程基础