encore-go-code-review

Par encoredev · skills

Examiner le code Encore Go pour les meilleures pratiques.

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

Examen du Code Go Encore

Instructions

Lors de l'examen du code Go Encore, vérifiez ces problèmes courants :

Problèmes Critiques

1. Infrastructure à l'Intérieur des Fonctions

// 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. Paramètre Context Manquant

// 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. Risque d'Injection 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. Types de Retour Incorrects

// 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. Erreurs Non Traitées

// 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
}

Problèmes d'Avertissement

6. Pas de Vérification de 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. Endpoints Internes Publics

// 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. Gestionnaires d'Abonnement Non-Idempotents

// 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. Rows de Requête Non Fermées

// 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()
}

Checklist d'Examen

  • [ ] Toute l'infrastructure au niveau du package
  • [ ] Tous les endpoints API ont context.Context en premier paramètre
  • [ ] SQL utilise des requêtes paramétrées ($1, $2, etc.)
  • [ ] Les types de réponse sont des pointeurs
  • [ ] Les erreurs sont traitées, pas ignorées
  • [ ] sqldb.ErrNoRows est vérifiée le cas échéant
  • [ ] Les endpoints internes utilisent private et non public
  • [ ] Les gestionnaires d'abonnement sont idempotents
  • [ ] Les rows de requête sont fermées avec defer rows.Close()
  • [ ] Les migrations respectent la convention de nommage (1_name.up.sql)

Format de Sortie

Lors de l'examen, signalez les problèmes comme suit :

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

Skills similaires