DRY Principle: Eliminating Repetition for Maintainable Code

The Copy-Paste Horror Story

It was 2 AM. The production server was on fire. A critical bug had brought down the entire payment system. Sarah, the on-call engineer, was frantically searching through the codebase.
The bug was simple: a date formatting issue that caused transactions to be recorded in the wrong timezone. The fix was also simple: change time.Now() to time.Now().UTC().
But here's where the horror began.
That date formatting logic was copied into 47 different files. Sarah had to find and fix every single copy. Miss one? Another timezone bug waiting to happen. Make a typo in one fix? Another bug.
What should have been a 2-minute fix became a 4-hour ordeal.
This is what happens when you violate DRY.

What is DRY?

DRY stands for "Don't Repeat Yourself."
The principle was formulated by Andy Hunt and Dave Thomas in their book "The Pragmatic Programmer":
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
Notice it says "knowledge," not just "code." DRY applies to:
  • Code: Functions, classes, algorithms
  • Data: Database schemas, constants
  • Configuration: Environment settings, feature flags
  • Documentation: Comments, README files
  • Test logic: Setup code, assertions
Design principles diagram 1

Design principles diagram 1

Why Repetition is Poison

The Shotgun Surgery Anti-Pattern

When the same logic exists in multiple places, any change requires "shotgun surgery" - making the same change in many places at once.
Design principles diagram 2

Design principles diagram 2

The Real Costs of Duplication

ProblemImpact
Inconsistent updatesSome copies get updated, others don't
Bug multiplicationFix one bug, miss 46 identical bugs
Wasted effortDevelopers write the same code repeatedly
Larger codebaseMore code = more to maintain, test, understand
Knowledge silos"Which version is correct?"
Merge conflictsMultiple people updating similar code

Types of Duplication

Type 1: Exact Copy-Paste

The most obvious violation. Identical code in multiple places.
go
// Filename: bad_exact_duplication.go // DRY VIOLATION: Exact same code in multiple places package main import ( "fmt" "regexp" "strings" ) // In user_handler.go func validateUserEmail(email string) bool { if email == "" { return false } if len(email) > 254 { return false } pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` matched, _ := regexp.MatchString(pattern, email) return matched } // In order_handler.go - EXACT SAME CODE! func validateOrderEmail(email string) bool { if email == "" { return false } if len(email) > 254 { return false } pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` matched, _ := regexp.MatchString(pattern, email) return matched } // In newsletter_handler.go - AGAIN! func validateSubscriberEmail(email string) bool { if email == "" { return false } if len(email) > 254 { return false } pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` matched, _ := regexp.MatchString(pattern, email) return matched } // What happens when the email regex needs to change? // What if we need to support new TLDs? // 3 places to update, 3 chances to make mistakes!

Type 2: Structural Duplication

Same structure with different details.
go
// Filename: bad_structural_duplication.go // DRY VIOLATION: Same structure, different details package main import ( "database/sql" "fmt" ) // User CRUD operations func CreateUser(db *sql.DB, name, email string) error { query := "INSERT INTO users (name, email) VALUES (?, ?)" _, err := db.Exec(query, name, email) if err != nil { return fmt.Errorf("failed to create user: %w", err) } return nil } func GetUser(db *sql.DB, id int) (*User, error) { query := "SELECT id, name, email FROM users WHERE id = ?" row := db.QueryRow(query, id) user := &User{} err := row.Scan(&user.ID, &user.Name, &user.Email) if err != nil { return nil, fmt.Errorf("failed to get user: %w", err) } return user, nil } // Product CRUD operations - SAME STRUCTURE! func CreateProduct(db *sql.DB, name string, price float64) error { query := "INSERT INTO products (name, price) VALUES (?, ?)" _, err := db.Exec(query, name, price) if err != nil { return fmt.Errorf("failed to create product: %w", err) } return nil } func GetProduct(db *sql.DB, id int) (*Product, error) { query := "SELECT id, name, price FROM products WHERE id = ?" row := db.QueryRow(query, id) product := &Product{} err := row.Scan(&product.ID, &product.Name, &product.Price) if err != nil { return nil, fmt.Errorf("failed to get product: %w", err) } return product, nil } // Order CRUD operations - SAME STRUCTURE AGAIN! func CreateOrder(db *sql.DB, userID, productID int) error { query := "INSERT INTO orders (user_id, product_id) VALUES (?, ?)" _, err := db.Exec(query, userID, productID) if err != nil { return fmt.Errorf("failed to create order: %w", err) } return nil } // ... and so on for every entity in the system

Type 3: Logic Duplication

Same business logic expressed differently.
go
// Filename: bad_logic_duplication.go // DRY VIOLATION: Same logic, different expressions package main // In pricing_service.go func CalculateDiscount(price float64, customerType string) float64 { switch customerType { case "premium": return price * 0.20 // 20% discount case "gold": return price * 0.15 // 15% discount case "silver": return price * 0.10 // 10% discount default: return 0 } } // In invoice_service.go - SAME LOGIC, DIFFERENT IMPLEMENTATION! func GetDiscountRate(customer Customer) float64 { if customer.IsPremium { return 0.20 } if customer.Tier == "gold" { return 0.15 } if customer.Tier == "silver" { return 0.10 } return 0 } // In checkout_service.go - AGAIN! var discountRates = map[string]float64{ "premium": 0.20, "gold": 0.15, "silver": 0.10, } func ApplyDiscount(price float64, tier string) float64 { if rate, ok := discountRates[tier]; ok { return price - (price * rate) } return price } // What happens when we add a "platinum" tier? // What if premium discount changes to 25%? // 3 places with the same knowledge about discount rates!

Eliminating Duplication: Patterns and Techniques

Pattern 1: Extract Function

The simplest and most common technique.
go
// Filename: dry_extract_function.go // DRY SOLUTION: Extract common code into a function package main import ( "fmt" "regexp" ) // ============================================================================= // SHARED VALIDATION PACKAGE // ============================================================================= // emailRegex is compiled once and reused var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) // ValidateEmail validates an email address // Why: Single source of truth for email validation // Why: Changes here automatically apply everywhere func ValidateEmail(email string) error { if email == "" { return fmt.Errorf("email is required") } if len(email) > 254 { return fmt.Errorf("email is too long (max 254 characters)") } if !emailRegex.MatchString(email) { return fmt.Errorf("email format is invalid") } return nil } // ============================================================================= // CONSUMERS - All use the same validation // ============================================================================= // User represents a user in the system type User struct { ID int Name string Email string } // NewUser creates a new user with validation func NewUser(name, email string) (*User, error) { // Use the shared validation if err := ValidateEmail(email); err != nil { return nil, fmt.Errorf("invalid user email: %w", err) } return &User{Name: name, Email: email}, nil } // Order represents an order type Order struct { ID int UserEmail string BillingEmail string } // NewOrder creates a new order with validation func NewOrder(userEmail, billingEmail string) (*Order, error) { // Use the same shared validation if err := ValidateEmail(userEmail); err != nil { return nil, fmt.Errorf("invalid user email: %w", err) } if err := ValidateEmail(billingEmail); err != nil { return nil, fmt.Errorf("invalid billing email: %w", err) } return &Order{UserEmail: userEmail, BillingEmail: billingEmail}, nil } // NewsletterSubscription represents a subscription type NewsletterSubscription struct { Email string } // Subscribe creates a newsletter subscription func Subscribe(email string) (*NewsletterSubscription, error) { // Same validation, no duplication! if err := ValidateEmail(email); err != nil { return nil, fmt.Errorf("invalid subscription email: %w", err) } return &NewsletterSubscription{Email: email}, nil } func main() { fmt.Println("╔══════════════════════════════════════════════════════════╗") fmt.Println("║ DRY: Extract Function Pattern ║") fmt.Println("╚══════════════════════════════════════════════════════════╝") // Test valid email user, err := NewUser("Alice", "alice@example.com") if err != nil { fmt.Printf("❌ User creation failed: %v\n", err) } else { fmt.Printf("✅ User created: %s (%s)\n", user.Name, user.Email) } // Test invalid email _, err = NewUser("Bob", "invalid-email") if err != nil { fmt.Printf("❌ User creation failed (expected): %v\n", err) } // Test order with valid emails order, err := NewOrder("customer@shop.com", "billing@shop.com") if err != nil { fmt.Printf("❌ Order creation failed: %v\n", err) } else { fmt.Printf("✅ Order created with emails: %s, %s\n", order.UserEmail, order.BillingEmail) } // Test subscription sub, err := Subscribe("newsletter@reader.com") if err != nil { fmt.Printf("❌ Subscription failed: %v\n", err) } else { fmt.Printf("✅ Subscribed: %s\n", sub.Email) } fmt.Println("\n" + "═"*60) fmt.Println("BENEFIT: Email validation logic exists in ONE place.") fmt.Println("Change the regex? One file. Add a new check? One file.") fmt.Println("═"*60) }
Expected Output:
╔══════════════════════════════════════════════════════════╗ ║ DRY: Extract Function Pattern ║ ╚══════════════════════════════════════════════════════════╝ ✅ User created: Alice (alice@example.com) ❌ User creation failed (expected): invalid user email: email format is invalid ✅ Order created with emails: customer@shop.com, billing@shop.com ✅ Subscribed: newsletter@reader.com ════════════════════════════════════════════════════════════ BENEFIT: Email validation logic exists in ONE place. Change the regex? One file. Add a new check? One file. ════════════════════════════════════════════════════════════

