- Introduced a new skill for Golang, providing comprehensive guidance on writing, debugging, and best practices for Go programming. - Added reference materials including effective Go guidelines, common patterns, and a quick reference cheat sheet to support users in Go development. - Created a skill creator guide to assist in developing new skills with structured templates and resource management. - Implemented scripts for skill initialization and packaging to streamline the skill creation process.
12 KiB
12 KiB
Go Common Patterns and Idioms
Design Patterns
Functional Options Pattern
Used for configuring objects with many optional parameters:
type Server struct {
host string
port int
timeout time.Duration
maxConn int
}
type Option func(*Server)
func WithHost(host string) Option {
return func(s *Server) {
s.host = host
}
}
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout time.Duration) Option {
return func(s *Server) {
s.timeout = timeout
}
}
func NewServer(opts ...Option) *Server {
// Set defaults
s := &Server{
host: "localhost",
port: 8080,
timeout: 30 * time.Second,
maxConn: 100,
}
// Apply options
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
srv := NewServer(
WithHost("example.com"),
WithPort(443),
WithTimeout(60 * time.Second),
)
Builder Pattern
For complex object construction:
type HTTPRequest struct {
method string
url string
headers map[string]string
body []byte
}
type RequestBuilder struct {
request *HTTPRequest
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &HTTPRequest{
headers: make(map[string]string),
},
}
}
func (b *RequestBuilder) Method(method string) *RequestBuilder {
b.request.method = method
return b
}
func (b *RequestBuilder) URL(url string) *RequestBuilder {
b.request.url = url
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}
func (b *RequestBuilder) Body(body []byte) *RequestBuilder {
b.request.body = body
return b
}
func (b *RequestBuilder) Build() *HTTPRequest {
return b.request
}
// Usage
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Body([]byte(`{"key":"value"}`)).
Build()
Singleton Pattern
Thread-safe singleton using sync.Once:
type Database struct {
conn *sql.DB
}
var (
instance *Database
once sync.Once
)
func GetDatabase() *Database {
once.Do(func() {
conn, err := sql.Open("postgres", "connection-string")
if err != nil {
log.Fatal(err)
}
instance = &Database{conn: conn}
})
return instance
}
Factory Pattern
type Animal interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{}
func (c Cat) Speak() string { return "Meow!" }
type AnimalFactory struct{}
func (f *AnimalFactory) CreateAnimal(animalType string) Animal {
switch animalType {
case "dog":
return &Dog{}
case "cat":
return &Cat{}
default:
return nil
}
}
Strategy Pattern
type PaymentStrategy interface {
Pay(amount float64) error
}
type CreditCard struct {
number string
}
func (c *CreditCard) Pay(amount float64) error {
fmt.Printf("Paying %.2f using credit card %s\n", amount, c.number)
return nil
}
type PayPal struct {
email string
}
func (p *PayPal) Pay(amount float64) error {
fmt.Printf("Paying %.2f using PayPal account %s\n", amount, p.email)
return nil
}
type PaymentContext struct {
strategy PaymentStrategy
}
func (pc *PaymentContext) SetStrategy(strategy PaymentStrategy) {
pc.strategy = strategy
}
func (pc *PaymentContext) ExecutePayment(amount float64) error {
return pc.strategy.Pay(amount)
}
Concurrency Patterns
Worker Pool
func worker(id int, jobs <-chan Job, results chan<- Result) {
for job := range jobs {
result := processJob(job)
results <- result
}
}
func WorkerPool(numWorkers int, jobs []Job) []Result {
jobsChan := make(chan Job, len(jobs))
results := make(chan Result, len(jobs))
// Start workers
for w := 1; w <= numWorkers; w++ {
go worker(w, jobsChan, results)
}
// Send jobs
for _, job := range jobs {
jobsChan <- job
}
close(jobsChan)
// Collect results
var output []Result
for range jobs {
output = append(output, <-results)
}
return output
}
Pipeline Pattern
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
// Create pipeline
c := generator(2, 3, 4)
out := square(c)
// Consume output
for result := range out {
fmt.Println(result)
}
}
Fan-Out, Fan-In
func fanOut(in <-chan int, n int) []<-chan int {
channels := make([]<-chan int, n)
for i := 0; i < n; i++ {
channels[i] = worker(in)
}
return channels
}
func worker(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- expensiveOperation(n)
}
close(out)
}()
return out
}
func fanIn(channels ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
wg.Add(len(channels))
for _, c := range channels {
go func(ch <-chan int) {
defer wg.Done()
for n := range ch {
out <- n
}
}(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Timeout Pattern
func DoWithTimeout(timeout time.Duration) (result string, err error) {
done := make(chan struct{})
go func() {
result = expensiveOperation()
close(done)
}()
select {
case <-done:
return result, nil
case <-time.After(timeout):
return "", fmt.Errorf("operation timed out after %v", timeout)
}
}
Graceful Shutdown
func main() {
server := &http.Server{Addr: ":8080"}
// Start server in goroutine
go func() {
if err := server.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 := server.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
}
Rate Limiting
func rateLimiter(rate time.Duration) <-chan time.Time {
return time.Tick(rate)
}
func main() {
limiter := rateLimiter(200 * time.Millisecond)
for req := range requests {
<-limiter // Wait for rate limiter
go handleRequest(req)
}
}
Circuit Breaker
type CircuitBreaker struct {
maxFailures int
timeout time.Duration
failures int
lastFail time.Time
state string
mu sync.Mutex
}
func (cb *CircuitBreaker) Call(fn func() error) error {
cb.mu.Lock()
defer cb.mu.Unlock()
if cb.state == "open" {
if time.Since(cb.lastFail) > cb.timeout {
cb.state = "half-open"
} else {
return fmt.Errorf("circuit breaker is open")
}
}
err := fn()
if err != nil {
cb.failures++
cb.lastFail = time.Now()
if cb.failures >= cb.maxFailures {
cb.state = "open"
}
return err
}
cb.failures = 0
cb.state = "closed"
return nil
}
Error Handling Patterns
Error Wrapping
func processFile(filename string) (err error) {
data, err := readFile(filename)
if err != nil {
return fmt.Errorf("failed to process file %s: %w", filename, err)
}
if err := validate(data); err != nil {
return fmt.Errorf("validation failed for %s: %w", filename, err)
}
return nil
}
Sentinel Errors
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
func FindUser(id int) (*User, error) {
user, exists := users[id]
if !exists {
return nil, ErrNotFound
}
return user, nil
}
// Check error
user, err := FindUser(123)
if errors.Is(err, ErrNotFound) {
// Handle not found
}
Custom Error Types
type ValidationError struct {
Field string
Value interface{}
Err error
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for field %s with value %v: %v",
e.Field, e.Value, e.Err)
}
func (e *ValidationError) Unwrap() error {
return e.Err
}
// Usage
var validErr *ValidationError
if errors.As(err, &validErr) {
fmt.Printf("Field: %s\n", validErr.Field)
}
Resource Management Patterns
Defer for Cleanup
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
// Process file
return nil
}
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
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
Sync.Pool for Object Reuse
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func process() {
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
buf.Reset()
// Use buffer
}
Testing Patterns
Table-Driven Tests
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -1, -2},
{"mixed signs", -5, 10, 5},
{"zeros", 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)
}
})
}
}
Mock Interfaces
type Database interface {
Get(key string) (string, error)
Set(key, value string) error
}
type MockDB struct {
data map[string]string
}
func (m *MockDB) Get(key string) (string, error) {
val, ok := m.data[key]
if !ok {
return "", errors.New("not found")
}
return val, nil
}
func (m *MockDB) Set(key, value string) error {
m.data[key] = value
return nil
}
func TestUserService(t *testing.T) {
mockDB := &MockDB{data: make(map[string]string)}
service := NewUserService(mockDB)
// Test service
}
Test Fixtures
func setupTestDB(t *testing.T) (*sql.DB, func()) {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
// Setup schema
_, err = db.Exec(schema)
if err != nil {
t.Fatal(err)
}
cleanup := func() {
db.Close()
}
return db, cleanup
}
func TestDatabase(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
// Run tests
}