首页
/ ECC 中 Go 编程模式规则详解:从 .cursor/rules/golang-patterns.md 看 Cursor 规则、多端同步与 golang-patterns 技能链

ECC 中 Go 编程模式规则详解:从 .cursor/rules/golang-patterns.md 看 Cursor 规则、多端同步与 golang-patterns 技能链

2026-09-06 18:52:57作者:董宙帆

本文以 ECC 仓库中 golang-patterns.md 这条 Cursor 规则文件为主体,完整解读它如何通过 globs 触发机制为 *.gogo.modgo.sum 文件注入 Go 惯用模式约束,并深入展开它所编码的三个核心设计模式(Functional Options、小接口、构造函数依赖注入)及其引用的 golang-patterns 技能全貌——涵盖并发、错误处理、包组织与 Lint 配置——帮助你在 Cursor/Codex/Kiro 等 Agent 环境下为 Go 项目建立一套可复制的编码约束体系。

一、规则文件的定位:ECC 语言级规则在 Cursor 中的落地形态

.cursor/rules/ 目录是 ECC 面向 Cursor IDE 的规则适配层。目录中与 Go 相关的规则共有五条,分别覆盖编码风格、钩子、模式、安全与测试(golang-coding-style.mdgolang-hooks.mdgolang-patterns.mdgolang-security.mdgolang-testing.md),此外还有 common-* 系列的通用规则作为各语言规则的基座。

1.1 文件头(Front Matter):glob 触发与按需注入

golang-patterns.md 的完整文件头如下:

---
description: "Go patterns extending common rules"
globs: ["**/*.go", "**/go.mod", "**/go.sum"]
alwaysApply: false
---

这三个字段共同决定了规则的运行行为:

字段 取值 作用
description Go patterns extending common rules 声明该规则是通用模式规则的 Go 特化扩展
globs **/*.go**/go.mod**/go.sum 仅当 Agent 上下文涉及 Go 源码或模块文件时,规则才被加载进上下文
alwaysApply false 规则不常驻系统提示,属于"按需注入",避免无关语言场景下的 token 消耗

alwaysApply: false + 精确 globs 的组合是 ECC 规则体系控制上下文预算的关键手段:当你用 Cursor 编辑一个 Python 文件时,这条 Go 规则完全不会占用上下文窗口;一旦打开 handler.gogo.mod,规则内容(含下述三段模式)就会自动生效。

1.2 多 Harness 同步:一份规则,多端生效

从源码结构看,.cursor/rules/golang-patterns.md 并非手工维护的孤立文件,而是由 sync-ecc-to-codex.sh 脚本同步生成的产物——脚本中定义了 CURSOR_RULES_DIR="$REPO_ROOT/.cursor/rules",并在同步清单里显式列出了 $CURSOR_RULES_DIR/golang-patterns.md(见 同步脚本)。

同一份规则内容在仓库中还有两个"同构镜像",各自使用目标 Harness 的触发格式:

三者的正文完全一致,差异仅在文件头声明的触发语法。这种"一处编写、多端同步"的设计正是项目名中 "harness"(Agent 宿主环境)一词的体现:ECC 把同一套工程约束适配到 Cursor、Kiro、Codex 等不同 Agent 平台上,规则正文本身与平台无关。

规则头部还写明 "This file extends the common patterns rule",即它在语义上继承自 rules/common/patterns.md——通用规则定义了 Repository 模式、统一 API 响应封装等跨语言约定,Go 规则在其上叠加语言特化的设计模式。

二、模式一:Functional Options(函数式选项)

文档给出的完整示例是一个 Server 的可选端口配置:

type Option func(*Server)

func WithPort(port int) Option {
    return func(s *Server) { s.port = port }
}

func NewServer(opts ...Option) *Server {
    s := &Server{port: 8080}
    for _, opt := range opts {
        opt(s)
    }
    return s
}

这个模式的要点在于:Option 本身就是一个接收并修改 *Server 的闭包函数,构造器先用默认值port: 8080)初始化结构体,再依次应用调用方传入的选项闭包。相比 Go 没有具名参数、又缺乏默认参数的语言限制,这种写法让 NewServer()(全默认)、NewServer(WithPort(9090))(局部覆盖)都能自然表达,且后续新增选项字段不会破坏既有调用签名。

所引用的 golang-patterns 技能中给出了该模式的完整工程化形态(见 SKILL.md):

type Server struct {
    addr    string
    timeout time.Duration
    logger  *log.Logger
}

type Option func(*Server)

func WithTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.timeout = d
    }
}

func WithLogger(l *log.Logger) Option {
    return func(s *Server) {
        s.logger = l
    }
}

