Go 编码规范指南

1. 前言

本规范基于 Google Go Style Guide 和业界最佳实践,旨在建立统一的编码风格,提高代码可读性、可维护性和团队协作效率。

规范等级说明

  • 必须(MUST): 强制要求,必须遵守

  • 推荐(SHOULD): 建议遵守,特殊情况可例外

  • 可选(MAY): 可参考使用


2. 代码格式化

2.1 【必须】使用 gofmt

所有代码必须使用 gofmtgoimports 格式化。

2.2 【推荐】行长度

  • 建议单行不超过 120 个字符

  • 超长行合理换行,保持可读性

例外情况:

  • import 语句

  • 工具生成的代码

  • struct tag

  • 长字符串字面量

2.3 【必须】空格和运算符

// ✅ 正确:运算符前后留空格
result := a + b * c
array[index] = value

// ✅ 正确:函数调用和数组索引紧凑
fmt.Printf("value: %d", arr[i])

// ❌ 错误:缺少空格
result:=a+b*c

3. 导入规范

3.1 【必须】使用 goimports

  • 使用 goimports 自动管理导入

  • 按字母顺序排序

  • 分组管理,空行分隔

3.2 【必须】导入分组

import (
    // 第一组:标准库
    "context"
    "fmt"
    "os"
    
    // 第二组:第三方库
    "github.com/gin-gonic/gin"
    "github.com/spf13/cobra"
    
    // 第三组:内部包
    "myproject/internal/config"
    "myproject/pkg/utils"
    
    // 第四组:匿名导入(可选)
    // import mysql driver
    _ "github.com/go-sql-driver/mysql"
)

3.3 【必须】路径规范

// ✅ 正确:使用完整路径
import "github.com/myproject/pkg/utils"

// ❌ 错误:使用相对路径
import "../utils"

3.4 【推荐】别名使用

// ✅ 仅在必要时使用别名
import (
    "fmt"
    "os"
    
    // 包名冲突时使用别名
    nettrace "golang.org/x/net/trace"
    "runtime/trace"
    
    // 包名不规范时使用别名
    xapi "github.com/some/X_Y_Z_API"
)

4. 错误处理

4.1 【必须】错误处理原则

// ✅ 正确:error 作为最后一个返回值
func processData(input string) (result Data, err error) {
    // 实现
}

// ✅ 正确:立即处理错误
data, err := processData(input)
if err != nil {
    return fmt.Errorf("处理数据失败: %w", err)
}
// 使用 data

// ❌ 错误:error 不是最后一个参数
func badFunction() (error, string) { }

// ❌ 错误:else 分支处理正常逻辑
if err != nil {
    // 错误处理
} else {
    // 正常逻辑
}

4.2 【推荐】错误创建

// ✅ 简单错误
return errors.New("配置文件不存在")

// ✅ 格式化错误(Go 1.13+)
return fmt.Errorf("无法连接到服务器 %s: %w", addr, err)

// ✅ 自定义错误类型
type ValidationError struct {
    Field string
    Value interface{}
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("字段 %s 的值 %v 无效", e.Field, e.Value)
}

4.3 【必须】panic 和 recover

// ✅ 正确:仅在不可恢复的情况下使用 panic
func main() {
    config, err := loadConfig()
    if err != nil {
        log.Fatalf("无法加载配置: %v", err)
    }
    // 继续执行
}

// ✅ 正确:goroutine 中捕获 panic
func worker() {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("worker panic: %v\n%s", r, debug.Stack())
        }
    }()
    
    // 工作逻辑
}

// ❌ 错误:在业务逻辑中使用 panic
func calculateDiscount(price float64) float64 {
    if price < 0 {
        panic("价格不能为负数") // 应该返回错误
    }
    return price * 0.1
}

4.4 【必须】类型断言

// ✅ 正确:使用 comma ok 模式
if str, ok := value.(string); ok {
    // 使用 str
} else {
    // 处理类型不匹配
}

// ❌ 错误:可能产生 panic
str := value.(string)

