一、项目结构规范
遵循 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 # 项目说明规范要求:
内部包与公开包分离:
internal目录下的包不可被外部模块引用,防止接口不稳定导致的依赖问题。单一职责原则:每个目录 / 文件仅处理单一功能(如
handlers处理 HTTP 请求,service处理业务逻辑)。配置管理:配置文件支持环境变量覆盖,通过
viper或config包加载,避免硬编码。
二、命名规范
1. 包名
使用小写单词,多个单词使用下划线,无驼峰(如
database、logger,避免dbUtils)。包名与目录名一致,禁止使用复数(如目录
models对应包名model)。避免模糊命名(如
util、common,建议更具体如timeutil、stringutils)。
2. 标识符(变量、函数、结构体)
导出规则:首字母大写表示可导出(公共接口),小写表示包内私有。
正确:
User(结构体)、GetUser()(函数)、MaxSize(常量)。错误:
user(公共结构体未导出)、getUser()(公共函数未导出)。
变量名:使用有意义的全称(如
userID而非uId),布尔变量以Is/Has/Can开头(如isActive、hasPermission)。函数名:动词或动词短语(如
CreateUser、ValidateInput),返回错误的函数以Err结尾(如GetUserErr)。结构体 / 接口名:名词或名词短语(如
UserRepository、CacheInterface)。
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结尾(如Reader、Writer),单个方法的接口使用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. 日志规范
区分日志级别(
debug、info、warn、error、fatal),使用服务的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包的T或TB接口,避免依赖外部测试框架。
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.mod和go.sum到版本控制。避免循环依赖:通过接口隔离、依赖倒置原则解耦模块。
2. 第三方库选择
优先使用内部已筛选过的三方库,不能满足业务的可选择社区主流库(如
gin用于 HTTP,gorm用于 ORM),避免维护成本高的小众库。定期更新依赖(每月一次),使用
go list -u检查可用更新,通过go get -u升级。敏感操作库(如加密、认证)需经过安全审计(如
golang.org/x/crypto而非自定义实现)。
七、代码审查与协作
1. 审查要点
设计合理性:是否符合单一职责、开闭原则,接口是否稳定。
错误处理:是否遗漏错误,错误链是否正确构建,日志是否足够。
测试覆盖:单元测试是否覆盖所有分支,集成测试是否验证端到端流程。
性能与安全:是否存在内存泄漏、竞态条件,敏感数据是否加密存储。
命名与注释:标识符是否清晰,复杂逻辑是否有注释(见下方注释规范)。
2. 分支策略
使用
master作为生产分支,dev作为开发分支。功能分支命名:
feature-v3.0.0。临时分支命名:
dev-temp250302,为临时处理需求的分支。禁止直接提交到
master分支,必须通过代码审查。
八、注释规范
1. 包注释
每个包的 doc.go 或包内首个文件添加注释,说明包的功能、设计原则:
// user 包提供用户管理核心逻辑,包括用户创建、查询、更新和删除功能。
// 所有接口遵循错误优先返回原则,业务错误通过 BusinessError 结构体传递。
package user2. 接口与结构体注释
导出的接口 / 结构体必须添加注释,说明用途和字段含义:
// 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,如govet、gocyclo、ineffassign)。调试与性能:
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)。