Pattern 2: Use Constants and Configuration

go
// Filename: dry_constants.go // DRY SOLUTION: Centralize magic numbers and configuration package main import ( "fmt" "time" ) // ============================================================================= // CENTRALIZED CONSTANTS // ============================================================================= // DiscountTier represents customer discount levels type DiscountTier string const ( TierPlatinum DiscountTier = "platinum" TierPremium DiscountTier = "premium" TierGold DiscountTier = "gold" TierSilver DiscountTier = "silver" TierBronze DiscountTier = "bronze" TierStandard DiscountTier = "standard" ) // DiscountRates is the single source of truth for all discount rates // Why: Adding or changing a discount requires changing only this map var DiscountRates = map[DiscountTier]float64{ TierPlatinum: 0.30, // 30% discount TierPremium: 0.25, // 25% discount TierGold: 0.20, // 20% discount TierSilver: 0.15, // 15% discount TierBronze: 0.10, // 10% discount TierStandard: 0.00, // No discount } // Application-wide constants const ( MaxLoginAttempts = 5 SessionTimeoutHours = 24 PasswordMinLength = 12 PasswordMaxLength = 128 UsernameMinLength = 3 UsernameMaxLength = 30 MaxUploadSizeMB = 10 DefaultPageSize = 20 MaxPageSize = 100 TokenExpiryMinutes = 15 RefreshTokenDays = 30 ) // Timeouts and durations var ( HTTPTimeout = 30 * time.Second DatabaseTimeout = 10 * time.Second CacheExpiry = 5 * time.Minute SessionTimeout = SessionTimeoutHours * time.Hour TokenExpiry = TokenExpiryMinutes * time.Minute RefreshTokenExpiry = RefreshTokenDays * 24 * time.Hour ) // ============================================================================= // USING THE CENTRALIZED VALUES // ============================================================================= // CalculateDiscount uses the centralized discount rates func CalculateDiscount(price float64, tier DiscountTier) float64 { rate, exists := DiscountRates[tier] if !exists { rate = DiscountRates[TierStandard] } return price * rate } // ValidatePassword uses centralized password rules func ValidatePassword(password string) error { if len(password) < PasswordMinLength { return fmt.Errorf("password must be at least %d characters", PasswordMinLength) } if len(password) > PasswordMaxLength { return fmt.Errorf("password cannot exceed %d characters", PasswordMaxLength) } return nil } // ValidateUsername uses centralized username rules func ValidateUsername(username string) error { if len(username) < UsernameMinLength { return fmt.Errorf("username must be at least %d characters", UsernameMinLength) } if len(username) > UsernameMaxLength { return fmt.Errorf("username cannot exceed %d characters", UsernameMaxLength) } return nil } // CreateSession uses centralized session settings func CreateSession() Session { return Session{ CreatedAt: time.Now(), ExpiresAt: time.Now().Add(SessionTimeout), } } // Session represents a user session type Session struct { CreatedAt time.Time ExpiresAt time.Time } func main() { fmt.Println("╔══════════════════════════════════════════════════════════╗") fmt.Println("║ DRY: Centralized Constants Pattern ║") fmt.Println("╚══════════════════════════════════════════════════════════╝") // Using discount rates fmt.Println("\n📊 Discount Calculations (price: $100):") fmt.Println("─"*50) tiers := []DiscountTier{TierPlatinum, TierPremium, TierGold, TierSilver, TierBronze} for _, tier := range tiers { discount := CalculateDiscount(100, tier) fmt.Printf(" %-10s: $%.2f off (%.0f%%)\n", tier, discount, DiscountRates[tier]*100) } // Using validation constants fmt.Println("\n🔐 Validation Rules:") fmt.Println("─"*50) fmt.Printf(" Password: %d-%d characters\n", PasswordMinLength, PasswordMaxLength) fmt.Printf(" Username: %d-%d characters\n", UsernameMinLength, UsernameMaxLength) fmt.Printf(" Max upload: %d MB\n", MaxUploadSizeMB) // Using timeout constants fmt.Println("\n⏱️ Timeout Settings:") fmt.Println("─"*50) fmt.Printf(" HTTP timeout: %v\n", HTTPTimeout) fmt.Printf(" Database timeout: %v\n", DatabaseTimeout) fmt.Printf(" Session timeout: %v\n", SessionTimeout) fmt.Printf(" Token expiry: %v\n", TokenExpiry) fmt.Println("\n" + "═"*60) fmt.Println("BENEFIT: All magic numbers in ONE place.") fmt.Println("Need to change password requirements? One file.") fmt.Println("Need to update discount rates? One file.") fmt.Println("═"*60) }

