Administrator
发布于 2025-08-12 / 3 阅读
0
0

Golang开发规范

一、项目结构规范

遵循 Go 模块(Module)标准,使用 micro工具的 micro new命令生成项目结构, 建议采用以下分层结构:

project/
├── api/                    # API 相关(admin/rpc 接口定义)
│   ├── admin/              # 管理端接口
│       ├── handler/        # 业务处理逻辑
│       └── proto/          # protobuff定义
│   └── rpc/                # 服务间rpc接口
│       ├── handler/        # 业务处理逻辑
│       └── proto/          # protobuff定义
├── assets/                 # 项目所需的静态资源
│       ├── approve_desc/   # 单据审批描述定义
│       ├── cancel/         # 单据依赖关系定义
│       └── codelike/       # 编码规则定义
├── bill/                   # 业务单据定义
│   ├── contract/           # 合同相关业务单据,也可根据业务模式进行分类,如:fti,cst
│   ├── fund/               # 资金相关业务单据
│   └── stock/              # 库存相关业务单据
├── biz/                    # 业务相关的公共实现
├── build/                  # 编译相关
│   ├── bin/                # 编译生成的可执行文件与启动/停用脚本
│   └── conf/               # 配置文件
├── errors/                 # 自定义错误定义
├── model/                  # 业务表生成的定义文件
├── tests/                  # 集成测试(区分单元测试和集成测试)
├── vm/                     # 编译期自动生成的文件
├── docs/                   # 文档(API 文档、设计文档、部署说明)
├── go.mod                  # 模块依赖文件
├── go.sum                  # 依赖哈希文件
├── .service.yml            # 服务相关的配置,如服务ID、服务名、服务版本、服务使用的tables
└── README.md               # 项目说明

规范要求:

  1. 内部包与公开包分离internal 目录下的包不可被外部模块引用,防止接口不稳定导致的依赖问题。

  2. 单一职责原则:每个目录 / 文件仅处理单一功能(如 handlers 处理 HTTP 请求,service 处理业务逻辑)。

  3. 配置管理:配置文件支持环境变量覆盖,通过 viperconfig 包加载,避免硬编码。

二、命名规范

1. 包名

  • 使用小写单词,多个单词使用下划线,无驼峰(如 databaselogger,避免 dbUtils)。

  • 包名与目录名一致,禁止使用复数(如目录 models 对应包名 model)。

  • 避免模糊命名(如 utilcommon,建议更具体如 timeutilstringutils)。

2. 标识符(变量、函数、结构体)

  • 导出规则:首字母大写表示可导出(公共接口),小写表示包内私有。

  • 正确:User(结构体)、GetUser()(函数)、MaxSize(常量)。

    • 错误:user(公共结构体未导出)、getUser()(公共函数未导出)。

  • 变量名:使用有意义的全称(如 userID 而非 uId),布尔变量以 Is/Has/Can 开头(如 isActivehasPermission)。

  • 函数名:动词或动词短语(如 CreateUserValidateInput),返回错误的函数以 Err 结尾(如 GetUserErr)。

  • 结构体 / 接口名:名词或名词短语(如 UserRepositoryCacheInterface)。

3. 常量与枚举

  • 常量全大写,单词间下划线分隔(如 MAX_CONNECTIONS = 100)。

  • 枚举使用 iota 定义,搭配 String() 方法(示例如下):

    type Status int
    ​
    const (
        StatusPending Status = iota
        StatusActive
        StatusInactive
        StatusDeleted
    )
    ​
    func (s Status) String() string {
        return [...]string{"Pending", "Active", "Inactive", "Deleted"}[s]
    }

三、编码规范

1. 格式与风格

  • 代码格式化:强制使用 go fmt 或 IDE 自动格式化,禁止手动调整缩进(缩进为 4 个空格,Go 工具会自动处理)。

  • 导入顺序

    :按标准库、第三方库、本地包分组,每组内按字母顺序排列,组间空一行:

    import (
        "fmt"
        "net/http"
    ​
        "github.com/gin-gonic/gin"
    ​
        "project/internal/model"
        "project/pkg/utils"
    )
  • 行长度

    :建议不超过 120 字符,长参数列表换行时保持缩进对齐:

    result, err := someFunction(
        param1,
        param2,
        param3,
    )

