Go conventions, best practices, and idiomatic patterns for production code
This skill provides guidelines for writing idiomatic, production-ready Go code following Go conventions, best practices, and patterns optimized for backend development with Gin and standard library.
// DO: Explicit error handling with context
result, err := processData(input)
if err != nil {
return fmt.Errorf("failed to process data: %w", err)
}
// DON'T: Ignore errors
result, _ := processData(input) // Never do this
// DO: Wrap errors for context
return fmt.Errorf("database operation failed: %w", err)
// DO: Use custom error types for domain-specific errors
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Message)
}
// DO: Use context for cancellation
func fetchData(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
// ...
}
// DO: Use channels with select for timeouts
select {
case result := <-ch:
return result, nil
case <-time.After(timeout):
return nil, errors.New("timeout")
case <-ctx.Done():
return nil, ctx.Err()
}
// DO: Use sync.WaitGroup for goroutine coordination
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1)
go func(t Task) {
defer wg.Done()
process(t)
}(task)
}
wg.Wait()
// DO: Small interfaces (Interface Segregation)
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// DO: Constructor patterns for complex structs
func NewService(db *sql.DB, cache *redis.Client, logger *zap.Logger) *Service {
return &Service{
db: db,
cache: cache,
logger: logger,
}
}
// DO: Method receivers - pointer vs value
func (s *Service) Update(data Data) error // Pointer for mutation
func (c Config) Validate() error // Value for read-only
myproject/
├── cmd/
│ └── api/ # Main application entry point
│ └── main.go
├── internal/
│ ├── domain/ # Business logic (entities, use cases)
│ ├── repository/ # Data access layer
│ ├── service/ # Business services
│ └── handler/ # HTTP handlers
├── pkg/
│ ├── middleware/ # Shared middleware
│ └── utils/ # Shared utilities
└── go.mod
// DO: Use dependency injection
func (h *UserHandler) CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user, err := h.service.CreateUser(c.Request.Context(), req)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusCreated, user)
}
// DO: Centralized error handling
func handleError(c *gin.Context, err error) {
switch e := err.(type) {
case *ValidationError:
c.JSON(http.StatusBadRequest, gin.H{"error": e.Error()})
case *NotFoundError:
c.JSON(http.StatusNotFound, gin.H{"error": e.Error()})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
}
}
// DO: Authentication middleware
func AuthMiddleware(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
return
}
claims, err := validateToken(token, secret)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Set("user", claims)
c.Next()
}
}
// DO: Logging middleware
func LoggerMiddleware(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
logger.Info("request",
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.Int("status", c.Writer.Status()),
zap.Duration("duration", time.Since(start)),
)
}
}
// DO: Repository pattern
type UserRepository interface {
Create(ctx context.Context, user *User) error
GetByID(ctx context.Context, id int64) (*User, error)
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id int64) error
List(ctx context.Context, opts ListOptions) ([]User, error)
}
// DO: Transaction handling
func (r *userRepo) CreateWithProfile(ctx context.Context, user *User, profile *Profile) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
if err := r.createUserTx(ctx, tx, user); err != nil {
return err
}
if err := r.createProfileTx(ctx, tx, profile); err != nil {
return err
}
return tx.Commit()
}
// DO: Table-driven tests
func TestCalculateTotal(t *testing.T) {
tests := []struct {
name string
items []Item
expected float64
}{
{
name: "empty cart",
items: []Item{},
expected: 0,
},
{
name: "single item",
items: []Item{{Price: 10, Quantity: 2}},
expected: 20,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := calculateTotal(tt.items)
assert.Equal(t, tt.expected, result)
})
}
}
// DO: Mock interfaces for testing
type MockUserService struct {
mock.Mock
}
func (m *MockUserService) GetUser(ctx context.Context, id int64) (*User, error) {
args := m.Called(ctx, id)
return args.Get(0).(*User), args.Error(1)
}
Use this skill when:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer