- 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
5.5 KiB
5.5 KiB
Go Rules
Essential rules for Go development.
Code Style
- Use
gofmtfor formatting - Use
go vetfor static analysis - Follow standard Go conventions
- Run
golangci-lintbefore 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.Errorfwith%wfor 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.Contextas 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.WaitGroupfor 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 tidyregularly - 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)