2. 控制结构

  • 条件语句 : 避免复杂条件,拆分为多个 if 语句;if 后紧跟小括号,无空格:

    if err != nil { ... } // 正确
    if (err != nil) { ... } // 错误(多余括号)
  • 循环: 仅用 for 循环;range 遍历切片 / 映射时,避免忽略索引或值(用 _ 占位):

    for i, user := range users { ... } // 正确
    for _, user := range users { ... } // 正确(忽略索引)
    for i := range users { ... } // 正确(仅用索引)
  • 错误优先返回: 函数返回值优先处理错误,保持代码扁平:

    func getUser(id int) (User, error) {
        if id <= 0 {
            return User{}, fmt.Errorf("invalid user ID: %d", id)
        }
        // 后续逻辑
    }

3. 函数与接口

  • 函数参数: 参数超过 3 个时,使用结构体传参(提高可读性):

    // 推荐
    func sendEmail(ctx context.Context, opts EmailOptions) error { ... }
    ​
    type EmailOptions struct {
        To      string
        Subject string
        Body    string
    }
  • 接口设计: 接口名以 er 结尾(如 ReaderWriter),单个方法的接口使用 func 类型别名(函数式接口):

    type Validator func(data interface{}) error // 单个方法接口,替代传统接口定义
  • defer 语句:用于资源释放(如文件、数据库连接),避免滥用(过多 defer 可能影响性能和可读性)。

四、错误处理规范

1. 基础规则

  • 禁止忽略错误,必须显式处理(使用 //nolint 注释例外,但需说明原因):

    // 错误:忽略错误
    result, _ := someFunction()
    ​
    // 正确:处理错误
    result, err := someFunction()
    if err != nil {
        return fmt.Errorf("process failed: %w", err) // 使用 %w 包裹错误
    }
  • 使用 fmt.Errorf + %w 实现错误链,便于追踪根本原因:

    return fmt.Errorf("database query failed: %w", err)

2. 自定义错误

  • 业务错误定义为实现

    Error() string

    方法的结构体,支持附加元数据:

    type BusinessError struct {
        Code    int    // 错误码(如 400、500)
        Message string // 错误信息
        Err     error  // 原始错误(可选)
    }
    ​
    func (e *BusinessError) Error() string {
        if e.Err != nil {
            return fmt.Sprintf("%d: %s: %v", e.Code, e.Message, e.Err)
        }
        return fmt.Sprintf("%d: %s", e.Code, e.Message)
    }

3. 日志规范

  • 区分日志级别(debuginfowarnerrorfatal),使用服务的 VM包输出日志:

    vm.Info("user logged in", zap.Int("user_id", user.ID)) // 结构化日志示例
  • 错误日志必须包含上下文信息(如请求 ID、用户 ID、时间戳),便于排查。

五、测试规范

1. 测试文件命名

  • 测试文件以 _test.go 结尾,位于被测试文件同目录或 tests/ 目录(集成测试)。

  • 测试函数命名为 TestXXX(导出函数)或 testXXX(非导出函数,包内测试)。

2. 单元测试

  • 使用表驱动测试(Table-Driven Tests)覆盖正常、边界、异常场景:

    func TestAdd(t *testing.T) {
        tests := []struct {
            name    string
            a, b    int
            want    int
            wantErr bool
        }{
            {"positive numbers", 2, 3, 5, false},
            {"negative numbers", -2, 3, 1, false},
            {"error case", 0, 0, 0, true}, // 示例错误场景
        }
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                got, err := Add(tt.a, tt.b)
                if (err != nil) != tt.wantErr {
                    t.Errorf("Add() error = %v, wantErr %v", err, tt.wantErr)
                    return
                }
                if got != tt.want {
                    t.Errorf("Add() = %v, want %v", got, tt.want)
                }
            })
        }
    }
  • 覆盖率要求:核心业务代码覆盖率 ≥ 80%,通过 go test -coverprofile=cover.out 检查。

3. 集成测试

  • 集成测试需启动真实服务(如数据库、HTTP 服务器),使用测试数据库(名称后缀加 _test)。

  • 使用 testing 包的 TTB 接口,避免依赖外部测试框架。

4. 基准测试

  • 性能敏感函数需添加基准测试(

    BenchmarkXXX

    ),关注内存分配和耗时:

    func BenchmarkAdd(b *testing.B) {
        for i := 0; i < b.N; i++ {
            Add(1, 2)
        }
    }

六、依赖管理与版本控制

1. Go Module 规范

  • 使用 go mod 管理依赖,禁止手动修改 go.mod,通过 go get/go mod tidy 维护。

  • 依赖版本固定,提交 go.modgo.sum 到版本控制。

  • 避免循环依赖:通过接口隔离、依赖倒置原则解耦模块。

2. 第三方库选择

  • 优先使用内部已筛选过的三方库,不能满足业务的可选择社区主流库(如 gin 用于 HTTP,gorm 用于 ORM),避免维护成本高的小众库。

  • 定期更新依赖(每月一次),使用 go list -u 检查可用更新,通过 go get -u 升级。

  • 敏感操作库(如加密、认证)需经过安全审计(如 golang.org/x/crypto 而非自定义实现)。

