encore-go-code-review by encoredev/skills
npx skills add https://github.com/encoredev/skills --skill encore-go-code-review审查 Encore Go 代码时,请检查以下常见问题:
// 错误:在函数内部声明基础设施
func setup() {
db := sqldb.NewDatabase("mydb", sqldb.DatabaseConfig{...})
topic := pubsub.NewTopic[*Event]("events", pubsub.TopicConfig{...})
}
// 正确:包级声明
var db = sqldb.NewDatabase("mydb", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
var topic = pubsub.NewTopic[*Event]("events", pubsub.TopicConfig{
DeliveryGuarantee: pubsub.AtLeastOnce,
})
// 错误:缺少 context
//encore:api public method=GET path=/users/:id
func GetUser(params *GetUserParams) (*User, error) {
// ...
}
// 正确:Context 作为第一个参数
//encore:api public method=GET path=/users/:id
func GetUser(ctx context.Context, params *GetUserParams) (*User, error) {
// ...
}
// 错误:字符串插值
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
rows, err := db.Query(ctx, query)
// 正确:参数化查询
rows, err := sqldb.Query[User](ctx, db, `
SELECT * FROM users WHERE email = $1
`, email)
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// 错误:返回非指针结构体
//encore:api public method=GET path=/users/:id
func GetUser(ctx context.Context, params *GetUserParams) (User, error) {
// ...
}
// 正确:返回结构体指针
//encore:api public method=GET path=/users/:id
func GetUser(ctx context.Context, params *GetUserParams) (*User, error) {
// ...
}
// 错误:忽略错误
user, _ := sqldb.QueryRow[User](ctx, db, query, id)
// 正确:处理错误
user, err := sqldb.QueryRow[User](ctx, db, query, id)
if err != nil {
return nil, err
}
// 有风险:返回 nil 但没有适当的错误
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 返回通用错误
}
return user, nil
}
// 更好:专门检查未找到的情况
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
}
// 检查:这个定时任务端点应该是公开的吗?
//encore:api public method=POST path=/internal/cleanup
func CleanupJob(ctx context.Context) error {
// ...
}
// 更好:内部端点使用 private
//encore:api private
func CleanupJob(ctx context.Context) error {
// ...
}
// 有风险:非幂等(pubsub 提供至少一次交付保证)
var _ = pubsub.NewSubscription(OrderCreated, "process-order",
pubsub.SubscriptionConfig[*OrderCreatedEvent]{
Handler: func(ctx context.Context, event *OrderCreatedEvent) error {
return chargeCustomer(ctx, event.OrderID) // 可能重复收费!
},
},
)
// 更安全:处理前检查
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 // 已处理
}
return chargeCustomer(ctx, event.OrderID)
},
},
)
// 错误:未关闭结果集
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, nil
}
// 正确:始终关闭结果集
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()
}
context.Context 作为第一个参数$1、$2 等)sqldb.ErrNoRowsprivate 而非 publicdefer rows.Close() 关闭1_name.up.sql)审查时,按以下格式报告问题:
[关键] [文件:行号] 问题描述
[警告] [文件:行号] 关注点描述
[良好] 观察到的显著良好实践
每周安装量
123
代码仓库
GitHub 星标数
20
首次出现时间
2026年1月21日
安全审计
已安装于
opencode103
gemini-cli102
codex102
claude-code95
github-copilot84
cursor82
When reviewing Encore Go code, check for these common issues:
// 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,
})
// 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) {
// ...
}
// 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)
// 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) {
// ...
}
// 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
}
// 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
}
// 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 {
// ...
}
// 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)
},
},
)
// 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()
}
context.Context as first parameter$1, $2, etc.)sqldb.ErrNoRows checked where appropriateprivate not publicdefer rows.Close()1_name.up.sql)When reviewing, report issues as:
[CRITICAL] [file:line] Description of issue
[WARNING] [file:line] Description of concern
[GOOD] Notable good practice observed
Weekly Installs
123
Repository
GitHub Stars
20
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode103
gemini-cli102
codex102
claude-code95
github-copilot84
cursor82
智能代码探索工具smart-explore:AST解析结构化代码搜索与导航
735 周安装