Files
APAW/.kilo/rules/go.md
¨NW¨ be4c586c8f feat: add Go Lang development coverage (Milestone #49)
- Add go-developer agent for Go backend development
- Add 8 Go skills: web-patterns, middleware, db-patterns,
  error-handling, security, testing, concurrency, modules
- Add go.md rules file
- Update capability-index.yaml with Go capabilities
- Complete backend coverage for both NodeJS and Go
2026-04-05 03:40:32 +01:00

5.5 KiB

Go Rules

Essential rules for Go development.

Code Style

  • Use gofmt for formatting
  • Use go vet for static analysis
  • Follow standard Go conventions
  • Run golangci-lint before commit
// ✅ Good
package user

import (
    "context"
    "errors"
    
    "github.com/gin-gonic/gin"
)

type Service struct {
    repo Repository
}

func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// ❌ Bad
package user
import "context"
import "errors"
import "github.com/gin-gonic/gin" // Wrong import grouping

Error Handling

  • Always handle errors
  • Use fmt.Errorf with %w for wrapping
  • Define custom error types
  • Never panic in library code
// ✅ Good
func GetUser(id int64) (*User, error) {
    user, err := repo.FindByID(id)
    if err != nil {
        return nil, fmt.Errorf("get user: %w", err)
    }
    return user, nil
}

// ❌ Bad
func GetUser(id int64) *User {
    user, _ := repo.FindByID(id) // Ignoring error
    return user
}

Context

  • Always pass context.Context as first parameter
  • Use context for cancellation and timeouts
  • Don't store context in structs
// ✅ Good
func (s *Service) GetByID(ctx context.Context, id int64) (*User, error) {
    return s.repo.FindByID(ctx, id)
}

// ❌ Bad
func (s *Service) GetByID(id int64) (*User, error) {
    return s.repo.FindByID(context.Background(), id)
}

Concurrency

  • Use sync.WaitGroup for goroutine coordination
  • Use channels for communication, not shared memory
  • Always close channels
  • Use context for cancellation
// ✅ Good
func Process(items []int) error {
    var wg sync.WaitGroup
    errCh := make(chan error, 1)
    
    for _, item := range items {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            if err := processItem(i); err != nil {
                select {
                case errCh <- err:
                default:
                }
            }
        }(item)
    }
    
    go func() {
        wg.Wait()
        close(errCh)
    }()
    
    return <-errCh
}

Testing

  • Write tests for all exported functions
  • Use table-driven tests
  • Use t.Parallel() where appropriate
  • Mock external dependencies
// ✅ Good: Table-driven test
func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name  string
        email string
        valid bool
    }{
        {"valid", "test@example.com", true},
        {"invalid", "invalid", false},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := ValidateEmail(tt.email)
            if got != tt.valid {
                t.Errorf("got %v, want %v", got, tt.valid)
            }
        })
    }
}

Project Structure

myapp/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── config/
│   ├── handlers/
│   ├── services/
│   ├── repositories/
│   └── models/
├── pkg/
│   └── public/
├── go.mod
└── go.sum

Security

  • Validate all inputs
  • Use parameterized queries
  • Never store passwords in plain text
  • Use environment variables for secrets
  • Set security headers
// ✅ Good: Parameterized query
func GetUser(db *sql.DB, id string) (*User, error) {
    query := "SELECT * FROM users WHERE id = ?"
    return db.QueryRow(query, id)
}

// ❌ Bad: SQL injection
func GetUser(db *sql.DB, id string) (*User, error) {
    query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", id)
    return db.QueryRow(query)
}

Dependencies

  • Use Go modules (go.mod)
  • Run go mod tidy regularly
  • Check for vulnerabilities: govulncheck ./...
  • Don't overuse external dependencies
# ✅ Good practices
go mod init myapp
go get github.com/gin-gonic/gin
go mod tidy
govulncheck ./...

# Update dependencies
go get -u ./...
go mod tidy

HTTP Handlers

  • Keep handlers thin
  • Return proper HTTP status codes
  • Use middleware for cross-cutting concerns
  • Validate input before processing
// ✅ Good: Thin handler
func (h *Handler) GetUser(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
        return
    }
    
    user, err := h.service.GetByID(c.Request.Context(), id)
    if err != nil {
        handleErrorResponse(c, err)
        return
    }
    
    c.JSON(http.StatusOK, gin.H{"user": user})
}

// ❌ Bad: Logic in handler
func GetUser(c *gin.Context) {
    db := getDB()
    var user User
    db.First(&user, c.Param("id"))
    c.JSON(200, user)
}

Interface Design

  • Accept interfaces, return concrete types
  • Keep interfaces small
  • Use interfaces for testing
// ✅ Good
type Repository interface {
    FindByID(ctx context.Context, id int64) (*User, error)
    Create(ctx context.Context, user *User) error
}

type UserService struct {
    repo Repository
}

// ❌ Bad: Too large interface
type Service interface {
    GetUser(id int64) (*User, error)
    CreateUser(user *User) error
    UpdateUser(user *User) error
    DeleteUser(id int64) error
    // ...many more methods
}

Logging

  • Use structured logging (zap, zerolog)
  • Include context in logs
  • Use appropriate log levels
  • Don't log sensitive data
// ✅ Good: Structured logging
logger.Info("user login",
    zap.String("user_id", userID),
    zap.String("ip", ip),
    zap.Time("timestamp", time.Now()),
)

// ❌ Bad: Printf logging
log.Printf("user %s logged in from %s", userID, ip)