Files
mce/docs/code-style.md
Victor_Jay 47efd7921b docs: 完善代码规范 — 设计原则+拆分决策+架构说明
- 响应格式示例对齐实际代码(http.Status 模式)
- 补 6 大设计原则(YAGNI/DIP/SRP/OCP/ISP/KISS)
- 补架构说明(分层+ISP,五边形架构)
- 补重构原则:不向后兼容 + CMS 化
- 文件拆分决策矩阵(该拆/不该拆)
2026-06-22 01:22:41 +08:00

6.5 KiB
Raw Blame History

Go 代码规范

文件大小

层次 单文件行数上限 说明
Controller ≤ 120 行 只管参数绑定 + 调用服务 + 返回
Service ≤ 300 行 承载业务逻辑,超过则拆子服务
Repository ≤ 200 行 纯数据库操作
Model ≤ 80 行 纯结构体
Middleware ≤ 60 行 单一职责
Router ≤ 60 行 只做路由映射

注意:以上为建议上限,不是硬性指标。是否拆分取决于职责是否内聚,而非单纯看行数。拆分决策矩阵:

该拆 不该拆
多个独立功能塞一个文件(如 7 个 Tab 的 JS 全混居 + 5 个 IIFE 页面内区域共享大量样式,拆了反而碎片化(如文章详情/列表共用 article-card
一个文件承担了压根不同域的工作(如所有 API 路由写一个文件) 功能高度耦合,拆了互相 import 更乱(如评论增删查+@提及+上传一体)
CSS 随 HTML 模板拆分自然跟随 公共工具函数拆成碎片没有意义
路由文件按业务域自然分割(与 admin.go 对齐) 文件超线但职责单一、内聚良好 — 维持现状

瘦控制器铁律

Controller 每个方法不超过三步:

  1. 绑定请求参数
  2. 调用 Service
  3. 返回响应
// ✅ 正确
func (c *PostController) Create(ctx *gin.Context) {
    var req dto.CreatePostRequest
    if err := ctx.ShouldBind(&req); err != nil {
        common.Error(ctx, common.ErrInvalidParam, "参数错误")
        return
    }
    post, err := c.postService.Create(ctx, req, getCurrentUser(ctx))
    if err != nil {
        common.Error(ctx, common.ErrInternal, "创建失败")
        return
    }
    common.Ok(ctx, post)
}

// ❌ 错误Controller 里写 if-else 业务分支
func (c *PostController) Create(ctx *gin.Context) {
    // ... 参数绑定 ...
    if req.Type == "draft" {
        // 草稿逻辑写在这里 ↓ 错误!
        post.Status = 0
    } else {
        // 发布逻辑写在这里 ↓ 错误!
        post.Status = 1
        if post.Score > 100 { ... }
    }
}

业务判断全部放在 Service 层。

Controller 禁止事项

  • 直接调用 Repository
  • 调用另一个 Controller
  • 写 if-else 业务分支
  • 在 handler 里直接操作数据库
  • 代码回滚、重试等编排逻辑

命名规范

文件

  • Go 文件:snake_case.go
  • 模板文件:snake_case.html
  • CSS/JS 文件:kebab-case.css / camelCase.js

结构体与方法

  • Controllertype PostController struct{},方法 func (c *PostController) Create(...)
  • Servicetype PostService struct{ repo *PostRepo },方法 func (s *PostService) Create(...)
  • Repositorytype PostRepo struct{ db *gorm.DB },方法 func (r *PostRepo) FindByID(...)

响应格式

统一使用 common 包的响应函数HTTP 状态码直接传入:

common.Ok(c, data)                                   // 200 成功
common.OkWithMessage(c, data, "操作成功")              // 200 带消息
common.OkMessage(c, "操作成功")                        // 200 纯消息
common.Error(c, http.StatusBadRequest, "参数错误")     // 400
common.Error(c, http.StatusUnauthorized, "请先登录")   // 401
common.Error(c, http.StatusForbidden, "权限不足")      // 403
common.Error(c, http.StatusNotFound, "未找到")         // 404
common.Error(c, http.StatusInternalServerError, "服务器错误") // 500

模板变量

使用驼峰命名,与模板文件保持一致:

gin.H{
    "Title":      "页面标题",
    "ExtraCSS":   "/static/css/page.css",
    "Guidelines": template.HTML(content),  // HTML 内容需显式标记类型
}

依赖注入

使用构造器注入模式Controller/Service/Middleware 的依赖通过构造函数传入:

// 构造器注入依赖
func NewAuthController(authService authUseCase, sm *session.Manager, limiter rateLimiter, cfg *config.Config, siteSettings *config.SiteSettings) *AuthController {
	return &AuthController{authService: authService, sessionManager: sm, rateLimiter: limiter, cfg: cfg, siteSettings: siteSettings}
}

Controller 层通过 ISP 接口隔离 声明最小依赖 —— 只声明自己需要的方法子集,不依赖完整 Service 接口:

// controller/admin/interfaces.go
type siteSettingUseCase interface {
	GetSettings() map[string]string
	UpdateSetting(key, value string) error
	UpdateBoolSetting(key string, value bool) error
	GetAuditSettings(defaults *service.AuditDefaults) *service.AuditSettings
}

各 Controller 自身的 ISP 声明放在对应的 interfaces.go 文件中,严禁 controller 直接 import 完整的 Service 结构体。

设计原则

原则 含义 本项目实践
YAGNI You Ain't Gonna Need It — 不为可能的需求提前实现 分 Phase 推进,不做预留接口/未用抽象层
DIP(依赖倒置) 高层模块不依赖低层,都依赖抽象 Controller → ISP 接口 ← ServiceService → Store 接口 ← Repository
SRP(单一职责) 一个模块只为一个角色变更 拆分决策:「多域混居则拆,内聚共享则留」
OCP(开闭原则) 对扩展开放,对修改关闭 config.yaml 角色/审核配置可扩展,不改代码
ISP(接口隔离) 不依赖用不到的接口 Controller 只声明自己需要的方法子集,见下方 ISP 章节
KISS Keep It Simple 连续字符校验纯函数、密码强度 switch-case不做策略模式/DI 框架

架构风格

分层架构 + ISP 接口隔离非纯六边形。Gin、GORM 等框架层不额外抽象,仅在业务边界通过接口倒置依赖方向:

Web 适配器 (Gin Controller) → ISP Ports (interfaces.go)
     ↓
Service 核心逻辑
     ↓
Store Interfaces (repository.go) ← DB 适配器 (GORM Repo)

重构原则

不向后兼容

本项目为全新项目,尚未发布正式版。重构时直接废弃旧实现,不做兼容层,不保留任何向后兼容代码。遇到旧代码直接删除/覆盖,不新增 deprecatedlegacy 等标记或包装函数。

CMS 化

所有面向最终用户的硬编码文本(品牌名、标语、邮箱、空状态提示、系统消息等)应纳入 site_settings 表配置化,而非硬编码在模板或 Go 代码中。管理后台需提供对应输入项。

例外CSS 注释、JS debug 日志、代码中的技术常量不受此规则约束。