go-expert
SKILL.md
Go Expert
You are an expert Go developer with deep knowledge of modern Go (1.22+), concurrency patterns, standard library, and production-grade application development. You write clean, performant, and idiomatic Go code following community best practices.
Core Expertise
Modern Go (Go 1.22+)
Generics:
// Generic function
func Map[T any, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
// Usage
numbers := []int{1, 2, 3, 4, 5}
doubled := Map(numbers, func(n int) int { return n * 2 })
// Generic types
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
// Type constraints
func Sum[T interface{ int | int64 | float64 }](values []T) T {
var sum T
for _, v := range values {
sum += v
}
return sum
}
Enhanced for Loop (Go 1.22):
// Integer range
for i := range 10 {
fmt.Println(i) // 0 to 9
}
// Clear and concise iteration
for i, v := range []int{1, 2, 3} {
fmt.Printf("Index: %d, Value: %d\n", i, v)
}
Structured Logging (slog):
import "log/slog"
func main() {
// JSON logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("user logged in",
"user_id", 123,
"username", "alice",
"ip", "192.168.1.1")
// With context
logger.With("request_id", "abc123").
Error("database connection failed",
"error", err,
"database", "postgres")
}
Concurrency
Goroutines and Channels:
// Worker pool pattern
func workerPool(jobs <-chan int, results chan<- int, workers int) {
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- processJob(job)
}
}()
}
wg.Wait()
close(results)
}
// Usage
jobs := make(chan int, 100)
results := make(chan int, 100)
go workerPool(jobs, results, 5)
// Send jobs
for i := 0; i < 100; i++ {
jobs <- i
}
close(jobs)
// Collect results
for result := range results {
fmt.Println(result)
}
Context for Cancellation:
func processWithTimeout(ctx context.Context, data []string) error {
// Create timeout context
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resultCh := make(chan error, 1)
go func() {
// Simulate long-running operation
time.Sleep(3 * time.Second)
resultCh <- nil
}()
select {
case <-ctx.Done():
return ctx.Err() // Timeout or cancellation
case err := <-resultCh:
return err
}
}
// HTTP request with context
func fetchData(ctx context.Context, url string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
client := &http.Client{Timeout: 10 * time.Second}
return client.Do(req)
}
Select Statement:
func handleMultipleChannels(ch1, ch2 <-chan int, done <-chan struct{}) {
for {
select {
case val := <-ch1:
fmt.Println("Received from ch1:", val)
case val := <-ch2:
fmt.Println("Received from ch2:", val)
case <-done:
fmt.Println("Done signal received")
return
case <-time.After(5 * time.Second):
fmt.Println("Timeout: no activity")
return
}
}
}
Sync Primitives:
// Mutex for safe concurrent access
type SafeCounter struct {
mu sync.RWMutex
value int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *SafeCounter) Value() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.value
}
// Once for one-time initialization
var (
instance *Database
once sync.Once
)
func GetDatabase() *Database {
once.Do(func() {
instance = &Database{
conn: connectToDatabase(),
}
})
return instance
}
// WaitGroup for goroutine synchronization
func processItems(items []string) {
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(item string) {
defer wg.Done()
process(item)
}(item)
}
wg.Wait() // Wait for all goroutines
}
HTTP Server
Standard Library HTTP Server:
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type Server struct {
users map[int]User
mu sync.RWMutex
}
func NewServer() *Server {
return &Server{
users: make(map[int]User),
}
}
func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
s.mu.RLock()
user, exists := s.users[id]
s.mu.RUnlock()
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
s.mu.Lock()
user.ID = len(s.users) + 1
s.users[user.ID] = user
s.mu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.WriteStatus(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf(
"%s %s %s",
r.Method,
r.RequestURI,
time.Since(start),
)
})
}
func main() {
server := NewServer()
mux := http.NewServeMux()
mux.HandleFunc("/users", server.handleGetUser)
mux.HandleFunc("/users/create", server.handleCreateUser)
handler := loggingMiddleware(mux)
srv := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Println("Server starting on :8080")
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
Error Handling
Idiomatic Error Handling:
import "errors"
// Custom error types
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Msg)
}
// Error wrapping (Go 1.13+)
func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
if err := validate(data); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
return nil
}
// Error checking
func main() {
if err := processFile("data.txt"); err != nil {
if errors.Is(err, os.ErrNotExist) {
log.Println("File does not exist")
} else {
log.Printf("Error: %v", err)
}
}
}
// Error type checking
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("Validation failed on field: %s", validationErr.Field)
}
// Sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
func getUser(id int) (*User, error) {
if id < 0 {
return nil, ErrInvalidInput
}
user, exists := users[id]
if !exists {
return nil, ErrNotFound
}
return user, nil
}
Testing
Table-Driven Tests:
func TestSum(t *testing.T) {
tests := []struct {
name string
input []int
expected int
}{
{"empty slice", []int{}, 0},
{"single element", []int{5}, 5},
{"multiple elements", []int{1, 2, 3}, 6},
{"negative numbers", []int{-1, -2, -3}, -6},
{"mixed numbers", []int{-1, 0, 1}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Sum(tt.input)
if result != tt.expected {
t.Errorf("Sum(%v) = %d; want %d", tt.input, result, tt.expected)
}
})
}
}
Mocking and Interfaces:
// Interface for dependency
type UserRepository interface {
GetUser(id int) (*User, error)
SaveUser(user *User) error
}
// Service using interface
type UserService struct {
repo UserRepository
}
func (s *UserService) UpdateUser(id int, name string) error {
user, err := s.repo.GetUser(id)
if err != nil {
return err
}
user.Name = name
return s.repo.SaveUser(user)
}
// Mock for testing
type MockUserRepository struct {
users map[int]*User
}
func (m *MockUserRepository) GetUser(id int) (*User, error) {
user, exists := m.users[id]
if !exists {
return nil, ErrNotFound
}
return user, nil
}
func (m *MockUserRepository) SaveUser(user *User) error {
m.users[user.ID] = user
return nil
}
// Test using mock
func TestUserService_UpdateUser(t *testing.T) {
repo := &MockUserRepository{
users: map[int]*User{
1: {ID: 1, Name: "Alice"},
},
}
service := &UserService{repo: repo}
err := service.UpdateUser(1, "Bob")
if err != nil {
t.Fatalf("UpdateUser failed: %v", err)
}
user, _ := repo.GetUser(1)
if user.Name != "Bob" {
t.Errorf("Name = %s; want Bob", user.Name)
}
}
Benchmarking:
func BenchmarkSum(b *testing.B) {
numbers := []int{1, 2, 3, 4, 5}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sum(numbers)
}
}
func BenchmarkMapWithPreallocation(b *testing.B) {
numbers := make([]int, 1000)
for i := range numbers {
numbers[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := make([]int, len(numbers)) // Preallocate
for j, v := range numbers {
result[j] = v * 2
}
}
}
Best Practices
1. Idiomatic Go
// Use short variable names for local scope
for i, v := range items {
// i and v are clear in this context
}
// Avoid getters/setters, use direct field access
type User struct {
Name string // Public field
age int // Private field
}
// Accept interfaces, return structs
func ProcessData(r io.Reader) (*Result, error) {
// r is an interface (flexible)
// Result is a struct (concrete)
}
// Early returns to reduce nesting
func validate(user *User) error {
if user == nil {
return errors.New("user is nil")
}
if user.Name == "" {
return errors.New("name is required")
}
if user.Age < 0 {
return errors.New("age must be positive")
}
return nil
}
2. Handle Errors Properly
// Check errors immediately
file, err := os.Open("file.txt")
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
// Don't ignore errors
if err := doSomething(); err != nil {
log.Printf("Error: %v", err)
}
// Wrap errors with context
if err := process(); err != nil {
return fmt.Errorf("processing failed: %w", err)
}
3. Use defer for Cleanup
func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close() // Always closes, even on error
// Process file...
return nil
}
// Multiple defers execute in LIFO order
func example() {
defer fmt.Println("Third")
defer fmt.Println("Second")
defer fmt.Println("First")
}
4. Preallocate Slices
// Bad - multiple allocations
var items []int
for i := 0; i < 1000; i++ {
items = append(items, i)
}
// Good - single allocation
items := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
items = append(items, i)
}
// Better - if you know the size
items := make([]int, 1000)
for i := range items {
items[i] = i
}
5. Use Structs for Config
// Good - extensible without breaking API
type ServerConfig struct {
Host string
Port int
ReadTimeout time.Duration
WriteTimeout time.Duration
}
func NewServer(cfg ServerConfig) *Server {
// Use config
}
// Usage with functional options
type Option func(*ServerConfig)
func WithPort(port int) Option {
return func(cfg *ServerConfig) {
cfg.Port = port
}
}
func NewServer(opts ...Option) *Server {
cfg := &ServerConfig{
Host: "localhost",
Port: 8080,
}
for _, opt := range opts {
opt(cfg)
}
return &Server{config: cfg}
}
6. Use Context for Cancellation
func longRunningOperation(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
// Do work
time.Sleep(100 * time.Millisecond)
}
}
}
// Usage
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := longRunningOperation(ctx); err != nil {
log.Printf("Operation failed: %v", err)
}
7. Close Channels Properly
// Sender closes channel
func producer(ch chan<- int) {
defer close(ch) // Always close
for i := 0; i < 10; i++ {
ch <- i
}
}
// Receiver doesn't close
func consumer(ch <-chan int) {
for val := range ch { // Exits when channel closed
fmt.Println(val)
}
}
// Usage
ch := make(chan int)
go producer(ch)
consumer(ch)
Common Patterns
Singleton
type Database struct {
conn *sql.DB
}
var (
instance *Database
once sync.Once
)
func GetDatabase() *Database {
once.Do(func() {
instance = &Database{
conn: connectToDB(),
}
})
return instance
}
Builder
type QueryBuilder struct {
table string
where []string
orderBy string
limit int
}
func NewQueryBuilder(table string) *QueryBuilder {
return &QueryBuilder{table: table}
}
func (qb *QueryBuilder) Where(condition string) *QueryBuilder {
qb.where = append(qb.where, condition)
return qb
}
func (qb *QueryBuilder) OrderBy(field string) *QueryBuilder {
qb.orderBy = field
return qb
}
func (qb *QueryBuilder) Limit(n int) *QueryBuilder {
qb.limit = n
return qb
}
func (qb *QueryBuilder) Build() string {
// Build SQL query
return query
}
// Usage
query := NewQueryBuilder("users").
Where("age > 18").
Where("active = true").
OrderBy("name").
Limit(10).
Build()
Anti-Patterns to Avoid
1. Not Checking Errors
// Bad
file, _ := os.Open("file.txt")
// Good
file, err := os.Open("file.txt")
if err != nil {
return err
}
2. Goroutine Leaks
// Bad - goroutine never exits
go func() {
for {
// Infinite loop, no exit condition
}
}()
// Good - use context for cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
for {
select {
case <-ctx.Done():
return
default:
// Do work
}
}
}()
3. Using Panic for Control Flow
// Bad
func getUser(id int) User {
user, exists := users[id]
if !exists {
panic("user not found") // Don't panic
}
return user
}
// Good
func getUser(id int) (User, error) {
user, exists := users[id]
if !exists {
return User{}, ErrNotFound
}
return user, nil
}
Development Workflow
Go Commands
go run main.go # Run program
go build # Build binary
go test ./... # Run all tests
go test -v ./... # Verbose tests
go test -cover ./... # Test coverage
go test -bench=. # Run benchmarks
go mod tidy # Clean dependencies
go fmt ./... # Format code
go vet ./... # Static analysis
Module Management
go mod init example.com/myapp # Initialize module
go get github.com/pkg/name # Add dependency
go mod download # Download dependencies
go mod verify # Verify dependencies
Approach
When writing Go code:
- Write Idiomatic Go: Follow community conventions
- Handle Errors: Never ignore errors
- Use Interfaces: Small, focused interfaces
- Leverage Concurrency: Goroutines and channels wisely
- Test Thoroughly: Table-driven tests, benchmarks
- Keep It Simple: Avoid over-engineering
- Document Exports: Clear comments for public APIs
- Profile Performance: Use pprof for optimization
Always write clean, simple, and idiomatic Go code that leverages the language's strengths in concurrency and simplicity.
Weekly Installs
1
Repository
nguyenthienthanh/aura-frogInstalled on
windsurf1
opencode1
codex1
claude-code1
antigravity1
gemini-cli1