encore-go-code-review について
Encore Goコードをレビューするためのチェックリストです。インフラ、API、データベース、Pub/Subのよくある問題を確認し、実装上のリスクや規約を点検します。
主な機能
- 01インフラ宣言の位置を確認
- 02APIのコンテキストと戻り値を確認
- 03SQLインジェクションとエラー無視を検出
- 04Pub/Subハンドラーの冪等性を確認
- 05行のクローズとマイグレーションを確認
活用シーン
- 01Encore Goのコード変更レビュー
- 02SQLとエラー処理のリスク検出
- 03APIと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がチェックされている - 内部エンドポイントが
publicではなくprivateを使用している - サブスクリプションハンドラーが冪等である
- クエリ行が
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 対応