func NewServer(addr string, opts ...Option) *Server {
    s := &Server{
        addr:    addr,
        timeout: 30 * time.Second, // default
        logger:  log.Default(),    // default
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
server := NewServer(":8080",
    WithTimeout(60*time.Second),
    WithLogger(customLogger),
)

可以看到规则文件的精简示例与技能中的完整实现是一致递进关系:必填参数(addr)走位置参数,可选参数(timeoutlogger)全部收敛到变参 opts ...Option,每个选项都附带注释标明默认值。这与规则文件中 WithPort 的写法遵循同一范式。

三、模式二:Small Interfaces(小接口)——在"使用处"定义接口

文档原文只有一句话,但它是整个 Go 规则中最具辨识度的原则:

Define interfaces where they are used, not where they are implemented.(在接口被使用的地方定义接口,而不是在被实现的地方。)

配套的 rules/golang/coding-style.md 进一步量化了这一约束:"Keep interfaces small (1-3 methods)",并把它与 "Accept interfaces, return structs"(接口作为入参、具体类型作为返回值)并列写入设计原则。

golang-patterns 技能把这句话展开成了可操作的代码范式(见 SKILL.md):

// In the consumer package, not the provider
package service

// UserStore defines what this service needs
type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type Service struct {
    store UserStore
}

// Concrete implementation can be in another package
// It doesn't need to know about this interface

即:service 包只声明它"需要"的 UserStore 接口,而提供实现的存储包完全不需要感知这个接口的存在——依赖方向因此被反转,存储层不会被业务接口耦合。技能中还给出了配套的两个进阶技巧:

组合接口SKILL.md):以单方法接口(Reader/Writer/Closer)为原子单元,按需组合出 ReadWriteCloser

用类型断言表达可选行为SKILL.md):

type Flusher interface {
    Flush() error
}

func WriteAndFlush(w io.Writer, data []byte) error {
    if _, err := w.Write(data); err != nil {
        return err
    }

    // Flush if supported
    if f, ok := w.(Flusher); ok {
        return f.Flush()
    }
    return nil
}

调用方不需要为"可能支持 Flush"而把接口做大,能力探测由运行时断言完成。

四、模式三:Dependency Injection(构造函数依赖注入)

文档给出的示例:

func NewUserService(repo UserRepository, logger Logger) *UserService {
    return &UserService{repo: repo, logger: logger}
}

含义是:依赖(数据访问 repo、日志 logger)通过 New* 构造函数的参数显式传入,并绑定到结构体字段上,而不是在函数内部 import 全局单例或 init() 里偷偷初始化。注意此示例中 UserRepositoryLogger 都是接口——这与上一节"在使用处定义接口"形成闭环:UserService 所在的包自己声明或引用这两个小接口,测试时只需传入 mock 实现即可。

golang-patterns 技能为这条规则提供了反面教材(见 SKILL.md),即"避免包级状态":

// Bad: Global mutable state
var db *sql.DB

func init() {
    db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}

// Good: Dependency injection
type Server struct {
    db *sql.DB
}

func NewServer(db *sql.DB) *Server {
    return &Server{db: db}
}

包级 var dbinit() 的组合让依赖不可见、不可替换,且忽略了 sql.Open 的返回值错误——而构造函数注入把数据库句柄变成显式的、可测试的依赖,正是文档那条 DI 规则要避免的反面。

五、引用链扩展:golang-patterns 技能的完整模式集

文档末尾的 Reference 一节指向 golang-patterns 技能,这是规则文件刻意保持精简的设计:Cursor 规则只承载最高频的三条模式,把深度内容交给按需激活的技能 skills/golang-patterns/SKILL.md。该技能声明的激活时机是"编写新 Go 代码、评审 Go 代码、重构既有 Go 代码、设计 Go 包/模块"四种场景,其内容可以按四个维度归纳。

5.1 核心设计原则

  • 简单优于巧妙:错误处理直接 return nil, fmt.Errorf("get user %s: %w", id, err),反对用匿名函数包裹控制流;
  • 让零值可用:如带 sync.MutexCounter 结构体声明出来即可用,而 counts map[string]int 这类零值会 panic 的字段设计被明确列为反例(见 SKILL.md);
  • 接口入参、具体类型出参ProcessData(r io.Reader) (*Result, error) 是正例,返回 io.Reader 是反例。

5.2 错误处理