5. 命名规范

5.1 【必须】通用命名规则

  • 使用驼峰命名法(camelCase/PascalCase)

  • 导出成员首字母大写,私有成员首字母小写

  • 避免使用缩写,除非是广泛认知的

5.2 【必须】包命名

// ✅ 正确
package user
package httputil
package stringutil

// ❌ 错误
package userManager
package utils        // 太泛化
package http_util    // 使用下划线

5.3 【必须】文件命名

// ✅ 正确
user_service.go
http_client.go
config_parser.go

// ❌ 错误
userService.go
HTTPClient.go

5.4 【必须】变量和函数命名

// ✅ 正确:专有名词规则
var apiClient *APIClient
var userID int64
var httpServer *HTTPServer

// 私有变量
var dbConn *sql.DB
var configFile string

// ❌ 错误
var apiClient *ApiClient  // API 应该全大写
var userId int64          // ID 应该全大写
var UrlPath string        // URL 在开头应该小写

5.5 【必须】常量命名

// ✅ 单个常量
const DefaultTimeout = 30 * time.Second

// ✅ 枚举常量
type Status int

const (
    StatusPending Status = iota
    StatusRunning
    StatusCompleted
    StatusFailed
)

// ✅ 私有常量
const maxRetryCount = 3

5.6 【必须】结构体和接口命名

// ✅ 结构体:名词或名词短语
type UserService struct {
    db *sql.DB
}

type HTTPClient struct {
    timeout time.Duration
}

// ✅ 接口命名
type Reader interface {
    Read([]byte) (int, error)
}

type UserRepository interface {
    FindByID(id int64) (*User, error)
    Create(user *User) error
}

// ❌ 错误
type UserProcessor struct {} // 应该避免动词
type DataInfo struct {}      // Info 太泛化

6. 代码结构

6.1 【推荐】控制结构最佳实践

if 语句

// ✅ 推荐:初始化语句
if err := validate(input); err != nil {
    return err
}

// ✅ 推荐:变量在左,常量在右
if count > 0 {
    // 处理逻辑
}

// ✅ 推荐:布尔值直接判断
if isValid {
    // 处理逻辑
}

if !isEnabled {
    return
}

for 和 range

// ✅ 推荐:使用短变量声明
for i := 0; i < len(items); i++ {
    process(items[i])
}

// ✅ 仅需要 key
for key := range m {
    delete(m, key)
}

// ✅ 仅需要 value
for _, value := range slice {
    process(value)
}

switch 语句

// ✅ 必须包含 default
switch status {
case StatusPending:
    // 处理等待状态
case StatusRunning:
    // 处理运行状态
default:
    // 处理未知状态
    log.Printf("未知状态: %v", status)
}

6.2 【推荐】提前返回

// ✅ 推荐:提前返回错误
func processUser(userID int64) error {
    user, err := findUser(userID)
    if err != nil {
        return fmt.Errorf("查找用户失败: %w", err)
    }
    
    if user.IsBlocked {
        return errors.New("用户已被锁定")
    }
    
    // 主要逻辑
    return updateUser(user)
}

7. 函数设计

7.1 【推荐】函数参数

// ✅ 推荐:命名返回值(多个相同类型)
func parseCoordinate(input string) (lat, lng float64, err error) {
    // 实现
}

// ✅ 推荐:普通返回值
func getUser(id int64) (*User, error) {
    // 实现
}

// ✅ 推荐:参数数量不超过 5 个
func createUser(name, email string, age int, isActive bool) (*User, error) {
    // 实现
}

// ✅ 考虑使用配置结构体
type CreateUserRequest struct {
    Name     string
    Email    string
    Age      int
    IsActive bool
    Tags     []string
}

func createUserAdvanced(req CreateUserRequest) (*User, error) {
    // 实现
}

7.2 【必须】资源管理

// ✅ 正确:检查错误后再 defer
func readFile(filename string) ([]byte, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close() // 确认打开成功后再 defer
    
    return io.ReadAll(file)
}

