Skip to main content
Glama
go-development.mdc13.4 kB
--- description: "Go development: error handling, interfaces, concurrency, modules, and idiomatic patterns" globs: ["**/*.go", "**/go.mod", "**/go.sum"] alwaysApply: false --- # Go Development Patterns Idiomatic Go patterns focusing on simplicity, clarity, and concurrency. ## CRITICAL: Agentic-First Go Development ### Pre-Development Verification (MANDATORY) Before writing ANY Go code: ``` 1. CHECK GO INSTALLATION → run_terminal_cmd("go version") 2. VERIFY CURRENT GO VERSION (use web_search) → web_search("Go golang stable version December 2024") → Ensure go.mod uses correct version 3. CHECK EXISTING PROJECT → Does go.mod exist? Read it first! → What Go version is specified? → Are dependencies already present? 4. FOR NEW PROJECTS - USE go mod init → NEVER manually create go.mod → run_terminal_cmd("go mod init github.com/org/project") ``` ### CLI-First Go Development **ALWAYS use Go CLI tools:** ```bash # Project initialization (NEVER manually create go.mod) go mod init github.com/myorg/myproject # Add dependencies (NEVER manually edit go.mod) go get github.com/gin-gonic/gin@latest go get github.com/lib/pq@v1.10.9 # Verify and tidy go mod tidy go mod verify # Build and test go build ./... go test ./... go vet ./... # Format (ALWAYS before committing) go fmt ./... gofmt -s -w . ``` ### Post-Edit Verification After ANY Go code changes, ALWAYS run: ```bash # Verify compilation go build ./... # Check for issues go vet ./... # Run tests go test ./... # Ensure dependencies are correct go mod tidy ``` ### Common Go Syntax Traps (Avoid These!) ```go // WRONG: Missing error check result, _ := doSomething() // Never ignore errors! // CORRECT: Always handle errors result, err := doSomething() if err != nil { return fmt.Errorf("doSomething failed: %w", err) } // WRONG: Range loop variable capture for _, item := range items { go func() { process(item) // Bug! Captures loop variable }() } // CORRECT: Pass as parameter for _, item := range items { go func(i Item) { process(i) }(item) } // WRONG: Nil map write var m map[string]int m["key"] = 1 // Panic! // CORRECT: Initialize map m := make(map[string]int) m["key"] = 1 ``` --- ## Error Handling ### Basic Error Handling ```go // Always check errors result, err := doSomething() if err != nil { return fmt.Errorf("doSomething failed: %w", err) } // Wrap errors with context if err := db.Query(sql); err != nil { return fmt.Errorf("failed to query users: %w", err) } ``` ### Custom Error Types ```go // Simple error type type NotFoundError struct { Resource string ID string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s with id %s not found", e.Resource, e.ID) } // Checking error types if errors.Is(err, sql.ErrNoRows) { return &NotFoundError{Resource: "user", ID: id} } // Type assertions var notFound *NotFoundError if errors.As(err, &notFound) { // Handle not found case } ``` ### Error Handling Patterns ```go // Early return pattern func processData(data []byte) error { if len(data) == 0 { return errors.New("empty data") } parsed, err := parse(data) if err != nil { return fmt.Errorf("parse failed: %w", err) } if err := validate(parsed); err != nil { return fmt.Errorf("validation failed: %w", err) } return save(parsed) } // Error sentinel values var ( ErrNotFound = errors.New("not found") ErrUnauthorized = errors.New("unauthorized") ErrInvalidInput = errors.New("invalid input") ) ``` --- ## Interfaces ### Interface Design ```go // Small, focused interfaces type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer } // Accept interfaces, return structs func ProcessData(r io.Reader) (*Result, error) { data, err := io.ReadAll(r) if err != nil { return nil, err } return &Result{Data: data}, nil } ``` ### Interface Best Practices ```go // Define interfaces where they're used, not where implemented // In consumer package: type UserStore interface { Get(id string) (*User, error) Save(user *User) error } type UserService struct { store UserStore // Accepts any implementation } // Implementation in another package type PostgresUserStore struct { db *sql.DB } func (s *PostgresUserStore) Get(id string) (*User, error) { ... } func (s *PostgresUserStore) Save(user *User) error { ... } ``` ### Empty Interface and Type Assertions ```go // Avoid interface{} / any when possible // When necessary, use type switches func process(v any) string { switch x := v.(type) { case string: return x case int: return strconv.Itoa(x) case fmt.Stringer: return x.String() default: return fmt.Sprintf("%v", v) } } ``` --- ## Concurrency ### Goroutines and Channels ```go // Basic goroutine go func() { result := expensiveOperation() resultChan <- result }() // Buffered channel jobs := make(chan Job, 100) // Wait for completion var wg sync.WaitGroup for i := 0; i < numWorkers; i++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { process(job) } }() } wg.Wait() ``` ### Context for Cancellation ```go func fetchData(ctx context.Context, url string) ([]byte, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) } // Usage with timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() data, err := fetchData(ctx, "https://api.example.com") ``` ### Worker Pool Pattern ```go func WorkerPool(ctx context.Context, jobs <-chan Job, numWorkers int) <-chan Result { results := make(chan Result) var wg sync.WaitGroup for i := 0; i < numWorkers; i++ { wg.Add(1) go func() { defer wg.Done() for { select { case job, ok := <-jobs: if !ok { return } results <- process(job) case <-ctx.Done(): return } } }() } go func() { wg.Wait() close(results) }() return results } ``` ### Mutex and Atomic Operations ```go type Counter struct { mu sync.Mutex value int } func (c *Counter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.value++ } func (c *Counter) Value() int { c.mu.Lock() defer c.mu.Unlock() return c.value } // For simple counters, use atomic type AtomicCounter struct { value atomic.Int64 } func (c *AtomicCounter) Increment() { c.value.Add(1) } ``` --- ## Structs and Methods ### Struct Design ```go type User struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` CreatedAt time.Time `json:"created_at"` } // Constructor function func NewUser(name, email string) *User { return &User{ ID: uuid.New().String(), Name: name, Email: email, CreatedAt: time.Now(), } } ``` ### Method Receivers ```go // Value receiver - doesn't modify, safe for concurrent use func (u User) FullName() string { return u.FirstName + " " + u.LastName } // Pointer receiver - can modify, more efficient for large structs func (u *User) UpdateEmail(email string) error { if !isValidEmail(email) { return errors.New("invalid email") } u.Email = email return nil } ``` ### Embedded Structs ```go type Timestamps struct { CreatedAt time.Time UpdatedAt time.Time } type User struct { Timestamps // Embedded - fields promoted ID string Name string } // Access promoted fields directly user.CreatedAt = time.Now() ``` --- ## Testing ### Table-Driven Tests ```go func TestAdd(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive", 2, 3, 5}, {"negative", -1, -1, -2}, {"zero", 0, 0, 0}, {"mixed", -1, 1, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }) } } ``` ### Subtests and Parallel ```go func TestUserService(t *testing.T) { t.Run("Create", func(t *testing.T) { t.Parallel() // test create }) t.Run("Get", func(t *testing.T) { t.Parallel() // test get }) } ``` ### Mocking with Interfaces ```go // Interface in production code type UserRepository interface { Get(id string) (*User, error) Save(user *User) error } // Mock in test type MockUserRepository struct { users map[string]*User } func (m *MockUserRepository) Get(id string) (*User, error) { user, ok := m.users[id] if !ok { return nil, ErrNotFound } return user, nil } func TestUserService_GetUser(t *testing.T) { repo := &MockUserRepository{ users: map[string]*User{ "1": {ID: "1", Name: "Test"}, }, } service := NewUserService(repo) user, err := service.Get("1") if err != nil { t.Fatalf("unexpected error: %v", err) } if user.Name != "Test" { t.Errorf("expected name 'Test', got '%s'", user.Name) } } ``` --- ## Project Structure ### Standard Layout ``` myproject/ cmd/ myapp/ main.go # Application entry point internal/ # Private application code config/ handler/ service/ repository/ pkg/ # Public library code utils/ api/ # API definitions (OpenAPI, protobuf) web/ # Web assets scripts/ # Build/CI scripts go.mod go.sum ``` ### Package Organization ```go // cmd/myapp/main.go package main import ( "myproject/internal/config" "myproject/internal/handler" "myproject/internal/service" ) func main() { cfg := config.Load() svc := service.New(cfg) h := handler.New(svc) http.ListenAndServe(":8080", h) } ``` --- ## HTTP Handlers ### Handler Functions ```go func handleGetUser(svc *UserService) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") user, err := svc.Get(r.Context(), id) if err != nil { if errors.Is(err, ErrNotFound) { http.Error(w, "user not found", http.StatusNotFound) return } http.Error(w, "internal error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } } ``` ### Middleware ```go func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() // Wrap response writer to capture status wrapped := &responseWriter{ResponseWriter: w, status: http.StatusOK} next.ServeHTTP(wrapped, r) log.Printf("%s %s %d %v", r.Method, r.URL.Path, wrapped.status, time.Since(start)) }) } type responseWriter struct { http.ResponseWriter status int } func (w *responseWriter) WriteHeader(status int) { w.status = status w.ResponseWriter.WriteHeader(status) } ``` --- ## Go Modules ### go.mod ```go module github.com/myorg/myproject go 1.21 require ( github.com/go-chi/chi/v5 v5.0.10 github.com/lib/pq v1.10.9 ) require ( // indirect dependencies ) ``` ### Common Commands ```bash # Initialize module go mod init github.com/myorg/myproject # Add dependencies go get github.com/go-chi/chi/v5 # Tidy dependencies go mod tidy # Update dependencies go get -u ./... # Vendor dependencies go mod vendor ``` --- ## Code Style ### Naming Conventions ```go // Exported (public) - PascalCase type UserService struct { ... } func NewUserService() *UserService { ... } // Unexported (private) - camelCase type userCache struct { ... } func validateEmail(email string) bool { ... } // Acronyms - all caps var httpClient *http.Client type JSONResponse struct { ... } userID := "123" // not userId ``` ### Comments ```go // Package users provides user management functionality. package users // User represents a registered user in the system. type User struct { // ID is the unique identifier for the user. ID string // Name is the user's display name. Name string } // NewUser creates a new user with the given name. // It returns an error if the name is empty. func NewUser(name string) (*User, error) { ... } ``` ### Formatting ```bash # Always format code go fmt ./... # Lint code golangci-lint run ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/madebyaris/rakitui-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server