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. 02APIのコンテキストと戻り値を確認
  3. 03SQLインジェクションとエラー無視を検出
  4. 04Pub/Subハンドラーの冪等性を確認
  5. 05行のクローズとマイグレーションを確認

活用シーン

  1. 01Encore Goのコード変更レビュー
  2. 02SQLとエラー処理のリスク検出
  3. 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

コメント