// ✅ 循环中的资源管理
func processFiles(filenames []string) error {
    for _, filename := range filenames {
        if err := func() error {
            file, err := os.Open(filename)
            if err != nil {
                return err
            }
            defer file.Close()
            
            return processFile(file)
        }(); err != nil {
            return err
        }
    }
    return nil
}

7.3 【推荐】方法接收器

// ✅ 推荐:接收器命名
type UserService struct {
    db *sql.DB
}

// 短方法可以使用单字符
func (u *UserService) Create(user *User) error {
    // 实现
}

// 长方法使用有意义的名称
func (service *UserService) generateComplexReport() (*Report, error) {
    // 超过 20 行的方法
}

// ❌ 错误:避免使用这些名称
func (self *UserService) BadMethod() {}
func (this *UserService) BadMethod() {}
func (me *UserService) BadMethod() {}

7.4 【必须】代码度量限制

  • 文件长度: 不超过 800 行

  • 函数长度: 不超过 80 行

  • 嵌套深度: 不超过 4 层

  • 函数参数: 不超过 5 个

7.5 【必须】魔法数字

// ❌ 错误:重复的魔法数字
func calculateArea(radius float64) float64 {
    return 3.14159 * radius * radius
}

func calculateCircumference(radius float64) float64 {
    return 2 * 3.14159 * radius
}

// ✅ 正确:使用常量
const Pi = 3.14159

func calculateArea(radius float64) float64 {
    return Pi * radius * radius
}

func calculateCircumference(radius float64) float64 {
    return 2 * Pi * radius
}

8. 注释规范

8.1 【必须】包注释

// Package user 提供用户管理相关的核心功能,包括用户创建、
// 认证、权限管理等操作。
package user

/*
Package config 提供应用程序配置管理功能。

支持多种配置源:
  - 配置文件 (JSON, YAML, TOML)
  - 环境变量
  - 命令行参数

基本用法:
    cfg, err := config.Load("app.yaml")
    if err != nil {
        log.Fatal(err)
    }
*/
package config

8.2 【必须】函数注释

// CreateUser 创建新用户并返回用户信息。
// 如果邮箱已存在,返回 ErrEmailExists 错误。
// 如果验证失败,返回 ValidationError。
func CreateUser(req CreateUserRequest) (*User, error) {
    // 实现
}

// 例外:某些标准方法可以不写注释
func (u *User) String() string {
    return fmt.Sprintf("User{ID: %d, Name: %s}", u.ID, u.Name)
}

8.3 【必须】类型注释

// User 表示系统中的用户实体
type User struct {
    ID       int64     `json:"id"`
    Name     string    `json:"name"`
    Email    string    `json:"email"`
    // CreatedAt 用户创建时间
    CreatedAt time.Time `json:"created_at"`
}

// UserStatus 定义用户状态类型
type UserStatus int

// UserRepository 定义用户数据访问接口
type UserRepository interface {
    // FindByID 根据用户ID查找用户
    FindByID(id int64) (*User, error)
    // Create 创建新用户
    Create(user *User) error
}

8.4 【必须】常量和变量注释

// DefaultTimeout 默认请求超时时间
const DefaultTimeout = 30 * time.Second

// 用户状态定义
const (
    UserStatusActive   UserStatus = 1 // 活跃用户
    UserStatusInactive UserStatus = 2 // 非活跃用户
    UserStatusBlocked  UserStatus = 3 // 被锁定用户
)

// ErrUserNotFound 用户不存在错误
var ErrUserNotFound = errors.New("用户不存在")

9. 测试规范

9.1 【必须】测试文件规范

// 文件命名:xxx_test.go
// user_service_test.go

func TestUserService_Create(t *testing.T) {
    // 测试实现
}

func Test_validateEmail(t *testing.T) {
    // 私有函数测试
}

func TestUser_String(t *testing.T) {
    // 方法测试
}

9.2 【推荐】测试结构

