新增 Principle VII: Documentation Standards(文档规范)
主要变更:
- 规定功能总结文档必须放在 docs/{feature-id}/ 目录,对应 specs/{feature-id}/
- 要求总结文档使用中文命名(功能总结.md、使用指南.md、架构说明.md)
- 要求总结文档内容使用中文编写,方便中文团队阅读
- 强制要求每次完成功能后更新 README.md,添加 2-3 句简短描述
- 目标:让首次接触项目的开发者能快速了解功能
模板同步更新:
- plan-template.md: 添加文档结构说明和 Documentation Standards 检查项
- tasks-template.md: 新增文档任务模板和文档相关质量检查项
版本升级理由:
MINOR 版本升级(2.1.1 → 2.2.0),因为新增了影响开发工作流程的强制性原则,
要求所有功能完成后必须创建中文总结文档并更新 README.md
1037 lines
36 KiB
Markdown
1037 lines
36 KiB
Markdown
<!--
|
||
SYNC IMPACT REPORT - Constitution Amendment
|
||
============================================
|
||
Version Change: 2.1.1 → 2.2.0
|
||
Date: 2025-11-11
|
||
|
||
NEW PRINCIPLES ADDED:
|
||
- VII. Documentation Standards (文档规范) - NEW principle for feature documentation
|
||
|
||
MODIFIED SECTIONS:
|
||
- Added new Principle VII with documentation structure and language requirements
|
||
- Rule: Summary docs MUST be placed in docs/{feature-id}/ mirroring specs/{feature-id}/
|
||
- Rule: Summary doc filenames MUST use Chinese
|
||
- Rule: Summary doc content MUST use Chinese
|
||
- Rule: README.md MUST be updated with brief Chinese summary for each feature
|
||
- Rule: Summary should be concise enough for first-time contributors to understand quickly
|
||
- Examples of correct documentation structure and naming
|
||
- Rationale for centralized Chinese documentation
|
||
|
||
TEMPLATES REQUIRING UPDATES:
|
||
✅ .specify/templates/plan-template.md - Added documentation structure guidance
|
||
✅ .specify/templates/spec-template.md - No changes needed
|
||
✅ .specify/templates/tasks-template.md - Added documentation task template in Polish phase
|
||
|
||
FOLLOW-UP ACTIONS:
|
||
- Update plan-template.md with docs/ structure
|
||
- Update tasks-template.md with documentation tasks in Polish phase
|
||
|
||
RATIONALE:
|
||
MINOR version bump (2.2.0) - New principle added for documentation standards.
|
||
This is a new governance rule that establishes how feature documentation should be
|
||
organized, named, and written. The principle addresses:
|
||
1. Documentation location consistency (docs/ mirrors specs/)
|
||
2. Language requirements (Chinese for accessibility)
|
||
3. README.md update requirements (brief summaries)
|
||
4. Target audience (first-time contributors)
|
||
|
||
This is a MINOR bump (not PATCH) because it adds a new mandatory principle that
|
||
affects the development workflow, requiring documentation tasks for all features.
|
||
-->
|
||
|
||
# 君鸿卡管系统 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](https://go.dev/doc/effective_go) 和 [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments)
|
||
- 变量命名 **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}`
|
||
- 示例:
|
||
```go
|
||
// 正确:使用常量和生成函数
|
||
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`(用于循环、索引、比较等明确的上下文)
|
||
- 一次性使用的临时值(测试数据、日志消息等)
|
||
|
||
**正确的常量使用:**
|
||
```go
|
||
// 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) // ✅ 使用常量
|
||
}
|
||
```
|
||
|
||
**错误的硬编码模式:**
|
||
```go
|
||
// ❌ 硬编码 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,
|
||
})
|
||
}
|
||
```
|
||
|
||
**可接受的字面量使用:**
|
||
```go
|
||
// ✅ 语言特性和明确上下文的数字
|
||
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 命名规范)
|
||
|
||
**正确的中文注释使用:**
|
||
```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
|
||
}
|
||
```
|
||
|
||
**正确的中文日志使用:**
|
||
```go
|
||
// 信息日志
|
||
logger.Info("服务启动成功",
|
||
zap.String("host", cfg.Server.Host),
|
||
zap.Int("port", cfg.Server.Port))
|
||
|
||
// 警告日志
|
||
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))
|
||
```
|
||
|
||
**理由 (RATIONALE):**
|
||
|
||
清晰的分层架构和代码组织使代码易于理解、测试和维护。统一的错误处理和响应格式提升 API 一致性和客户端集成体验。依赖注入模式便于单元测试和模块替换。集中管理常量和 Redis key 避免拼写错误、重复定义和命名不一致,提升代码可维护性和重构安全性。Redis key 统一管理便于监控、调试和缓存策略调整。遵循 Go 官方代码风格确保代码一致性和可读性。
|
||
|
||
避免硬编码和强制使用常量的规则能够:
|
||
1. **提高可维护性**:修改常量值只需改一处,不需要搜索所有硬编码位置
|
||
2. **减少错误**:避免手动输入错误(拼写错误、大小写错误)
|
||
3. **增强可读性**:`constants.MaxPageSize` 比 `100` 更能表达意图
|
||
4. **便于重构**:IDE 可以追踪常量使用,重命名时不会遗漏
|
||
5. **统一业务规则**:确保所有地方使用相同的业务规则值
|
||
6. "3 次规则"提供明确的阈值,避免过早优化,同时确保重复值被及时抽取
|
||
|
||
使用中文注释和日志的理由:
|
||
1. **提高团队效率**:中文开发团队阅读中文注释更快速准确
|
||
2. **降低理解成本**:减少翻译和理解偏差
|
||
3. **改善调试体验**:中文日志更易于排查问题
|
||
4. **保持生态兼容**:导出 API 的文档注释可保留英文
|
||
|
||
---
|
||
|
||
### 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 示例:**
|
||
|
||
```go
|
||
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 格式:
|
||
```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 风格:**
|
||
```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 风格(禁止):**
|
||
```go
|
||
// ❌ 大接口,方法过多
|
||
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 风格:**
|
||
```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 风格(禁止):**
|
||
```go
|
||
// ❌ 使用 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 风格:**
|
||
```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 风格(禁止):**
|
||
```go
|
||
// ❌ 私有字段 + 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 风格:**
|
||
```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 风格(禁止):**
|
||
```go
|
||
// ❌ 线程池模式
|
||
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 风格:**
|
||
```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 风格(禁止):**
|
||
```go
|
||
// ❌ 过长的变量名
|
||
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 风格模式:**
|
||
|
||
1. ❌ **过度抽象**:不需要的接口、工厂、构造器
|
||
2. ❌ **Getter/Setter**:直接访问导出字段
|
||
3. ❌ **继承层次**:使用组合,不是嵌入
|
||
4. ❌ **异常处理**:使用错误返回,不是 panic/recover
|
||
5. ❌ **单例模式**:使用包级别变量或 `sync.Once`
|
||
6. ❌ **线程池**:直接使用 goroutines
|
||
7. ❌ **深层包嵌套**:保持扁平结构
|
||
8. ❌ **类型前缀**:`IService`, `AbstractBase`, `ServiceImpl`
|
||
9. ❌ **Bean 风格**:不需要 POJO/JavaBean 模式
|
||
10. ❌ **过度 DI 框架**:简单直接的依赖注入
|
||
|
||
**理由 (RATIONALE):**
|
||
|
||
Go 语言的设计哲学强调简单性、直接性和实用性。Java 风格的模式(深层继承、过度抽象、复杂的设计模式)违背了 Go 的核心理念。Go 提供了更简单、更高效的方式来解决相同的问题:接口的隐式实现、结构体组合、显式错误处理、轻量级并发。遵循 Go 惯用法不仅使代码更地道,还能充分发挥 Go 的性能优势和简洁性。
|
||
|
||
---
|
||
|
||
### VII. Documentation Standards (文档规范)
|
||
|
||
**规则 (RULES):**
|
||
|
||
- 每个功能完成后 **MUST** 在 `docs/` 目录创建总结文档
|
||
- 总结文档路径 **MUST** 遵循规范:`docs/{feature-id}/` 对应 `specs/{feature-id}/`
|
||
- 总结文档文件名 **MUST** 使用中文命名(例如:`功能总结.md`、`使用指南.md`、`架构说明.md`)
|
||
- 总结文档内容 **MUST** 使用中文编写
|
||
- 每次添加新功能总结文档时 **MUST** 同步更新 `README.md`
|
||
- `README.md` 中的功能描述 **MUST** 简短精炼,让首次接触项目的开发者能快速了解
|
||
- `README.md` 的功能描述 **SHOULD** 控制在 2-3 句话以内
|
||
|
||
**文档结构示例:**
|
||
|
||
```
|
||
specs/001-fiber-middleware-integration/ # 功能规划文档(设计阶段)
|
||
├── spec.md
|
||
├── plan.md
|
||
├── tasks.md
|
||
└── quickstart.md
|
||
|
||
docs/001-fiber-middleware-integration/ # 功能总结文档(完成阶段)
|
||
├── 功能总结.md # 功能概述和实现要点
|
||
├── 使用指南.md # 如何使用该功能
|
||
└── 架构说明.md # 架构设计和技术决策(可选)
|
||
|
||
README.md # 项目主文档
|
||
## 核心功能
|
||
- **认证中间件**:基于 Redis 的 Token 认证
|
||
- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端
|
||
```
|
||
|
||
**正确的文档组织:**
|
||
|
||
```markdown
|
||
<!-- docs/002-user-management/功能总结.md -->
|
||
# 用户管理功能总结
|
||
|
||
## 功能概述
|
||
|
||
实现了完整的用户 CRUD 操作,包括用户创建、查询、更新、删除和列表分页。
|
||
|
||
## 核心实现
|
||
|
||
- Handler: `internal/handler/user.go`
|
||
- Service: `internal/service/user.go`
|
||
- Store: `internal/store/postgres/user.go`
|
||
- Model: `internal/model/user.go`
|
||
|
||
## API 端点
|
||
|
||
- `POST /api/v1/users` - 创建用户
|
||
- `GET /api/v1/users/:id` - 获取用户
|
||
- `PUT /api/v1/users/:id` - 更新用户
|
||
- `DELETE /api/v1/users/:id` - 删除用户
|
||
- `GET /api/v1/users` - 用户列表(分页)
|
||
|
||
## 技术要点
|
||
|
||
- 使用 GORM 进行数据访问
|
||
- 使用 Validator 进行参数验证
|
||
- 支持事务处理
|
||
- 统一错误处理和响应格式
|
||
|
||
详细使用说明请参阅 [使用指南.md](./使用指南.md)
|
||
```
|
||
|
||
```markdown
|
||
<!-- README.md 更新示例 -->
|
||
## 核心功能
|
||
|
||
- **认证中间件**:基于 Redis 的 Token 认证
|
||
- **限流中间件**:IP 限流,支持 Memory 和 Redis 存储
|
||
- **用户管理**:完整的用户 CRUD 和分页列表功能
|
||
- **日志系统**:Zap 结构化日志,自动轮转和请求追踪
|
||
|
||
详细文档:
|
||
- [认证中间件](docs/001-fiber-middleware-integration/功能总结.md)
|
||
- [用户管理](docs/002-user-management/功能总结.md)
|
||
```
|
||
|
||
**错误的文档组织(禁止):**
|
||
|
||
```
|
||
❌ docs/feature-summary.md # 所有功能混在一个文件
|
||
❌ docs/001-summary.md # 英文命名
|
||
❌ docs/001/summary.md # 英文内容
|
||
❌ specs/001.../implementation.md # 总结文档放在 specs/ 下
|
||
❌ README.md 中没有更新新功能 # 遗漏 README 更新
|
||
❌ README.md 功能描述过长(超过 5 句话) # 描述冗长
|
||
```
|
||
|
||
**README.md 更新要求:**
|
||
|
||
- **简洁性**:每个功能 2-3 句话描述核心价值
|
||
- **可读性**:使用中文,便于中文开发者快速理解
|
||
- **链接性**:提供到详细文档的链接
|
||
- **分类性**:按功能模块分组(如"核心功能"、"中间件"、"业务模块"等)
|
||
|
||
**理由 (RATIONALE):**
|
||
|
||
统一的文档结构使新成员快速了解项目功能和架构。中文文档降低中文团队的阅读门槛,提高协作效率。将总结文档与设计文档分离(`docs/` vs `specs/`),清晰区分"计划做什么"和"做了什么"。强制更新 README.md 确保项目主文档始终反映最新功能。简短的 README 描述让首次接触项目的开发者能在 5 分钟内了解项目全貌。
|
||
|
||
---
|
||
|
||
## Development Workflow (开发工作流程)
|
||
|
||
### 分支管理
|
||
|
||
- **MUST** 从 `main` 分支创建功能分支
|
||
- 功能分支命名格式:`feature/###-brief-description` 或 `fix/###-brief-description`
|
||
- **MUST** 在合并前保持分支与 `main` 同步(rebase 或 merge)
|
||
- 合并到 `main` **MUST** 通过 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` 检查
|
||
- **文档已按规范更新(docs/ 和 README.md)**
|
||
|
||
---
|
||
|
||
## 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,无线程池类)
|
||
- [ ] **功能总结文档已创建**(在 `docs/{feature-id}/` 目录下,中文命名和内容)
|
||
- [ ] **README.md 已更新**(添加功能简短描述,2-3 句话)
|
||
|
||
### 上线前检查
|
||
|
||
- [ ] 所有功能在 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.2.0 | **Ratified**: 2025-11-10 | **Last Amended**: 2025-11-11
|