golang-development
- Repo stars 0
- Author updated Live
- Author repo skills-registry
- Domain
- AI
- Compatible agents
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- Trust score
- 88 / 100 · community maintained
- Author / version / license
- @tomevault-io · no license declared
- Token usage
- Heavy
- Setup complexity
- Manual integration
- External API key
- Not required
- Operating systems
- Docker
- Runtime requirements
- Docker
- Permissions
-
- Read-only
- Write / modify
- Shell exec
- Env read
- Network behavior
- External requests
- Install commands
- 26 variants
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
---
name: golang-development
description: Write and review Go code with idiomatic project structure, errors, concurrency, testing, and per…
category: ai
runtime: Docker
---
# golang-development output preview
## PART A: Task fit
- Use case: Write and review Go code with idiomatic project structure, errors, concurrency, testing, and performance guidance. Use when this capability is needed. Use this skill when writing, reviewing, or debugging Go code. Covers idiomatic Go patterns, project structure, concurrency, testing, and production-ready practices. makes outbound network calls; runs on Doc….
- Inputs: target material, constraints, expected output, and acceptance criteria.
- Evidence boundary: follow “When to Use / Go Philosophy / Project Structure” and do not present inference as author intent.
## PART B: Execution result
- **01** The card summarizes the use case; runtime output centers on “Write and review Go code with idiomatic project structure, errors, concurrency, testing, and performance guidance. Use when this capability is needed. Use this skill when writing, reviewing, or debugging Go code. Covers idiomatic Go patterns, project structure, concurrency, testing, and production-ready practices. makes outbound network calls; runs on Doc…”.
- **02** When the source has headings, the agent prioritizes “When to Use / Go Philosophy / Project Structure” so the result follows the author’s structure.
- **03** Typical output includes task judgment, concrete steps, required commands or file edits, validation, and follow-up options.
- **04** Risk context follows the fingerprint: read files, write/modify files, run shell commands, read environment variables; may access external network resources; usually needs no extra API key.
## Running Rules
- read files, write/modify files, run shell commands, read environment variables; may access external network resources; usually needs no extra API key.
- Validate with a small sample before expanding scope.
- Return the result, validation criteria, and next iteration options. The source does not require a stable slash command. After installation, invoke the skill by name and describe the task.
Name target files or source material, expected output, forbidden changes, and whether network or shell access is allowed. Permission fingerprint: read files, write/modify files, run shell commands, read environment variables.
Start with a small task and check whether the result follows “When to Use / Go Philosophy / Project Structure”. Inspect diffs, logs, previews, or tests before expanding scope.
Confirm the final output includes a concrete result, evidence, and next action. If it stays generic, tighten inputs, boundaries, and acceptance criteria.
---
name: golang-development
description: Write and review Go code with idiomatic project structure, errors, concurrency, testing, and per…
category: ai
source: tomevault-io/skills-registry
---
# golang-development
## When to use
- Write and review Go code with idiomatic project structure, errors, concurrency, testing, and performance guidance. Use…
- Use it when the task has clear inputs, repeatable steps, and validation criteria.
## What to provide
- Target material, scope, expected result, and forbidden changes.
- Whether network, commands, file writes, or external services are allowed.
## Execution rules
- Organize steps around “When to Use / Go Philosophy / Project Structure” and keep inference separate from source facts.
- read files, write/modify files, run shell commands, read environment variables; may access external network resources; usually needs no extra API key.
- Validate with a small sample before expanding the task.
## Output requirements
- Return the deliverable, key evidence, validation method, and next action.
- Mark missing information as unknown; do not invent commands, platforms, or dependencies. The author source anchors workflow facts; repository files anchor sources and commands; Fluxly only adds fit, limitations, and quality judgment.
skill "golang-development" {
input -> user goal + target files + boundaries + acceptance criteria
context -> When to Use / Go Philosophy / Project Structure
rules -> SKILL.md triggers / order / output contract
runtime -> Docker | read files, write/modify files, run shell commands, read environment variables | may access external network resources
guardrails -> usually needs no extra API key + small-sample validation + diff/log review
output -> copyable result + checklist + next iteration
} Go (Golang) - Patterns and Guidance
Use this skill when writing, reviewing, or debugging Go code. Covers idiomatic Go patterns, project structure, concurrency, testing, and production-ready practices.
When to Use
- Writing new Go packages, services, or CLI tools
- Reviewing or refactoring existing Go code for idiomatic style
- Debugging Go concurrency, error handling, or performance issues
- Setting up Go project structure, module layout, or test harness
Go Philosophy
Core principles:
- Simplicity over cleverness
- Explicit over implicit
- Composition over inheritance
- Clear is better than clever
- Errors are values
- Concurrency is not parallelism
Key idioms:
- "Don't communicate by sharing memory; share memory by communicating"
- "A little copying is better than a little dependency"
- "The bigger the interface, the weaker the abstraction"
- "Make the zero value useful"
- "Accept interfaces, return structs"
Project Structure
Standard Layout
myproject/
├── cmd/ # Main applications
│ ├── api/
│ │ └── main.go # API server entry point
│ └── worker/
│ └── main.go # Background worker entry point
├── internal/ # Private application code
│ ├── app/ # Application logic
│ ├── domain/ # Domain models
│ └── platform/ # Platform-specific code
├── pkg/ # Public libraries (reusable)
│ ├── auth/
│ └── logger/
├── api/ # API definitions (OpenAPI, protobuf)
├── web/ # Web assets (templates, static files)
├── scripts/ # Build, install, analysis scripts
├── configs/ # Configuration files
├── deployments/ # Deployment configs (docker, k8s)
├── test/ # Additional test data
├── docs/ # Documentation
├── examples/ # Example code
├── tools/ # Supporting tools
├── vendor/ # Vendored dependencies (optional)
├── go.mod # Module definition
├── go.sum # Dependency checksums
├── Makefile # Build automation
└── README.md
Key directories:
| Directory | Purpose | Public/Private |
|---|---|---|
cmd/ |
Main applications (one per binary) | Private |
internal/ |
Private application code (cannot be imported) | Private |
pkg/ |
Public libraries (can be imported by others) | Public |
api/ |
API definitions, schemas | Public |
Rules:
internal/cannot be imported by external packages (enforced by Go compiler)- One
main.gopercmd/<app>/subdirectory - Keep
pkg/minimal. Place application code ininternal/unless other modules must import it. - Flat is better than nested - avoid deep hierarchies
Minimal Project
For small projects:
myproject/
├── main.go
├── handler.go
├── service.go
├── repository.go
├── go.mod
├── go.sum
└── README.md
Package Naming
// ❌ BAD: Stuttering
auth.AuthService
user.UserRepository
http.HTTPClient
// ✅ GOOD: Concise
auth.Service
user.Repository
http.Client
Rules:
- Lowercase, single word
- No underscores or mixedCaps
- Avoid generic names (
util,common,base,helper) - Name by purpose, not type (
auth,logger, notstructs,interfaces)
Error Handling
Basic Pattern
// ✅ GOOD: Immediate error check
result, err := doSomething()
if err != nil {
return nil, err
}
// ❌ BAD: Deferred error check
result, err := doSomething()
// ... many lines ...
if err != nil {
return nil, err
}
Error Wrapping
// ✅ GOOD: Wrap errors with context
func processUser(id int) error {
user, err := fetchUser(id)
if err != nil {
return fmt.Errorf("failed to fetch user %d: %w", id, err)
}
if err := saveUser(user); err != nil {
return fmt.Errorf("failed to save user %d: %w", id, err)
}
return nil
}
// Check wrapped errors
err := processUser(123)
if errors.Is(err, sql.ErrNoRows) {
// Handle specific error
}
Use %w for wrapping, %v for opaque errors.
Custom Errors
// Define sentinel errors for expected conditions
var (
ErrUserNotFound = errors.New("user not found")
ErrInvalidInput = errors.New("invalid input")
ErrUnauthorized = errors.New("unauthorized")
)
// Use in code
func getUser(id int) (*User, error) {
if id <= 0 {
return nil, ErrInvalidInput
}
user := db.FindUser(id)
if user == nil {
return nil, ErrUserNotFound
}
return user, nil
}
// Check in caller
user, err := getUser(id)
if errors.Is(err, ErrUserNotFound) {
return http.StatusNotFound
}
Error Types
// For errors with additional context
type ValidationError struct {
Field string
Value interface{}
Err error
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for field %s: %v", e.Field, e.Err)
}
func (e *ValidationError) Unwrap() error {
return e.Err
}
// Usage
func validateAge(age int) error {
if age < 0 {
return &ValidationError{
Field: "age",
Value: age,
Err: errors.New("must be non-negative"),
}
}
return nil
}
Error Handling Anti-Patterns
// ❌ BAD: Ignoring errors
result, _ := doSomething()
// ❌ BAD: Generic error messages
if err != nil {
return errors.New("error")
}
// ❌ BAD: Panic for expected errors
if err != nil {
panic(err) // Only use panic for truly unrecoverable situations
}
// ✅ GOOD: Explicit handling
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
Interfaces
Accept Interfaces, Return Structs
// ❌ BAD: Returning interface
func NewService() ServiceInterface {
return &service{}
}
// ✅ GOOD: Returning struct
func NewService() *Service {
return &Service{}
}
// ✅ GOOD: Accepting interface
func ProcessData(r io.Reader) error {
// Can accept *os.File, bytes.Buffer, etc.
}
Small Interfaces
// ✅ GOOD: Single-method interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ✅ GOOD: Compose when needed
type ReadWriter interface {
Reader
Writer
}
// ❌ BAD: Large interfaces
type UserService interface {
CreateUser(ctx context.Context, user *User) error
GetUser(ctx context.Context, id int) (*User, error)
UpdateUser(ctx context.Context, user *User) error
DeleteUser(ctx context.Context, id int) error
ListUsers(ctx context.Context, filter Filter) ([]*User, error)
SearchUsers(ctx context.Context, query string) ([]*User, error)
// ... 10 more methods
}
Rule: "The bigger the interface, the weaker the abstraction."
Interface Definition Location
// ✅ GOOD: Define interfaces where they're used (consumer-side)
package handler
type UserRepository interface {
GetUser(id int) (*User, error)
}
type Handler struct {
repo UserRepository
}
// Separate implementation
package postgres
type UserRepo struct {
db *sql.DB
}
func (r *UserRepo) GetUser(id int) (*User, error) {
// Implementation
}
Rule: Let consumers define interfaces, not providers.
Concurrency
Goroutines
// ✅ GOOD: Launch goroutine
go func() {
result := compute()
fmt.Println(result)
}()
// ✅ GOOD: Launch with closure
for _, item := range items {
item := item // Capture loop variable
go func() {
process(item)
}()
}
// ❌ BAD: Loop variable capture
for _, item := range items {
go func() {
process(item) // All goroutines will see the same 'item'
}()
}
Channels
// Unbuffered channel (synchronous)
ch := make(chan int)
// Buffered channel (asynchronous)
ch := make(chan int, 10)
// Send and receive
ch <- 42 // Send
value := <-ch // Receive
// Close channel (sender's responsibility)
close(ch)
// Check if closed
value, ok := <-ch
if !ok {
// Channel closed
}
// Range over channel (stops when closed)
for value := range ch {
process(value)
}
Select Statement
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
case ch3 <- msg:
fmt.Println("Sent to ch3")
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
default:
fmt.Println("No channel ready")
}
Worker Pool Pattern
func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
// Start workers
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
result := processJob(job)
results <- result
}
}()
}
// Wait for all workers to finish
wg.Wait()
close(results)
}
// Usage
jobs := make(chan Job, 100)
results := make(chan Result, 100)
go workerPool(jobs, results, 5)
// Send jobs
for _, job := range allJobs {
jobs <- job
}
close(jobs)
// Collect results
for result := range results {
fmt.Println(result)
}
Context for Cancellation
func doWork(ctx context.Context) error {
// Create channel for result
resultCh := make(chan Result)
errCh := make(chan error)
// Start work in goroutine
go func() {
result, err := expensiveOperation()
if err != nil {
errCh <- err
return
}
resultCh <- result
}()
// Wait for result or cancellation
select {
case result := <-resultCh:
return processResult(result)
case err := <-errCh:
return fmt.Errorf("operation failed: %w", err)
case <-ctx.Done():
return ctx.Err()
}
}
// Usage
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := doWork(ctx)
if errors.Is(err, context.DeadlineExceeded) {
// Handle timeout
}
Mutex for Shared State
type SafeCounter struct {
mu sync.RWMutex
count map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.count[key]++
}
func (c *SafeCounter) Value(key string) int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count[key]
}
Rule: Use sync.RWMutex when reads vastly outnumber writes.
Common Concurrency Patterns
Fan-out, fan-in:
func fanOut(input <-chan int, workers int) []<-chan int {
channels := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
ch := make(chan int)
channels[i] = ch
go func() {
defer close(ch)
for v := range input {
ch <- process(v)
}
}()
}
return channels
}
func fanIn(channels []<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
out <- v
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Pipeline:
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}
// Usage
nums := gen(2, 3, 4)
squares := sq(nums)
for s := range squares {
fmt.Println(s)
}
Testing
Table-Driven Tests
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 1, 2, 3},
{"negative", -1, -2, -3},
{"mixed", 1, -1, 0},
{"zero", 0, 0, 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
func TestUser(t *testing.T) {
t.Run("Create", func(t *testing.T) {
user := NewUser("alice")
if user.Name != "alice" {
t.Errorf("expected name alice, got %s", user.Name)
}
})
t.Run("Update", func(t *testing.T) {
user := NewUser("alice")
user.SetName("bob")
if user.Name != "bob" {
t.Errorf("expected name bob, got %s", user.Name)
}
})
}
Test Helpers
func TestMain(m *testing.M) {
// Setup
setup()
// Run tests
code := m.Run()
// Teardown
teardown()
os.Exit(code)
}
func setup() {
// Initialize test database, etc.
}
func teardown() {
// Cleanup
}
Mocking with Interfaces
// Define interface
type UserRepository interface {
GetUser(id int) (*User, error)
}
// Real implementation
type PostgresRepo struct {
db *sql.DB
}
func (r *PostgresRepo) GetUser(id int) (*User, error) {
// Real database query
}
// Mock for testing
type MockRepo struct {
GetUserFunc func(id int) (*User, error)
}
func (m *MockRepo) GetUser(id int) (*User, error) {
return m.GetUserFunc(id)
}
// Test
func TestService(t *testing.T) {
mockRepo := &MockRepo{
GetUserFunc: func(id int) (*User, error) {
return &User{ID: id, Name: "test"}, nil
},
}
service := NewService(mockRepo)
user, err := service.GetUser(1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "test" {
t.Errorf("expected name test, got %s", user.Name)
}
}
Testing HTTP Handlers
func TestHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/users/1", nil)
w := httptest.NewRecorder()
handler := NewHandler()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
var user User
if err := json.NewDecoder(w.Body).Decode(&user); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if user.ID != 1 {
t.Errorf("expected user ID 1, got %d", user.ID)
}
}
Benchmarks
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
func BenchmarkAddParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Add(1, 2)
}
})
}
Run: go test -bench=.
Coverage
go test -cover
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Common Patterns
Constructor Pattern
// ✅ GOOD: New function returns pointer
func NewService(repo Repository) *Service {
return &Service{
repo: repo,
}
}
// ✅ GOOD: Functional options
type ServiceOption func(*Service)
func WithTimeout(d time.Duration) ServiceOption {
return func(s *Service) {
s.timeout = d
}
}
func NewService(opts ...ServiceOption) *Service {
s := &Service{
timeout: 30 * time.Second, // default
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
svc := NewService(
WithTimeout(1 * time.Minute),
WithRetries(3),
)
Builder Pattern (Rare in Go)
// Only use when construction is complex
type RequestBuilder struct {
method string
url string
headers map[string]string
body io.Reader
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
headers: make(map[string]string),
}
}
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.url = u
return b
}
func (b *RequestBuilder) Build() (*http.Request, error) {
return http.NewRequest(b.method, b.url, b.body)
}
// Usage
req, err := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Build()
Singleton (Use Sparingly)
var (
instance *Service
once sync.Once
)
func GetInstance() *Service {
once.Do(func() {
instance = &Service{}
})
return instance
}
Note: Prefer dependency injection over singletons.
Adapter Pattern
// Adapt third-party library to your interface
type Logger interface {
Info(msg string)
Error(msg string)
}
type ZapAdapter struct {
logger *zap.Logger
}
func (z *ZapAdapter) Info(msg string) {
z.logger.Info(msg)
}
func (z *ZapAdapter) Error(msg string) {
z.logger.Error(msg)
}
Struct Patterns
Composition Over Inheritance
// ✅ GOOD: Embedding for composition
type User struct {
ID int
Name string
}
type Admin struct {
User // Embedded
Permissions []string
}
// Admin has all User fields
admin := Admin{
User: User{ID: 1, Name: "alice"},
Permissions: []string{"read", "write"},
}
fmt.Println(admin.Name) // Directly accessible
Zero Value Usability
// ✅ GOOD: Usable with zero value
type Buffer struct {
data []byte
}
func (b *Buffer) Write(p []byte) (int, error) {
b.data = append(b.data, p...)
return len(p), nil
}
// Usage without initialization
var buf Buffer
buf.Write([]byte("hello")) // Works!
Private Fields, Public Methods
type Account struct {
balance int // private
}
func (a *Account) Balance() int {
return a.balance
}
func (a *Account) Deposit(amount int) error {
if amount <= 0 {
return errors.New("amount must be positive")
}
a.balance += amount
return nil
}
Performance Optimization
Preallocate Slices
// ❌ BAD: Append without capacity
var items []Item
for i := 0; i < 1000; i++ {
items = append(items, Item{})
}
// ✅ GOOD: Preallocate capacity
items := make([]Item, 0, 1000)
for i := 0; i < 1000; i++ {
items = append(items, Item{})
}
String Builder
// ❌ BAD: String concatenation
var s string
for i := 0; i < 1000; i++ {
s += "x"
}
// ✅ GOOD: strings.Builder
var sb strings.Builder
sb.Grow(1000) // Preallocate
for i := 0; i < 1000; i++ {
sb.WriteString("x")
}
s := sb.String()
Avoid Allocations
// ❌ BAD: Allocates on every call
func process(items []int) []int {
result := make([]int, 0)
for _, item := range items {
if item > 0 {
result = append(result, item)
}
}
return result
}
// ✅ GOOD: Reuse slice
func process(items []int, result []int) []int {
result = result[:0] // Reset length
for _, item := range items {
if item > 0 {
result = append(result, item)
}
}
return result
}
Sync.Pool for Temporary Objects
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func processData(data []byte) {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
// Use buf
}
Profiling
import _ "net/http/pprof"
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Your application code
}
Access profiles:
- CPU:
http://localhost:6060/debug/pprof/profile - Heap:
http://localhost:6060/debug/pprof/heap - Goroutines:
http://localhost:6060/debug/pprof/goroutine
Analyze:
go tool pprof http://localhost:6060/debug/pprof/profile
Common Pitfalls
Range Loop Variable Capture
// ❌ BAD: Captures loop variable
for _, item := range items {
go func() {
fmt.Println(item) // All goroutines print last item
}()
}
// ✅ GOOD: Capture explicitly
for _, item := range items {
item := item // Reassign
go func() {
fmt.Println(item)
}()
}
// ✅ GOOD: Pass as parameter
for _, item := range items {
go func(i Item) {
fmt.Println(i)
}(item)
}
Nil Interface Gotcha
// ❌ BAD: Interface is not nil
func returnsError() error {
var p *MyError = nil
return p // error interface is not nil!
}
err := returnsError()
if err != nil {
// This block executes!
}
// ✅ GOOD: Return explicit nil
func returnsError() error {
var p *MyError = nil
if p == nil {
return nil
}
return p
}
Defer in Loops
// ❌ BAD: Defer accumulates
for _, file := range files {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close() // Won't run until function returns
}
// ✅ GOOD: Close immediately or use function
for _, file := range files {
if err := processFile(file); err != nil {
return err
}
}
func processFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// Process file
return nil
}
Slice Append Gotcha
// ❌ BAD: Original slice affected
a := []int{1, 2, 3}
b := a[:2]
b = append(b, 4)
// a is now [1, 2, 4]
// ✅ GOOD: Copy slice
a := []int{1, 2, 3}
b := make([]int, len(a[:2]))
copy(b, a[:2])
b = append(b, 4)
// a is still [1, 2, 3]
Map Concurrent Access
// ❌ BAD: Concurrent map writes cause panic
m := make(map[string]int)
go func() {
m["key"] = 1
}()
go func() {
m["key"] = 2
}()
// ✅ GOOD: Use sync.Map or mutex
var mu sync.Mutex
m := make(map[string]int)
go func() {
mu.Lock()
m["key"] = 1
mu.Unlock()
}()
go func() {
mu.Lock()
m["key"] = 2
mu.Unlock()
}()
Production Guidance
Logging
// Use structured logging
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("user logged in",
zap.Int("user_id", 123),
zap.String("ip", "192.168.1.1"),
)
logger.Error("failed to process request",
zap.Error(err),
zap.String("request_id", reqID),
)
Configuration
// Use environment variables or config files
import "github.com/spf13/viper"
func loadConfig() (*Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("/etc/myapp/")
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
Graceful Shutdown
func main() {
srv := &http.Server{
Addr: ":8080",
Handler: handler,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// Graceful shutdown with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
}
Database Connections
import "database/sql"
func initDB() (*sql.DB, error) {
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, err
}
// Connection pool settings
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
// Verify connection
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}
Context Propagation
// Always pass context as first parameter
func fetchUser(ctx context.Context, userID int) (*User, error) {
// Use context for cancellation, timeouts, and values
if err := ctx.Err(); err != nil {
return nil, err
}
// Pass context to downstream calls
return db.GetUser(ctx, userID)
}
// HTTP handler
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, err := fetchUser(ctx, 123)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}
Tools and Commands
Essential Commands
# Format code
go fmt ./...
# Lint code (requires golangci-lint)
golangci-lint run
# Run tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run tests with race detector
go test -race ./...
# Run benchmarks
go test -bench=. ./...
# Build binary
go build -o myapp ./cmd/myapp
# Install dependencies
go mod download
# Tidy dependencies
go mod tidy
# Vendor dependencies
go mod vendor
# Update dependencies
go get -u ./...
# View documentation
go doc fmt.Println
# Run code
go run main.go
Useful Tools
| Tool | Purpose | Install |
|---|---|---|
golangci-lint |
Comprehensive linter | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest |
gofmt |
Code formatter | Built-in |
goimports |
Import formatter | go install golang.org/x/tools/cmd/goimports@latest |
staticcheck |
Static analysis | go install honnef.co/go/tools/cmd/staticcheck@latest |
govulncheck |
Vulnerability scanner | go install golang.org/x/vuln/cmd/govulncheck@latest |
dlv |
Debugger | go install github.com/go-delve/delve/cmd/dlv@latest |
Makefile Example
.PHONY: build test lint clean
build:
go build -o bin/myapp ./cmd/myapp
test:
go test -v -race -cover ./...
lint:
golangci-lint run
clean:
rm -rf bin/
install-tools:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install golang.org/x/tools/cmd/goimports@latest
run:
go run ./cmd/myapp
Rules
- ALWAYS check errors immediately - defer error checks only when a later check is intentional and documented.
- ALWAYS use
context.Contextas the first parameter of functions that do I/O or long-running work. - ALWAYS close resources with defer immediately after opening them (files, connections, etc.).
- ALWAYS use
go fmtbefore committing code - no exceptions. - ALWAYS run tests with
-raceflag to catch race conditions. - NEVER ignore errors - at minimum, log them; never use
_for error returns. - NEVER use
panicfor expected errors - panic only for truly unrecoverable situations (programmer errors). - NEVER share memory by communicating - communicate by sharing memory (use channels over mutexes when possible).
- NEVER mutate slices/maps without understanding capacity - be aware of shared underlying arrays.
- Accept interfaces, return structs - keep APIs flexible by accepting interfaces but returning concrete types.
- Define interfaces where they're used, not where they're implemented (consumer-side).
- Keep interfaces small - single-method interfaces are ideal.
- Use table-driven tests for comprehensive test coverage with minimal code.
- Preallocate slices when size is known - use
make([]T, 0, capacity). - Use
strings.Builderfor string concatenation in loops - never concatenate strings with+. - Pass context to downstream functions - never create new context in the middle of a call chain.
- Name packages by purpose, not type -
auth, notstructs;logger, notinterfaces. - Avoid stuttering in names -
user.Repository, notuser.UserRepository.
Quick Reference
Minimal HTTP Server
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Minimal CLI App
package main
import (
"flag"
"fmt"
"os"
)
func main() {
name := flag.String("name", "World", "name to greet")
flag.Parse()
fmt.Printf("Hello, %s!\n", *name)
}
Module Initialization
# Initialize module
go mod init github.com/myorg/myapp
# Add dependency
go get github.com/pkg/errors
# Remove unused dependencies
go mod tidy
Resources
Inputs
- Go source files, module (
go.mod), and description of the feature, bug, or refactoring goal - Target Go version and any relevant package constraints
Outputs
- Idiomatic Go code following standard project layout, explicit error handling, and table-driven tests
Examples
// Idiomatic error wrapping
func getUser(id int) (*User, error) {
u, err := db.QueryUser(id)
if err != nil {
return nil, fmt.Errorf("getUser %d: %w", id, err)
}
return u, nil
}
Source: brpaz/agent-skills — distributed by TomeVault.
Decide Fit First
Design Intent
How To Use It
Boundaries And Review