func TestCreateUser(t *testing.T) {
    tests := []struct {
        name    string
        input   CreateUserRequest
        want    *User
        wantErr bool
    }{
        {
            name: "valid user",
            input: CreateUserRequest{
                Name:  "张三",
                Email: "[email protected]",
            },
            want: &User{
                Name:  "张三",
                Email: "[email protected]",
            },
            wantErr: false,
        },
        {
            name: "invalid email",
            input: CreateUserRequest{
                Name:  "李四",
                Email: "invalid-email",
            },
            want:    nil,
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := CreateUser(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("CreateUser() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("CreateUser() = %v, want %v", got, tt.want)
            }
        })
    }
}

10. 依赖管理

10.1 【必须】使用 Go Modules

# 初始化模块
go mod init github.com/company/project

# 整理依赖
go mod tidy

# 验证依赖
go mod verify

10.2 【推荐】版本管理

// go.mod 示例
module github.com/company/project

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/spf13/cobra v1.7.0
)

require (
    // 间接依赖...
)

10.3 【必须】提交规范

  • 必须提交: go.modgo.sum

  • 不要提交: vendor/ 目录(除非特定需求)

  • 建议: 定期执行 go mod tidy 清理依赖


11. 项目结构建议

11.1 【推荐】标准项目布局

project/
├── cmd/                 # 主应用程序入口
│   └── server/
│       └── main.go
├── internal/            # 私有应用程序代码
│   ├── config/
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                 # 可被外部使用的库代码
│   └── utils/
├── api/                 # API 定义文件
├── docs/                # 设计和用户文档
├── scripts/             # 构建、安装、分析等脚本
├── test/                # 额外的外部测试应用程序和测试数据
├── go.mod
├── go.sum
└── README.md

12. 常用工具

12.1 【推荐】开发工具

# 格式化代码
gofmt -w .
goimports -w .

# 静态分析
go vet ./...
golint ./...

# 安全检查
gosec ./...

# 依赖检查
go mod tidy
go mod verify

# 测试
go test ./...
go test -race ./...
go test -cover ./...

12.2 【推荐】编辑器配置

建议配置编辑器/IDE 在保存时自动执行:

  • goimports

  • go vet

  • golint


13. 性能建议

13.1 【推荐】避免常见性能陷阱

// ✅ 推荐:预分配切片容量
items := make([]Item, 0, expectedSize)

// ✅ 推荐:使用字符串构建器
var builder strings.Builder
builder.Grow(expectedSize) // 预分配
for _, item := range items {
    builder.WriteString(item.String())
}
result := builder.String()

// ✅ 推荐:避免在循环中进行字符串拼接
// ❌ 错误
var result string
for _, item := range items {
    result += item.String() // 每次都会重新分配内存
}

13.2 【推荐】并发最佳实践

// ✅ 推荐:使用 context 控制超时和取消
func fetchData(ctx context.Context, url string) (*Data, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    // 处理响应
}

// ✅ 推荐:使用 sync.Pool 复用对象
var bufferPool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 1024)
    },
}

func processData(data []byte) error {
    buf := bufferPool.Get().([]byte)
    defer bufferPool.Put(buf)
    
    // 使用 buf 处理数据
}

14. 安全建议

14.1 【必须】输入验证

// ✅ 推荐:验证输入参数
func updateUser(userID int64, updates map[string]interface{}) error {
    if userID <= 0 {
        return errors.New("无效的用户ID")
    }
    
    // 验证允许的字段
    allowedFields := map[string]bool{
        "name":  true,
        "email": true,
        "age":   true,
    }
    
    for field := range updates {
        if !allowedFields[field] {
            return fmt.Errorf("不允许更新字段: %s", field)
        }
    }
    
    // 执行更新
}

14.2 【必须】敏感信息处理

// ✅ 推荐:不在日志中记录敏感信息
type User struct {
    ID       int64  `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    Password string `json:"-"` // 不序列化密码
}

func (u User) String() string {
    return fmt.Sprintf("User{ID: %d, Name: %s, Email: %s}", 
        u.ID, u.Name, u.Email) // 不包含密码
}

最后更新于