mcpField
E
encore-go-code-review

encore-go-code-review

github.com/encoredev测试与质量30567 次安装2 天前更新
代码仓库

关于 encore-go-code-review

用于审查 Encore Go 代码的检查清单,覆盖基础设施、API、数据库和 Pub/Sub 常见问题,帮助审查者发现实现风险并核对约定。

主要功能

  1. 01检查基础设施声明位置
  2. 02核对 API 上下文和返回类型
  3. 03标记 SQL 注入与忽略错误
  4. 04检查 Pub/Sub 处理器幂等性
  5. 05检查关闭查询行和迁移规范

适用场景

  1. 01审查 Encore Go 代码变更
  2. 02发现 SQL 与错误处理风险
  3. 03核对 API 和 Pub/Sub 约定

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/encoredev/skills --skill encore-go-code-review

安装后在 Claude Code 中输入/encore-go-code-review确认已加载。

详细介绍

Encore Go 代码审查

说明

审查 Encore Go 代码时,请检查以下常见问题:

严重问题

1. 在函数内初始化基础设施
// WRONG: Infrastructure declared inside function
func setup() {
    db := sqldb.NewDatabase("mydb", sqldb.DatabaseConfig{...})
    topic := pubsub.NewTopic[*Event]("events", pubsub.TopicConfig{...})
}

// CORRECT: Package level declaration
var db = sqldb.NewDatabase("mydb", sqldb.DatabaseConfig{
    Migrations: "./migrations",
})

var topic = pubsub.NewTopic[*Event]("events", pubsub.TopicConfig{
    DeliveryGuarantee: pubsub.AtLeastOnce,
})
2. 缺少 Context 参数
// WRONG: Missing context
//encore:api public method=GET path=/users/:id
func GetUser(params *GetUserParams) (*User, error) {
    // ...
}

// CORRECT: Context as first parameter
//encore:api public method=GET path=/users/:id
func GetUser(ctx context.Context, params *GetUserParams) (*User, error) {
    // ...
}
3. SQL 注入风险
// WRONG: String interpolation
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
rows, err := db.Query(ctx, query)

// CORRECT: Parameterized query
rows, err := sqldb.Query[User](ctx, db, `
    SELECT * FROM users WHERE email = $1
`, email)
4. 返回类型错误
// WRONG: Returning non-pointer struct
//encore:api public method=GET path=/users/:id
func GetUser(ctx context.Context, params *GetUserParams) (User, error) {
    // ...
}

// CORRECT: Return pointer to struct
//encore:api public method=GET path=/users/:id
func GetUser(ctx context.Context, params *GetUserParams) (*User, error) {
    // ...
}
5. 忽略错误
// WRONG: Ignoring error
user, _ := sqldb.QueryRow[User](ctx, db, query, id)

// CORRECT: Handle error
user, err := sqldb.QueryRow[User](ctx, db, query, id)
if err != nil {
    return nil, err
}

警告问题

6. 未检查 ErrNoRows
// RISKY: Returns nil without proper error
func getUser(ctx context.Context, id string) (*User, error) {
    user, err := sqldb.QueryRow[User](ctx, db, `
        SELECT * FROM users WHERE id = $1
    `, id)
    if err != nil {
        return nil, err  // ErrNoRows returns generic error
    }
    return user, nil
}

// BETTER: Check for not found specifically
import "errors"

func getUser(ctx context.Context, id string) (*User, error) {
    user, err := sqldb.QueryRow[User](ctx, db, `
        SELECT * FROM users WHERE id = $1
    `, id)
    if errors.Is(err, sqldb.ErrNoRows) {
        return nil, &errs.Error{
            Code:    errs.NotFound,
            Message: "user not found",
        }
    }
    if err != nil {
        return nil, err
    }
    return user, nil
}
7. 公开的内部端点
// CHECK: Should this cron endpoint be public?
//encore:api public method=POST path=/internal/cleanup
func CleanupJob(ctx context.Context) error {
    // ...
}

// BETTER: Use private for internal endpoints
//encore:api private
func CleanupJob(ctx context.Context) error {
    // ...
}
8. 非幂等的订阅处理程序
// RISKY: Not idempotent (pubsub has at-least-once delivery)
var _ = pubsub.NewSubscription(OrderCreated, "process-order",
    pubsub.SubscriptionConfig[*OrderCreatedEvent]{
        Handler: func(ctx context.Context, event *OrderCreatedEvent) error {
            return chargeCustomer(ctx, event.OrderID)  // Could charge twice!
        },
    },
)

// SAFER: Check before processing
var _ = pubsub.NewSubscription(OrderCreated, "process-order",
    pubsub.SubscriptionConfig[*OrderCreatedEvent]{
        Handler: func(ctx context.Context, event *OrderCreatedEvent) error {
            order, err := getOrder(ctx, event.OrderID)
            if err != nil {
                return err
            }
            if order.Status != "pending" {
                return nil  // Already processed
            }
            return chargeCustomer(ctx, event.OrderID)
        },
    },
)
9. 未关闭查询行
// WRONG: Rows not closed
func listUsers(ctx context.Context) ([]*User, error) {
    rows, err := sqldb.Query[User](ctx, db, `SELECT * FROM users`)
    if err != nil {
        return nil, err
    }
    // Missing: defer rows.Close()
    
    var users []*User
    for rows.Next() {
        users = append(users, rows.Value())
    }
    return users, nil
}

// CORRECT: Always close rows
func listUsers(ctx context.Context) ([]*User, error) {
    rows, err := sqldb.Query[User](ctx, db, `SELECT * FROM users`)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var users []*User
    for rows.Next() {
        users = append(users, rows.Value())
    }
    return users, rows.Err()
}

审查清单

  • 所有基础设施都在包级别初始化
  • 所有 API 端点都将 context.Context 作为第一个参数
  • SQL 使用参数化查询($1、$2 等)
  • 响应类型为指针
  • 错误均得到处理,未被忽略
  • 在适当情况下检查 sqldb.ErrNoRows
  • 内部端点使用 private 而非 public
  • 订阅处理程序具备幂等性
  • 使用 defer rows.Close() 关闭查询行
  • 迁移文件遵循命名约定(1_name.up.sql)

输出格式

审查时,请按以下格式报告问题:

[CRITICAL] [file:line] Description of issue
[WARNING] [file:line] Description of concern  
[GOOD] Notable good practice observed

评论

支持 Markdown