36 KiB
君鸿卡管系统 Constitution
Core Principles
I. Tech Stack Adherence (技术栈遵守)
规则 (RULES):
- 开发时 MUST 严格遵守项目定义的技术栈:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL
- MUST NOT 使用原生调用或绕过框架的快捷方式(禁止
database/sql直接调用、禁止net/http替代 Fiber、禁止encoding/json替代 sonic) - 所有 HTTP 路由和中间件 MUST 使用 Fiber 框架
- 所有数据库操作 MUST 通过 GORM 进行
- 所有配置管理 MUST 使用 Viper
- 所有日志记录 MUST 使用 Zap + Lumberjack.v2
- 所有 JSON 序列化 MUST 使用 sonic
- 所有异步任务 MUST 使用 Asynq
- MUST 使用 Go 官方工具链:
go fmt、go vet、golangci-lint - MUST 使用 Go Modules 进行依赖管理
理由 (RATIONALE):
一致的技术栈使用确保代码可维护性、团队协作效率和长期技术债务可控。绕过框架的"快捷方式"会导致代码碎片化、难以调试、性能不一致和安全漏洞。框架选择已经过深思熟虑,必须信任并充分利用其生态系统。Go 官方工具链确保代码风格一致性和质量。
II. Code Quality Standards (代码质量标准)
规则 (RULES):
- 代码 MUST 遵循项目分层架构:
Handler → Service → Store → Model - Handler 层 MUST ONLY 处理 HTTP 请求/响应,不得包含业务逻辑
- Service 层 MUST 包含所有业务逻辑,支持跨模块调用
- Store 层 MUST 统一管理所有数据访问,支持事务处理
- Model 层 MUST 定义清晰的数据结构和 DTO
- 所有依赖 MUST 通过结构体字段进行依赖注入(不使用构造函数模式)
- 所有公共错误 MUST 在
pkg/errors/中定义,使用统一错误码 - 所有 API 响应 MUST 使用
pkg/response/的统一格式 - 所有常量 MUST 在
pkg/constants/中定义和管理 - 所有 Redis key MUST 通过
pkg/constants/中的 Key 生成函数统一管理 - MUST 为所有导出的函数、类型和常量编写 Go 风格的文档注释(
// FunctionName does something...) - MUST 避免 magic numbers 和 magic strings,使用常量定义
Go 代码风格要求:
- MUST 使用
gofmt格式化所有代码 - MUST 遵循 Effective Go 和 Go Code Review Comments
- 变量命名 MUST 使用 Go 风格:
userID(不是userId)、HTTPServer(不是HttpServer) - 缩写词 MUST 全部大写或全部小写:
URL、ID、HTTP(导出)或url、id、http(未导出) - 包名 MUST 简短、小写、单数、无下划线:
user、order、pkg(不是users、userService、user_service) - 接口命名 SHOULD 使用
-er后缀:Reader、Writer、Logger(不是ILogger、LoggerInterface)
常量管理规范 (Constants Management):
- 业务常量(状态码、类型枚举等)MUST 定义在
pkg/constants/constants.go或按模块分文件 - Redis key MUST 使用函数生成,不允许硬编码字符串拼接
- Redis key 生成函数 MUST 遵循命名规范:
Redis{Module}{Purpose}Key(params...) - Redis key 格式 MUST 使用冒号分隔:
{module}:{purpose}:{identifier} - 示例:
// 正确:使用常量和生成函数 constants.SIMStatusActive constants.RedisSIMStatusKey(iccid) // 生成 "sim:status:{iccid}" // 错误:硬编码和拼接 status := "active" key := "sim:status:" + iccid
Magic Numbers 和硬编码规则 (Magic Numbers and Hardcoding Rules):
- MUST NOT 在代码中直接使用 magic numbers(未定义含义的数字字面量)
- MUST NOT 在代码中硬编码字符串字面量(URL、状态码、配置值、业务规则等)
- 当相同的字面量值在 3 个或以上位置使用时,MUST 提取为常量
- 已定义的常量 MUST 被使用,MUST NOT 重复硬编码相同的值
- 只允许在以下情况使用字面量:
- 语言特性:
nil、true、false - 数学常量:
0、1、-1(用于循环、索引、比较等明确的上下文) - 一次性使用的临时值(测试数据、日志消息等)
- 语言特性:
正确的常量使用:
// pkg/constants/constants.go
const (
DefaultPageSize = 20
MaxPageSize = 100
MinPageSize = 1
SIMStatusActive = "active"
SIMStatusInactive = "inactive"
SIMStatusSuspended = "suspended"
OrderTypeRecharge = "recharge"
OrderTypeTransfer = "transfer"
)
// internal/handler/user.go
func (h *Handler) ListUsers(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", constants.DefaultPageSize) // ✅ 使用常量
if pageSize > constants.MaxPageSize { // ✅ 使用常量
pageSize = constants.MaxPageSize
}
users, err := h.service.List(page, pageSize)
// ...
}
// internal/service/sim.go
func (s *Service) Activate(iccid string) error {
return s.store.UpdateStatus(iccid, constants.SIMStatusActive) // ✅ 使用常量
}
错误的硬编码模式:
// ❌ 硬编码 magic number(在多处使用)
func ListUsers(c *fiber.Ctx) error {
pageSize := c.QueryInt("page_size", 20) // ❌ 硬编码 20
if pageSize > 100 { // ❌ 硬编码 100
pageSize = 100
}
// ...
}
func ListOrders(c *fiber.Ctx) error {
pageSize := c.QueryInt("page_size", 20) // ❌ 重复硬编码 20
// ...
}
// ❌ 硬编码字符串(在多处使用)
func ActivateSIM(iccid string) error {
return UpdateStatus(iccid, "active") // ❌ 硬编码 "active"
}
func IsSIMActive(sim *SIM) bool {
return sim.Status == "active" // ❌ 重复硬编码 "active"
}
// ❌ 已定义常量但不使用
// 常量已定义在 pkg/response/codes.go
func Success(c *fiber.Ctx, data any) error {
return c.JSON(Response{
Code: 0, // ❌ 应该使用 errors.CodeSuccess
Data: data,
})
}
可接受的字面量使用:
// ✅ 语言特性和明确上下文的数字
if user == nil { // ✅ nil
return errors.New("user not found")
}
for i := 0; i < len(items); i++ { // ✅ 0, 1 用于循环
// ...
}
if count == 1 { // ✅ 1 用于比较
// 特殊处理单个元素
}
// ✅ 测试数据和日志消息
func TestUserCreate(t *testing.T) {
user := &User{
Name: "Test User", // ✅ 测试数据
Email: "test@example.com",
}
// ...
}
logger.Info("processing request", // ✅ 日志消息
zap.String("path", c.Path()),
zap.Int("status", 200))
中文注释和输出规范 (Chinese Comments and Output Standards):
本项目面向中文开发者,为提高代码可读性和维护效率,SHOULD 优先使用中文进行注释和输出。
- 代码注释(implementation comments)SHOULD 使用中文
- 日志消息(log messages)SHOULD 使用中文
- 用户可见的错误消息 MUST 使用中文(通过
pkg/errors/的双语消息支持) - 内部错误消息和调试日志 SHOULD 使用中文
- Go 文档注释(doc comments for exported APIs)MAY 使用英文以保持生态兼容性,但中文注释更佳
- 变量名、函数名、类型名 MUST 使用英文(遵循 Go 命名规范)
正确的中文注释使用:
// GetUserByID 根据用户 ID 获取用户信息
// 如果用户不存在,返回 NotFoundError
func (s *Service) GetUserByID(ctx context.Context, id string) (*User, error) {
// 参数验证
if id == "" {
return nil, errors.New(errors.CodeInvalidParam, "用户 ID 不能为空")
}
// 从存储层获取用户
user, err := s.store.GetByID(ctx, id)
if err != nil {
s.logger.Error("获取用户失败",
zap.String("user_id", id),
zap.Error(err))
return nil, fmt.Errorf("获取用户失败: %w", err)
}
// 检查用户状态
if user.Status != constants.UserStatusActive {
s.logger.Warn("用户状态异常",
zap.String("user_id", id),
zap.String("status", user.Status))
}
return user, nil
}
// CreateOrder 创建新订单
func (s *Service) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error) {
// 开启事务
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
s.logger.Error("创建订单时发生panic", zap.Any("panic", r))
}
}()
// 验证库存
if err := s.validateStock(ctx, req.ProductID, req.Quantity); err != nil {
tx.Rollback()
return nil, fmt.Errorf("库存验证失败: %w", err)
}
// 创建订单记录
order := &Order{
UserID: req.UserID,
ProductID: req.ProductID,
Quantity: req.Quantity,
Status: constants.OrderStatusPending,
}
if err := tx.Create(order).Error; err != nil {
tx.Rollback()
s.logger.Error("创建订单失败",
zap.String("user_id", req.UserID),
zap.Error(err))
return nil, err
}
tx.Commit()
s.logger.Info("订单创建成功",
zap.String("order_id", order.ID),
zap.String("user_id", req.UserID))
return order, nil
}
正确的中文日志使用:
// 信息日志
logger.Info("服务启动成功",
zap.String("host", cfg.Server.Host),
zap.Int("port", cfg.Server.Port))
logger.Info("配置热重载成功",
zap.String("config_file", "config.yaml"),
zap.Time("reload_time", time.Now()))
// 警告日志
logger.Warn("Redis 连接延迟较高",
zap.Duration("latency", latency),
zap.String("threshold", "50ms"))
// 错误日志
logger.Error("数据库连接失败",
zap.String("host", cfg.DB.Host),
zap.Int("port", cfg.DB.Port),
zap.Error(err))
logger.Error("令牌验证失败",
zap.String("token", token[:10]+"..."),
zap.String("client_ip", clientIP),
zap.Error(err))
// 调试日志
logger.Debug("处理用户请求",
zap.String("request_id", requestID),
zap.String("method", c.Method()),
zap.String("path", c.Path()),
zap.String("user_id", userID))
用户可见错误消息(双语支持):
// pkg/errors/codes.go
var errorMessages = map[int]ErrorMessage{
CodeSuccess: {"Success", "成功"},
CodeInternalError: {"Internal server error", "内部服务器错误"},
CodeMissingToken: {"Missing authentication token", "缺失认证令牌"},
CodeInvalidToken: {"Invalid or expired token", "令牌无效或已过期"},
CodeTooManyRequests: {"Too many requests", "请求过于频繁"},
CodeAuthServiceUnavailable: {"Authentication service unavailable", "认证服务不可用"},
}
// 使用时根据语言返回对应消息
message := errors.GetMessage(code, "zh") // 返回中文消息
错误的英文注释(不推荐):
// ❌ 全英文注释(可读性较差,不便于中文团队维护)
// GetUserByID retrieves user information by user ID
// Returns NotFoundError if user does not exist
func (s *Service) GetUserByID(ctx context.Context, id string) (*User, error) {
// Validate parameters
if id == "" {
return nil, errors.New(errors.CodeInvalidParam, "用户 ID 不能为空")
}
// Fetch user from store
user, err := s.store.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get user", // ❌ 英文日志
zap.String("user_id", id),
zap.Error(err))
return nil, fmt.Errorf("failed to get user: %w", err)
}
return user, nil
}
例外情况(可使用英文):
// ✅ 导出的包级别文档注释可使用英文(生态兼容性)
// Package user provides user management functionality.
// It includes user CRUD operations, authentication, and authorization.
package user
// ✅ 导出的 API 文档注释可使用英文
// UserService provides user-related business logic operations.
type UserService struct {
store UserStorer
logger *zap.Logger
}
// ✅ 但内部实现注释应使用中文
func (s *UserService) Create(ctx context.Context, req *CreateUserRequest) (*User, error) {
// 验证用户输入
if err := req.Validate(); err != nil {
return nil, err
}
// 检查用户是否已存在
existing, _ := s.store.GetByEmail(ctx, req.Email)
if existing != nil {
return nil, errors.New(errors.CodeUserExists, "用户邮箱已存在")
}
// 创建用户记录
user := &User{
Name: req.Name,
Email: req.Email,
}
return s.store.Create(ctx, user)
}
理由 (RATIONALE):
清晰的分层架构和代码组织使代码易于理解、测试和维护。统一的错误处理和响应格式提升 API 一致性和客户端集成体验。依赖注入模式便于单元测试和模块替换。集中管理常量和 Redis key 避免拼写错误、重复定义和命名不一致,提升代码可维护性和重构安全性。Redis key 统一管理便于监控、调试和缓存策略调整。遵循 Go 官方代码风格确保代码一致性和可读性。
避免硬编码和强制使用常量的规则能够:
- 提高可维护性:修改常量值只需改一处,不需要搜索所有硬编码位置
- 减少错误:避免手动输入错误(拼写错误、大小写错误)
- 增强可读性:
constants.MaxPageSize比100更能表达意图 - 便于重构:IDE 可以追踪常量使用,重命名时不会遗漏
- 统一业务规则:确保所有地方使用相同的业务规则值
- "3 次规则"提供明确的阈值,避免过早优化,同时确保重复值被及时抽取
III. Testing Standards (测试标准)
规则 (RULES):
- 所有核心业务逻辑(Service 层)MUST 有单元测试覆盖
- 所有 API 端点 MUST 有集成测试覆盖
- 所有数据库操作 SHOULD 有事务回滚测试
- 测试 MUST 使用 Go 标准测试框架(
testing包) - 测试文件 MUST 与源文件同目录,命名为
*_test.go - 测试函数 MUST 使用
Test前缀:func TestUserCreate(t *testing.T) - 基准测试 MUST 使用
Benchmark前缀:func BenchmarkUserCreate(b *testing.B) - 测试 MUST 可独立运行,不依赖外部服务(使用 mock 或 testcontainers)
- 单元测试 MUST 在 100ms 内完成
- 集成测试 SHOULD 在 1s 内完成
- 测试覆盖率 SHOULD 达到 70% 以上(核心业务代码必须 90% 以上)
- 测试 MUST 使用 table-driven tests 处理多个测试用例
- 测试 MUST 使用
t.Helper()标记辅助函数
Table-Driven Test 示例:
func TestUserValidate(t *testing.T) {
tests := []struct {
name string
user User
wantErr bool
}{
{"valid user", User{Name: "John", Email: "john@example.com"}, false},
{"empty name", User{Name: "", Email: "john@example.com"}, true},
{"invalid email", User{Name: "John", Email: "invalid"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.user.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
理由 (RATIONALE):
高质量的测试是代码质量的基石。单元测试确保业务逻辑正确性,集成测试确保模块间协作正常。快速的测试执行时间保证开发效率。测试独立性避免环境依赖导致的 flaky tests。Table-driven tests 使测试更简洁、易于扩展和维护,这是 Go 社区的最佳实践。
IV. User Experience Consistency (用户体验一致性)
规则 (RULES):
- 所有 API 响应 MUST 使用统一的 JSON 格式:
{ "code": 0, "message": "success", "data": {}, "timestamp": "2025-11-10T15:30:00Z" } - 所有错误响应 MUST 包含明确的错误码和错误消息(中英文双语)
- 所有 API 端点 MUST 遵循 RESTful 设计原则
- 所有分页 API MUST 使用统一的分页参数:
page、page_size、total - 所有时间字段 MUST 使用 ISO 8601 格式(RFC3339)
- 所有货币金额 MUST 使用整数表示(分为单位),避免浮点精度问题
- 所有布尔字段 MUST 使用
true/false,不使用0/1 - API 版本 MUST 通过 URL 路径管理(如
/api/v1/...)
理由 (RATIONALE):
一致的 API 设计降低客户端开发成本,减少集成错误。统一的数据格式和错误处理提升系统可预测性。清晰的时间和金额表示避免常见的数据处理错误。
V. Performance Requirements (性能要求)
规则 (RULES):
- API 响应时间(P95)MUST < 200ms(数据库查询 < 50ms)
- API 响应时间(P99)MUST < 500ms
- 批量操作 MUST 使用批量查询/插入,避免 N+1 查询问题
- 所有数据库查询 MUST 有适当的索引支持
- 列表查询 MUST 实现分页,默认
page_size=20,最大page_size=100 - 异步任务 MUST 用于非实时操作(批量同步、分佣计算等)
- 内存使用(API 服务)SHOULD < 500MB(正常负载)
- 内存使用(Worker 服务)SHOULD < 1GB(正常负载)
- 数据库连接池 MUST 配置合理(
MaxOpenConns=25,MaxIdleConns=10,ConnMaxLifetime=5m) - Redis 连接池 MUST 配置合理(
PoolSize=10,MinIdleConns=5) - 并发操作 SHOULD 使用 goroutines 和 channels(不是线程池模式)
- MUST 使用
context.Context进行超时和取消控制 - MUST 使用
sync.Pool复用频繁分配的对象(如缓冲区)
理由 (RATIONALE):
性能要求确保系统在生产环境下的稳定性和用户体验。批量操作和异步任务避免阻塞主流程。合理的连接池配置平衡性能和资源消耗。明确的性能指标便于监控和优化。使用 Go 的并发原语(goroutines、channels)而不是传统的线程池模式,充分发挥 Go 的并发优势。
VI. Go Idiomatic Design Principles (Go 语言惯用设计原则)
核心理念:写 Go 味道的代码,不要写 Java 味道的代码
包组织 (Package Organization)
MUST 遵循的原则:
- 包结构 MUST 扁平化,避免深层嵌套(最多 2-3 层)
- 包 MUST 按功能组织,不是按层次组织
- 包名 MUST 描述功能,不是类型(
http不是httputils、handlers)
正确的 Go 风格:
internal/
├── user/ # user 功能的所有代码
│ ├── handler.go # HTTP handlers
│ ├── service.go # 业务逻辑
│ ├── store.go # 数据访问
│ └── model.go # 数据模型
├── order/
│ ├── handler.go
│ ├── service.go
│ └── store.go
└── sim/
├── handler.go
├── service.go
└── store.go
错误的 Java 风格(禁止):
internal/
├── handlers/
│ ├── user/
│ │ └── UserHandler.go # ❌ 类名式命名
│ └── order/
│ └── OrderHandler.go
├── services/
│ ├── user/
│ │ ├── IUserService.go # ❌ 接口前缀 I
│ │ └── UserServiceImpl.go # ❌ Impl 后缀
│ └── impls/ # ❌ 过度抽象
├── repositories/ # ❌ Repository 模式过度使用
│ └── interfaces/
└── models/
└── entities/
└── dto/ # ❌ 过度分层
接口设计 (Interface Design)
MUST 遵循的原则:
- 接口 MUST 小而专注(1-3 个方法),不是大而全
- 接口 SHOULD 在使用方定义,不是实现方(依赖倒置)
- 接口命名 SHOULD 使用
-er后缀:Reader、Writer、Storer - MUST NOT 使用
I前缀或Interface后缀 - MUST NOT 创建只有一个实现的接口(除非明确需要抽象)
正确的 Go 风格:
// 小接口,在使用方定义
type UserStorer interface {
Create(ctx context.Context, user *User) error
GetByID(ctx context.Context, id string) (*User, error)
}
// 具体实现
type PostgresStore struct {
db *gorm.DB
}
func (s *PostgresStore) Create(ctx context.Context, user *User) error {
return s.db.WithContext(ctx).Create(user).Error
}
错误的 Java 风格(禁止):
// ❌ 大接口,方法过多
type IUserRepository interface {
Create(user *User) error
Update(user *User) error
Delete(id string) error
FindByID(id string) (*User, error)
FindByEmail(email string) (*User, error)
FindAll() ([]*User, error)
FindByStatus(status string) ([]*User, error)
Count() (int, error)
// ... 更多方法
}
// ❌ I 前缀
type IUserService interface {}
// ❌ 不必要的抽象层
type UserRepositoryImpl struct {} // 只有一个实现
错误处理 (Error Handling)
MUST 遵循的原则:
- 错误 MUST 显式返回和检查,不使用异常(panic/recover)
- 错误处理 MUST 紧跟错误产生的代码
- MUST 使用
errors.Is()和errors.As()检查错误类型 - MUST 使用
fmt.Errorf()包装错误,保留错误链 - 自定义错误 SHOULD 实现
error接口 - panic MUST ONLY 用于不可恢复的程序错误
正确的 Go 风格:
func (s *Service) GetUser(ctx context.Context, id string) (*User, error) {
user, err := s.store.GetByID(ctx, id)
if err != nil {
// 包装错误,添加上下文
return nil, fmt.Errorf("get user by id %s: %w", id, err)
}
return user, nil
}
// 自定义错误类型
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s not found: %s", e.Resource, e.ID)
}
错误的 Java 风格(禁止):
// ❌ 使用 panic 代替错误返回
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil {
panic(err) // ❌ 不要这样做
}
return user
}
// ❌ try-catch 风格
func DoSomething() {
defer func() {
if r := recover(); r != nil { // ❌ 滥用 recover
log.Println("recovered:", r)
}
}()
// ...
}
// ❌ 异常类层次结构
type Exception interface { // ❌ 不需要
GetMessage() string
GetStackTrace() []string
}
type BusinessException struct {} // ❌ 过度设计
type ValidationException struct {} // ❌ 过度设计
结构体和方法 (Structs and Methods)
MUST 遵循的原则:
- 结构体 MUST 简单直接,不是类(class)的替代品
- MUST NOT 为每个字段创建 getter/setter 方法
- MUST 直接访问导出的字段(大写开头)
- MUST 使用组合(composition)而不是继承(inheritance)
- 构造函数 SHOULD 命名为
New或NewXxx,返回具体类型 - MUST NOT 使用构造器模式(Builder Pattern)除非真正需要
正确的 Go 风格:
// 简单结构体,导出字段直接访问
type User struct {
ID string
Name string
Email string
}
// 简单构造函数
func NewUser(name, email string) *User {
return &User{
ID: generateID(),
Name: name,
Email: email,
}
}
// 使用组合
type Service struct {
store UserStorer
logger *zap.Logger
}
func NewService(store UserStorer, logger *zap.Logger) *Service {
return &Service{
store: store,
logger: logger,
}
}
错误的 Java 风格(禁止):
// ❌ 私有字段 + getter/setter
type User struct {
id string // ❌ 不需要私有
name string
email string
}
func (u *User) GetID() string { return u.id } // ❌ 不需要
func (u *User) SetID(id string) { u.id = id } // ❌ 不需要
func (u *User) GetName() string { return u.name } // ❌ 不需要
func (u *User) SetName(name string) { u.name = name } // ❌ 不需要
// ❌ 构造器模式(不必要)
type UserBuilder struct {
user *User
}
func NewUserBuilder() *UserBuilder { // ❌ 过度设计
return &UserBuilder{user: &User{}}
}
func (b *UserBuilder) WithName(name string) *UserBuilder {
b.user.name = name
return b
}
func (b *UserBuilder) Build() *User {
return b.user
}
// ❌ 工厂模式(过度抽象)
type UserFactory interface { // ❌ 不需要
CreateUser(name string) *User
}
并发模式 (Concurrency Patterns)
MUST 遵循的原则:
- MUST 使用 goroutines 和 channels,不是线程和锁(大多数情况)
- MUST 使用
context.Context传递取消信号 - MUST 遵循"通过通信共享内存,不要通过共享内存通信"
- SHOULD 使用
sync.WaitGroup等待 goroutines 完成 - SHOULD 使用
sync.Once确保只执行一次 - MUST NOT 创建线程池类(Go 运行时已处理)
正确的 Go 风格:
// 使用 goroutines 和 channels
func ProcessBatch(ctx context.Context, items []Item) error {
results := make(chan Result, len(items))
errors := make(chan error, len(items))
for _, item := range items {
go func(item Item) {
result, err := process(ctx, item)
if err != nil {
errors <- err
return
}
results <- result
}(item)
}
// 收集结果
for i := 0; i < len(items); i++ {
select {
case <-ctx.Done():
return ctx.Err()
case err := <-errors:
return err
case <-results:
// 处理结果
}
}
return nil
}
// 使用 sync.WaitGroup
func ProcessConcurrently(items []Item) {
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(item Item) {
defer wg.Done()
process(item)
}(item)
}
wg.Wait()
}
错误的 Java 风格(禁止):
// ❌ 线程池模式
type ThreadPool struct { // ❌ 不需要
workers int
taskQueue chan Task
wg sync.WaitGroup
}
func NewThreadPool(workers int) *ThreadPool { // ❌ Go 运行时已处理
return &ThreadPool{
workers: workers,
taskQueue: make(chan Task, 100),
}
}
// ❌ Future/Promise 模式(不需要单独实现)
type Future struct { // ❌ 直接使用 channel
result chan interface{}
err chan error
}
// ❌ 过度使用 mutex(应该用 channel)
type SafeMap struct { // ❌ 考虑使用 sync.Map 或 channel
mu sync.Mutex
data map[string]interface{}
}
func (m *SafeMap) Get(key string) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[key]
}
命名约定 (Naming Conventions)
MUST 遵循的原则:
- 变量名 MUST 简短且符合上下文:
- 短作用域用短名字:
i,j,k用于循环 - 长作用域用描述性名字:
userRepository,configManager
- 短作用域用短名字:
- 缩写词 MUST 保持一致的大小写:
URL,HTTP,ID(不是Url,Http,Id) - MUST NOT 使用匈牙利命名法或类型前缀:
strName,arrUsers(禁止) - MUST NOT 使用下划线连接(除了测试和包名):
user_service(禁止,应该是userservice或UserService) - 方法接收者名称 SHOULD 使用 1-2 个字母的缩写,全文件保持一致
正确的 Go 风格:
// 短作用域,短名字
for i, user := range users {
fmt.Println(i, user.Name)
}
// 长作用域,描述性名字
func ProcessUserRegistration(ctx context.Context, req *RegistrationRequest) error {
// ...
}
// 缩写词大小写一致
type UserAPI struct {
BaseURL string
APIKey string
UserID int64
}
// 方法接收者简短一致
type UserService struct {}
func (s *UserService) Create(user *User) error { // s 用于 service
return s.store.Create(user)
}
func (s *UserService) Update(user *User) error { // 保持一致使用 s
return s.store.Update(user)
}
错误的 Java 风格(禁止):
// ❌ 过长的变量名
func ProcessRegistration() {
userRegistrationRequest := &Request{} // ❌ 太长
userRegistrationValidator := NewValidator() // ❌ 太长
userRegistrationService := NewService() // ❌ 太长
}
// ❌ 匈牙利命名
strUserName := "John" // ❌
intUserAge := 25 // ❌
arrUserList := []User{} // ❌
// ❌ 下划线命名
type User_Service struct {} // ❌ 应该是 UserService
func Get_User_By_Id() {} // ❌ 应该是 GetUserByID
// ❌ 缩写词大小写不一致
type UserApi struct { // ❌ 应该是 UserAPI
BaseUrl string // ❌ 应该是 BaseURL
ApiKey string // ❌ 应该是 APIKey
UserId int64 // ❌ 应该是 UserID
}
// ❌ 方法接收者不一致或过长
func (userService *UserService) Create() {} // ❌ 太长
func (us *UserService) Update() {} // ❌ 与上面不一致
func (this *UserService) Delete() {} // ❌ 不要使用 this/self
反模式清单 (Anti-Patterns to Avoid)
严格禁止的 Java 风格模式:
- ❌ 过度抽象:不需要的接口、工厂、构造器
- ❌ Getter/Setter:直接访问导出字段
- ❌ 继承层次:使用组合,不是嵌入
- ❌ 异常处理:使用错误返回,不是 panic/recover
- ❌ 单例模式:使用包级别变量或
sync.Once - ❌ 线程池:直接使用 goroutines
- ❌ 深层包嵌套:保持扁平结构
- ❌ 类型前缀:
IService,AbstractBase,ServiceImpl - ❌ Bean 风格:不需要 POJO/JavaBean 模式
- ❌ 过度 DI 框架:简单直接的依赖注入
理由 (RATIONALE):
Go 语言的设计哲学强调简单性、直接性和实用性。Java 风格的模式(深层继承、过度抽象、复杂的设计模式)违背了 Go 的核心理念。Go 提供了更简单、更高效的方式来解决相同的问题:接口的隐式实现、结构体组合、显式错误处理、轻量级并发。遵循 Go 惯用法不仅使代码更地道,还能充分发挥 Go 的性能优势和简洁性。
Development Workflow (开发工作流程)
分支管理
- MUST 从
main分支创建功能分支 - 功能分支命名格式:
feature/###-brief-description或fix/###-brief-description - MUST 在合并前保持分支与
main同步(rebase 或 merge) - 合并到
mainMUST 通过 Pull Request 并经过代码审查
提交规范
- 提交信息 MUST 遵循 Conventional Commits 格式:
feat: 新功能描述fix: 修复问题描述refactor: 重构描述test: 测试相关docs: 文档更新chore: 构建/工具相关
- 提交信息 SHOULD 简洁明了(中文或英文)
代码审查
- 所有 PR MUST 至少有一人审查通过
- 审查者 MUST 验证:
- 代码符合本宪章所有原则(特别是 Go 惯用法原则)
- 无 Java 风格的反模式(getter/setter、过度抽象等)
- 测试覆盖充分且通过
- 无安全漏洞(SQL 注入、XSS、命令注入等)
- 性能影响可接受
- 代码通过
gofmt、go vet、golangci-lint检查
Quality Gates (质量关卡)
代码合并前检查
- 所有测试通过(
go test ./...) - 代码格式化(
gofmt -l .无输出) - 代码静态检查通过(
go vet ./...) - 代码质量检查通过(
golangci-lint run) - 测试覆盖率符合标准(
go test -cover ./...) - 无 TODO/FIXME 遗留(或已记录 Issue)
- API 文档已更新(如有 API 变更)
- 数据库迁移文件已创建(如有 schema 变更)
- 常量和 Redis key 使用符合规范(无硬编码字符串)
- 无重复硬编码值(3 次以上相同字面量已提取为常量)
- 已定义的常量被正确使用(无重复硬编码已有常量的值)
- 代码注释优先使用中文(实现注释使用中文,提高团队可读性)
- 日志消息使用中文(Info/Warn/Error/Debug 日志使用中文描述)
- 错误消息支持中文(用户可见错误有中文消息)
- 无 Java 风格反模式(无 getter/setter、无不必要接口、无过度抽象)
- 遵循 Go 命名约定(缩写大小写一致、包名简短、无下划线)
- 使用 Go 惯用并发模式(goroutines/channels,无线程池类)
上线前检查
- 所有功能在 staging 环境测试通过
- 性能测试通过(响应时间、内存使用、并发能力)
- 数据库迁移在 staging 环境验证通过
- 监控和告警配置完成
- 回滚方案已准备
Governance
本宪章是所有开发实践的最高指导原则,优先级高于个人偏好或临时便利。
修订流程
- 宪章修订 MUST 经过团队讨论并达成共识
- 修订 MUST 包含明确的理由和影响分析
- 修订 MUST 更新版本号(遵循语义化版本)
- 修订 MUST 同步更新相关模板(plan-template.md、spec-template.md、tasks-template.md)
版本策略
- MAJOR: 移除或重新定义核心原则(不兼容变更)
- MINOR: 新增原则或显著扩展指导内容
- PATCH: 澄清说明、措辞优化、错误修正
合规审查
- 所有 PR 审查 MUST 验证是否符合本宪章
- 违反宪章的代码 MUST 在合并前修正
- 特别关注 Go 惯用法原则,拒绝 Java 风格的代码
- 特别关注常量使用规范,拒绝不必要的硬编码
- 任何复杂性增加(新依赖、新架构层)MUST 在设计文档中明确说明必要性
运行时开发指导
开发时参考本宪章确保一致性。如有疑问,优先遵守原则,再讨论例外情况。记住:写 Go 代码,不是用 Go 语法写 Java。记住:定义常量是为了使用,不是为了装饰。
Version: 2.1.1 | Ratified: 2025-11-10 | Last Amended: 2025-11-11