Pattern 3: Template Method with Interfaces

go
// Filename: dry_template_method.go // DRY SOLUTION: Use interfaces for structural duplication package main import ( "fmt" ) // ============================================================================= // GENERIC REPOSITORY PATTERN // ============================================================================= // Entity is the interface all entities must implement type Entity interface { TableName() string ID() int SetID(id int) } // Repository provides generic CRUD operations // Why: Same database logic for all entities type Repository[T Entity] struct { db map[int]T // Simulated database nextID int } // NewRepository creates a new repository func NewRepository[T Entity]() *Repository[T] { return &Repository[T]{ db: make(map[int]T), nextID: 1, } } // Create inserts a new entity func (r *Repository[T]) Create(entity T) (T, error) { entity.SetID(r.nextID) r.db[r.nextID] = entity r.nextID++ fmt.Printf("[%s] Created with ID %d\n", entity.TableName(), entity.ID()) return entity, nil } // FindByID retrieves an entity by ID func (r *Repository[T]) FindByID(id int) (T, bool) { entity, exists := r.db[id] if exists { fmt.Printf("[%s] Found ID %d\n", entity.TableName(), id) } return entity, exists } // FindAll retrieves all entities func (r *Repository[T]) FindAll() []T { result := make([]T, 0, len(r.db)) for _, entity := range r.db { result = append(result, entity) } return result } // Update updates an existing entity func (r *Repository[T]) Update(entity T) error { if _, exists := r.db[entity.ID()]; !exists { return fmt.Errorf("entity with ID %d not found", entity.ID()) } r.db[entity.ID()] = entity fmt.Printf("[%s] Updated ID %d\n", entity.TableName(), entity.ID()) return nil } // Delete removes an entity func (r *Repository[T]) Delete(id int) error { if _, exists := r.db[id]; !exists { return fmt.Errorf("entity with ID %d not found", id) } delete(r.db, id) fmt.Printf("[%s] Deleted ID %d\n", r.db[id].TableName(), id) return nil } // Count returns the number of entities func (r *Repository[T]) Count() int { return len(r.db) } // ============================================================================= // ENTITIES - Each only needs to implement the Entity interface // ============================================================================= // User entity type User struct { id int Name string Email string } func (u *User) TableName() string { return "users" } func (u *User) ID() int { return u.id } func (u *User) SetID(id int) { u.id = id } // Product entity type Product struct { id int Name string Price float64 } func (p *Product) TableName() string { return "products" } func (p *Product) ID() int { return p.id } func (p *Product) SetID(id int) { p.id = id } // Order entity type Order struct { id int UserID int ProductID int Quantity int } func (o *Order) TableName() string { return "orders" } func (o *Order) ID() int { return o.id } func (o *Order) SetID(id int) { o.id = id } // ============================================================================= // DEMONSTRATION // ============================================================================= func main() { fmt.Println("╔══════════════════════════════════════════════════════════╗") fmt.Println("║ DRY: Generic Repository Pattern (Go 1.18+) ║") fmt.Println("╚══════════════════════════════════════════════════════════╝") // User repository - NO duplicate CRUD code! userRepo := NewRepository[*User]() fmt.Println("\n👤 User Operations:") fmt.Println("─"*50) user1, _ := userRepo.Create(&User{Name: "Alice", Email: "alice@example.com"}) user2, _ := userRepo.Create(&User{Name: "Bob", Email: "bob@example.com"}) if found, exists := userRepo.FindByID(1); exists { fmt.Printf(" Found user: %s (%s)\n", found.Name, found.Email) } // Product repository - Same generic code! productRepo := NewRepository[*Product]() fmt.Println("\n📦 Product Operations:") fmt.Println("─"*50) product1, _ := productRepo.Create(&Product{Name: "Laptop", Price: 999.99}) product2, _ := productRepo.Create(&Product{Name: "Mouse", Price: 29.99}) if found, exists := productRepo.FindByID(1); exists { fmt.Printf(" Found product: %s ($%.2f)\n", found.Name, found.Price) } // Order repository - Still the same generic code! orderRepo := NewRepository[*Order]() fmt.Println("\n🛒 Order Operations:") fmt.Println("─"*50) orderRepo.Create(&Order{UserID: user1.ID(), ProductID: product1.ID(), Quantity: 1}) orderRepo.Create(&Order{UserID: user2.ID(), ProductID: product2.ID(), Quantity: 3}) // Stats fmt.Println("\n📊 Repository Statistics:") fmt.Println("─"*50) fmt.Printf(" Users: %d\n", userRepo.Count()) fmt.Printf(" Products: %d\n", productRepo.Count()) fmt.Printf(" Orders: %d\n", orderRepo.Count()) fmt.Println("\n" + "═"*60) fmt.Println("BENEFIT: CRUD logic written ONCE, works for ALL entities!") fmt.Println("Add a new entity type? Just implement the Entity interface.") fmt.Println("Fix a bug in Create? Fixed for ALL repositories.") fmt.Println("═"*60) }
Expected Output:
╔══════════════════════════════════════════════════════════╗ ║ DRY: Generic Repository Pattern (Go 1.18+) ║ ╚══════════════════════════════════════════════════════════╝ 👤 User Operations: ────────────────────────────────────────────────── [users] Created with ID 1 [users] Created with ID 2 [users] Found ID 1 Found user: Alice (alice@example.com) 📦 Product Operations: ────────────────────────────────────────────────── [products] Created with ID 1 [products] Created with ID 2 [products] Found ID 1 Found product: Laptop ($999.99) 🛒 Order Operations: ────────────────────────────────────────────────── [orders] Created with ID 1 [orders] Created with ID 2 📊 Repository Statistics: ────────────────────────────────────────────────── Users: 2 Products: 2 Orders: 2 ════════════════════════════════════════════════════════════ BENEFIT: CRUD logic written ONCE, works for ALL entities! Add a new entity type? Just implement the Entity interface. Fix a bug in Create? Fixed for ALL repositories. ════════════════════════════════════════════════════════════