七、代码审查与协作

1. 审查要点

  1. 设计合理性:是否符合单一职责、开闭原则,接口是否稳定。

  2. 错误处理:是否遗漏错误,错误链是否正确构建,日志是否足够。

  3. 测试覆盖:单元测试是否覆盖所有分支,集成测试是否验证端到端流程。

  4. 性能与安全:是否存在内存泄漏、竞态条件,敏感数据是否加密存储。

  5. 命名与注释:标识符是否清晰,复杂逻辑是否有注释(见下方注释规范)。

2. 分支策略

  • 使用 master 作为生产分支,dev 作为开发分支。

  • 功能分支命名:feature-v3.0.0

  • 临时分支命名:dev-temp250302,为临时处理需求的分支。

  • 禁止直接提交到 master 分支,必须通过代码审查。

八、注释规范

1. 包注释

每个包的 doc.go 或包内首个文件添加注释,说明包的功能、设计原则:

// user 包提供用户管理核心逻辑,包括用户创建、查询、更新和删除功能。
// 所有接口遵循错误优先返回原则,业务错误通过 BusinessError 结构体传递。
package user

2. 接口与结构体注释

导出的接口 / 结构体必须添加注释,说明用途和字段含义:

// User 表示系统中的用户实体。
// 字段说明:
// - ID: 用户唯一标识符(自增整数)
// - Name: 用户名(最大长度 50 字符)
// - Email: 注册邮箱(唯一索引)
type User struct {
    ID    int64
    Name  string
    Email string
}

3. 函数注释

导出函数必须添加注释,说明功能、参数、返回值、错误场景:

// CreateUser 创建新用户,用户名和邮箱需唯一。
// 参数:
// - ctx: 上下文(用于传递请求范围数据和取消信号)
// - req: 创建用户请求,包含用户名、邮箱、密码
// 返回:
// - user: 创建成功的用户实体
// - err: 错误信息,可能为 ErrUserExists(用户名或邮箱已存在)、ErrInvalidInput(参数校验失败)
func CreateUser(ctx context.Context, req CreateUserRequest) (User, error) { ... }

4. 复杂逻辑注释

非导出函数或复杂逻辑块添加注释,解释算法、设计决策或注意事项:

// 此处使用双重检查锁定(Double-Check Locking)实现单例模式,
// 需注意并发安全(通过 sync.Once 保证初始化仅执行一次)。
var once sync.Once
var instance *Singleton

九、最佳实践与禁忌

1. 并发安全

  • 使用 sync.Mutex/sync.RWMutex 保护共享资源,避免竞态条件(通过 go test -race 检测)。

  • 限制 Goroutine 数量,使用 sync.WaitGroup 等待所有 Goroutine 完成,避免泄漏。

2. 性能优化

  • 避免频繁内存分配:使用 sync.Pool 重用对象,预分配切片容量(make([]T, 0, capacity))。

  • 合理使用 interface{}:类型断言和反射有性能开销,优先使用具体类型。

  • 性能敏感代码使用 pprof 分析(CPU / 内存 / 阻塞),避免过早优化。

3. 避免常见陷阱

  • 切片陷阱:注意切片共享底层数组,修改可能影响其他变量。

  • 空指针与零值:结构体指针需初始化(避免 nil 解引用),合理利用 Go 的零值初始化。

  • import 副作用:禁止无意义的 import "package"(仅为触发 init() 函数),改用显式初始化函数。

十、工具链与 IDE 配置

1. 必备工具

  • 格式化go fmt(自动格式化)、goimports(自动整理导入)。

  • 静态检查go vet(类型检查)、golangci-lint(集成多个 linter,如 govetgocycloineffassign)。

  • 调试与性能delve(调试器)、pprof(性能分析)。

  • 依赖管理go mod(官方工具)、go install(安装工具)。

2. IDE 配置

  • VS Code:安装 Go 扩展(gopls),启用自动格式化、代码补全、错误提示。

  • GoLand:默认配置符合 Go 规范,建议启用代码审查插件(如 SonarLint)。

3. CI/CD 流程

  • 每次提交触发:golangci-lint run(静态检查)、go test -race ./...(竞态检测)、单元测试。

  • 合并到 main 分支前触发:集成测试、构建二进制文件、生成覆盖率报告。

  • 发布时:使用 go build -ldflags="-s -w" 生成无调试信息的二进制,确保版本号正确(-X main.version=1.0.0)。


评论