golang-development
Skill1 starsChanged 6 months agoMITView on GitHub
Write and review Go code with idiomatic project structure, errors, concurrency, testing, and performance guidance.
- Deletes or force-pushes
- Installs packages
We split this skill into the rules it gives an agent
10 rules. 0 are common, 5 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 10
Use this skill when writing, reviewing, or debugging Go code.
- Very specific: line 82
Keep pkg/ minimal. Place application code in internal/ unless other modules must import it.
- Distinctive: line 116
No underscores or mixedCaps
- Very specific: line 117
Avoid generic names (util, common, base, helper)
- Very specific: line 504
Rule: Use sync.RWMutex when reads vastly outnumber writes.
- Distinctive: line 849
Note: Prefer dependency injection over singletons.
- Very specific: line 1396
ALWAYS use
go fmt: before committing code - no exceptions. - Distinctive: line 1402
Accept interfaces, return structs: - keep APIs flexible by accepting interfaces but returning concrete types.
- Very specific: line 1406
Preallocate slices when size is known: - use make([]T, 0, capacity).
- Distinctive: line 1408
Pass context to downstream functions: - never create new context in the middle of a call chain.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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.mdKey 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.mdPackage Naming
// ❌ BAD: Stuttering
auth.AuthService
user.UserRepository
http.HTTPClient
// ✅ GOOD: Concise
auth.Service
user.Repository
http.ClientRules:
- 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.outCommon 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 accessibleZero 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/profileCommon 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.goUseful 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/myappRules
- 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 tidyResources
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
}Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- sammcj/agentic-coding★ 164same name
- tstapler/dotfiles★ 8same name
- Rhiadc/skills★ 1same name
- jmlrt/skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository36 other files it gives its agents.
- skills/backend-best-practices/AGENTS.mdAGENTS.md
- adr-writerSkill
- application-healthchecksSkill
- backend-best-practicesSkill
- bspec-prd-discoverySkill
- bspec-story-implementationSkill
- bspec-story-reviewSkill
- bspec-story-syncSkill
- bspec-technical-architectureSkill
- bspec-workflowSkill
- conventional-commitsSkill
- copier-scaffoldSkill
- devenvSkill
- direnvSkill
- docker-composeSkill
- dockerfileSkill
- docker-setupSkill
- docs-writerSkill
- github-actions-dockerSkill
- gitleaks-setupSkill
- golang-slogSkill
- golang-testingSkill
- playwright-devenvSkill
- project-webappSkill
- readme-writerSkill
- release-drafter-setupSkill
- release-drafterSkill
- release-notesSkill
- renovate-pr-triageSkill
- renovate-setupSkill
- structured-loggingSkill
- summarize-articleSkill
- summarize-youtube-videoSkill
- tailwind-v4Skill
- vicinae-extensionsSkill
- zensical-setupSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