Pattern 4: Middleware and Decorators

go
// Filename: dry_middleware.go // DRY SOLUTION: Use middleware to avoid repeated cross-cutting concerns package main import ( "fmt" "time" ) // ============================================================================= // MIDDLEWARE PATTERN FOR HTTP-LIKE HANDLERS // ============================================================================= // Request represents an incoming request type Request struct { Path string Method string UserID string Body string } // Response represents an outgoing response type Response struct { StatusCode int Body string } // Handler processes requests type Handler func(Request) Response // Middleware wraps a handler with additional behavior type Middleware func(Handler) Handler // ============================================================================= // REUSABLE MIDDLEWARE - Write once, use everywhere! // ============================================================================= // LoggingMiddleware logs all requests // Why: Instead of adding logging to every handler, wrap once func LoggingMiddleware(next Handler) Handler { return func(req Request) Response { start := time.Now() fmt.Printf("📝 [LOG] %s %s started\n", req.Method, req.Path) response := next(req) duration := time.Since(start) fmt.Printf("📝 [LOG] %s %s completed in %v (status: %d)\n", req.Method, req.Path, duration, response.StatusCode) return response } } // AuthMiddleware checks authentication // Why: Authentication logic in ONE place, applied to any handler func AuthMiddleware(next Handler) Handler { return func(req Request) Response { if req.UserID == "" { fmt.Println("🔒 [AUTH] No user ID - unauthorized") return Response{StatusCode: 401, Body: "Unauthorized"} } fmt.Printf("🔒 [AUTH] User %s authenticated\n", req.UserID) return next(req) } } // TimingMiddleware measures execution time func TimingMiddleware(next Handler) Handler { return func(req Request) Response { start := time.Now() response := next(req) duration := time.Since(start) fmt.Printf("⏱️ [TIMING] Request took %v\n", duration) return response } } // RecoveryMiddleware catches panics func RecoveryMiddleware(next Handler) Handler { return func(req Request) Response { defer func() { if r := recover(); r != nil { fmt.Printf("💥 [RECOVERY] Caught panic: %v\n", r) } }() return next(req) } } // RateLimitMiddleware limits request rates (simplified) func RateLimitMiddleware(maxRequests int) Middleware { requestCount := 0 return func(next Handler) Handler { return func(req Request) Response { requestCount++ if requestCount > maxRequests { fmt.Printf("🚫 [RATE] Rate limit exceeded (%d/%d)\n", requestCount, maxRequests) return Response{StatusCode: 429, Body: "Too Many Requests"} } fmt.Printf("📊 [RATE] Request %d/%d\n", requestCount, maxRequests) return next(req) } } } // Chain applies multiple middleware to a handler func Chain(handler Handler, middlewares ...Middleware) Handler { // Apply middleware in reverse order so first middleware is outermost for i := len(middlewares) - 1; i >= 0; i-- { handler = middlewares[i](handler) } return handler } // ============================================================================= // ACTUAL HANDLERS - Now clean and focused on business logic! // ============================================================================= // GetUserHandler handles user retrieval // Why: This handler only contains business logic, no logging/auth/timing func GetUserHandler(req Request) Response { // Simulate some work time.Sleep(10 * time.Millisecond) return Response{ StatusCode: 200, Body: fmt.Sprintf("User data for request to %s", req.Path), } } // CreateOrderHandler handles order creation func CreateOrderHandler(req Request) Response { time.Sleep(20 * time.Millisecond) return Response{ StatusCode: 201, Body: "Order created successfully", } } // DeleteProductHandler handles product deletion func DeleteProductHandler(req Request) Response { time.Sleep(15 * time.Millisecond) return Response{ StatusCode: 204, Body: "", } } // ============================================================================= // DEMONSTRATION // ============================================================================= func main() { fmt.Println("╔══════════════════════════════════════════════════════════╗") fmt.Println("║ DRY: Middleware Pattern ║") fmt.Println("╚══════════════════════════════════════════════════════════╝") // Apply middleware once, use everywhere standardMiddleware := []Middleware{ RecoveryMiddleware, LoggingMiddleware, TimingMiddleware, AuthMiddleware, } // Wrap handlers with middleware getUserWrapped := Chain(GetUserHandler, standardMiddleware...) createOrderWrapped := Chain(CreateOrderHandler, standardMiddleware...) // Test 1: Authenticated request fmt.Println("\n📤 Test 1: Authenticated GET /users/123") fmt.Println("─"*50) req1 := Request{ Path: "/users/123", Method: "GET", UserID: "user_42", } resp1 := getUserWrapped(req1) fmt.Printf("📥 Response: %d - %s\n", resp1.StatusCode, resp1.Body) // Test 2: Unauthenticated request fmt.Println("\n📤 Test 2: Unauthenticated POST /orders") fmt.Println("─"*50) req2 := Request{ Path: "/orders", Method: "POST", UserID: "", // No user ID } resp2 := createOrderWrapped(req2) fmt.Printf("📥 Response: %d - %s\n", resp2.StatusCode, resp2.Body) // Test 3: Rate limited handler fmt.Println("\n📤 Test 3: Rate Limited Endpoint") fmt.Println("─"*50) rateLimitedHandler := Chain( GetUserHandler, LoggingMiddleware, RateLimitMiddleware(3), ) for i := 1; i <= 5; i++ { req := Request{Path: fmt.Sprintf("/api/data/%d", i), Method: "GET"} resp := rateLimitedHandler(req) fmt.Printf(" Request %d: Status %d\n", i, resp.StatusCode) } fmt.Println("\n" + "═"*60) fmt.Println("BENEFIT: Logging, auth, timing written ONCE as middleware.") fmt.Println("Every handler automatically gets these behaviors.") fmt.Println("Change logging format? One place. Add metrics? One place.") fmt.Println("═"*60) }