技能规定了错误处理的四个子模式(SKILL.md):

  1. 带上下文的错误包装fmt.Errorf("load config %s: %w", path, err)——每跨一层就补充一层语境,%w 保留可追踪性;
  2. 领域自定义错误类型ValidationError{Field, Message} 结构体错误,外加 ErrNotFoundErrUnauthorized 等哨兵错误;
  3. errors.Is / errors.As 判定:哨兵错误用 errors.Is(err, sql.ErrNoRows),结构化错误用 errors.As(err, &validationErr) 解包取字段,禁止 err == target 直接比较;
  4. 绝不吞错result, _ := doSomething() 是明确反例;确需忽略时写作 _ = writer.Close() 并注释说明这是"尽力而为的清理"。

5.3 并发

技能收录了五个并发模式,均含完整可运行示例(SKILL.md):

  • Worker PoolL177-L195):sync.WaitGroup 拉起 numWorkers 个 goroutine 从 jobs 通道取任务,处理完 close(results)
  • Context 超时L198-L218):context.WithTimeout + http.NewRequestWithContext,请求级超时随 ctx 传播;
  • 优雅关机L220-L239):signal.Notify 监听 SIGINT/SIGTERM,收到信号后带 30 秒超时的 server.Shutdown(ctx)
  • errgroup 协调L241-L267):errgroup.WithContext 并发抓取多个 URL,任一失败即取消其余任务,并注意 i, url := i, url 捕获循环变量;
  • 防止 goroutine 泄漏L269-L297):无缓冲通道上"无接收者则永久阻塞"被标记为泄漏,正确写法是缓冲通道 + select { case ch <- data: case <-ctx.Done(): }

5.4 包组织、内存与工具链

  • 标准目录布局cmd/myapp/main.go 入口、internal/{handler,service,repository,config} 分层、pkg/client 对外客户端、api/v1 契约定义(SKILL.md);
  • 性能三招:容量已知时 make([]Result, 0, len(items)) 预分配 slice、高频对象复用 sync.Pool、循环内字符串拼接改用 strings.Builderstrings.JoinL495-L569);
  • Lint 基线:技能给出了包含 errcheckgovet(启用 shadow)、staticcheckgofmtgoimportsunparam 等 linter 的 .golangci.yml 推荐配置,以及 go build/test -race/vetgo mod tidy/verifygolangci-lint run 的完整命令集(L571-L597);
  • 惯用法速查表:以 "Accept interfaces, return structs"、"Errors are values"、"Don't communicate by sharing memory"、"Return early" 等 8 条速查条目收尾(L627-L638),并列出裸返回、panic 作控制流、context 塞进结构体等反模式。

六、执行闭环:go-reviewer Agent 如何按这套模式审查 Go 代码

规则定义"应该怎么写",ECC 中配套的 agents/go-reviewer.md 则负责"违规时指出"。该 Agent 声明了固定的审查启动流程:先 git diff -- '*.go' 圈定改动面,再运行 go vet ./...staticcheck ./...,随后按优先级审查。其 CRITICAL 级"Error Handling"检查项与 golang-patterns 技能逐条对应:

  • _ 丢弃错误(对应技能"Never Ignore Errors"一节);
  • 缺少 fmt.Errorf("context: %w", err) 包装(对应"Error Wrapping with Context");
  • 可恢复错误使用 panic(对应反模式清单"Using panic for control flow");
  • err == target 而非 errors.Is(err, target) 判断错误(对应"errors.Is and errors.As"一节)。

也就是说,golang-patterns.md 这条 Cursor 规则、golang-patterns 技能、go-reviewer Agent 三者构成了一条从"编写时注入约束"到"评审时按同一条标准打分"的闭环,且同一套约束经由同步脚本分发到 Cursor、Kiro 等各端。

七、小结

golang-patterns.md 这条规则文件虽然正文只有约 40 行,却体现了 ECC 规则体系的三层结构:

  1. 触发层globs + alwaysApply: false 保证规则只在 Go 相关上下文激活,且经 scripts/sync-ecc-to-codex.shrules/golang/patterns.md.kiro/steering/golang-patterns.md 保持内容一致;
  2. 约束层:Functional Options(默认值 + 可变选项闭包)、小接口(在使用处定义、1–3 个方法)、构造函数注入(依赖显式可见、可替换)三条模式,均可直接复制到实际项目;
  3. 深化层:通过 Reference 指向的 golang-patterns 技能 承接错误处理、并发、包组织与 Lint 配置的完整内容,并由 go-reviewer Agent 在评审环节执行同一套标准。

对于在自己的仓库中引入类似规则链的开发者,可复制的路径是:先写一条 glob 触发、正文精简的语言规则,再把深度内容放入可检索的技能文档,最后用评审 Agent 把规则中的反例清单变成可执行的审查项。

登录后查看全文
热门项目推荐
相关项目推荐