The WET Exception: When Duplication is Okay

DRY has an opposite: WET (Write Everything Twice) or more generously, "Write Explicit Tests".
Sometimes a little duplication is better than the wrong abstraction.

The Rule of Three

"Three strikes and you refactor."
  1. First time: Just write it
  2. Second time: Note the duplication, but maybe duplicate
  3. Third time: Now refactor and extract
Why? Because you need enough examples to understand the true abstraction.
go
// First implementation - just write it func FormatUserName(user User) string { return fmt.Sprintf("%s %s", user.FirstName, user.LastName) } // Second implementation - similar but different context func FormatOrderCustomerName(order Order) string { return fmt.Sprintf("%s %s", order.CustomerFirstName, order.CustomerLastName) } // Third implementation - NOW we see the pattern func FormatEmployeeName(employee Employee) string { return fmt.Sprintf("%s %s", employee.FirstName, employee.LastName) } // NOW extract because we understand the abstraction: type Namer interface { FirstName() string LastName() string } func FormatFullName(n Namer) string { return fmt.Sprintf("%s %s", n.FirstName(), n.LastName()) }

When Duplication is the Right Choice

Design principles diagram 3

Design principles diagram 3

Test Code Example

go
// In tests, duplication often HELPS readability // OVER-DRY: Hard to understand what's being tested func TestUserCreation(t *testing.T) { user := createTestUser(t, withEmail("test@test.com"), withName("Test")) // What does createTestUser actually create? // Need to check helper to understand test } // APPROPRIATELY WET: Clear what's being tested func TestUserCreation_ValidEmail(t *testing.T) { user := User{ Name: "Alice", Email: "alice@example.com", } err := user.Validate() if err != nil { t.Errorf("expected valid user, got error: %v", err) } } func TestUserCreation_InvalidEmail(t *testing.T) { user := User{ Name: "Bob", Email: "invalid-email", // Clearly shows what makes it invalid } err := user.Validate() if err == nil { t.Error("expected error for invalid email") } }

The DRY Decision Framework

Design principles diagram 4

Design principles diagram 4


Summary: The DRY Essentials

When to Apply DRY

SituationAction
Same code copied 3+ timesExtract to function
Same magic numbers everywhereCreate named constants
Same structure, different detailsConsider generics or interfaces
Same cross-cutting concernsUse middleware/decorators
Same configuration in multiple placesCentralize configuration

When NOT to Apply DRY

SituationWhy Keep Duplication
Test setup codeClarity is more important
Two similar things that may divergePremature abstraction hurts
Can't name the abstraction clearlyYou don't understand it yet
Duplication is in different bounded contextsThey may need to evolve independently
Only two occurrencesRule of three not met

Key Takeaways

  1. DRY is about knowledge, not just code - Same business rule shouldn't exist in multiple places
  2. One change = One place to edit - That's the goal
  3. Rule of three - Don't extract too early
  4. Wrong abstraction is worse than duplication - Be patient
  5. Test code can be WET - Clarity matters more there

Your Next Steps

  1. Audit: Find repeated code in your project
  2. Evaluate: Does it represent the same knowledge?
  3. Extract: If rule of three is met and abstraction is clear
  4. Read Next: YAGNI Principle

"Duplication is far cheaper than the wrong abstraction." - Sandi Metz
Remember: DRY is a guideline, not a law. The goal is maintainable code, not zero duplication at all costs.
All Blogs
Tags:drydesign-principlesclean-codegolangrefactoring