Refactoring
Linter-driven refactoring patterns to reduce complexity and improve code quality.
When to Use
- •Linter fails with complexity issues (cyclomatic, cognitive, maintainability)
- •Code feels hard to read or maintain
- •Functions are too long or deeply nested
- •Automatically invoked by @linter-driven-development when linter fails
Learning Resources
Choose your learning path:
- •Quick Start: Use the patterns below for common refactoring cases
- •Complete Reference: See reference.md for full decision tree and all patterns
- •Real-World Examples: See examples.md to learn the refactoring thought process
Refactoring Signals
Linter Failures
- •Cyclomatic Complexity: Too many decision points → Extract functions, simplify logic
- •Cognitive Complexity: Hard to understand → Storifying, reduce nesting
- •Maintainability Index: Hard to maintain → Break into smaller pieces
Code Smells
- •Functions > 50 LOC
- •Nesting > 2 levels
- •Mixed abstraction levels
- •Unclear flow/purpose
- •Primitive obsession
Workflow
1. Interpret Linter Output
Run task lintwithfix and analyze failures:
user/service.go:45:1: cyclomatic complexity 15 of func `CreateUser` is high (> 10) user/handler.go:23:1: cognitive complexity 25 of func `HandleRequest` is high (> 15)
2. Diagnose Root Cause
For each failure, ask (see reference.md):
- •Does this code read like a story? → Storifying needed
- •Can this be broken into smaller pieces? → Extract functions/types
- •Does logic run on primitives? → Primitive obsession
- •Is function long due to switch statement? → Categorize and extract
3. Apply Refactoring Pattern
Choose appropriate pattern:
- •Storifying: Extract helpers to clarify levels
- •Extract Type: Move primitive logic to custom type
- •Extract Function: Pull out complexity
- •Early Returns: Reduce nesting
- •Switch Extraction: Categorize cases
4. Verify Improvement
- •Re-run linter
- •Tests still pass
- •Code more readable?
Refactoring Patterns
Pattern 1: Storifying (Mixed Abstractions)
Signal: Function mixes high-level steps with low-level details
// ❌ Before - Mixed abstractions
func ProcessOrder(order Order) error {
// Validation
if order.ID == "" {
return errors.New("invalid")
}
// Low-level DB setup
db, err := sql.Open("postgres", connStr)
if err != nil { return err }
defer db.Close()
// SQL construction
query := "INSERT INTO..."
// ... many lines
return nil
}
// ✅ After - Story-like
func ProcessOrder(order Order) error {
if err := validateOrder(order); err != nil {
return err
}
if err := saveToDatabase(order); err != nil {
return err
}
return notifyCustomer(order)
}
func validateOrder(order Order) error { /* ... */ }
func saveToDatabase(order Order) error { /* ... */ }
func notifyCustomer(order Order) error { /* ... */ }
Pattern 2: Extract Type (Primitive Obsession)
Signal: Complex logic operating on primitives
// ❌ Before - Primitive obsession
func ValidatePort(port int) error {
if port <= 0 || port >= 9000 {
return errors.New("invalid port")
}
return nil
}
func GetServiceURL(host string, port int) string {
return fmt.Sprintf("%s:%d", host, port)
}
// ✅ After - Custom type
type Port int
func NewPort(p int) (Port, error) {
if p <= 0 || p >= 9000 {
return 0, errors.New("invalid port")
}
return Port(p), nil
}
func (p Port) Int() int {
return int(p)
}
type ServiceAddress struct {
host string
port Port
}
func (a ServiceAddress) URL() string {
return fmt.Sprintf("%s:%d", a.host, a.port)
}
⚠️ Warning: Not Every Primitive Needs a Type!
Create types when: >1 meaningful methods, validation, complex logic, or controlled mutation DON'T when: One trivial method, good naming is enough, adds ceremony without benefit
// ❌ Over-abstraction
type IsValidFlag bool
func (f IsValidFlag) IsValid() bool { return bool(f) }
// ✅ Good naming or private fields
isValid bool // Clear enough!
→ See Example 2 for complete case study.
Pattern 3: Extract Function (Long Functions)
Signal: Function > 50 LOC or multiple responsibilities
// ❌ Before - Long function
func CreateUser(data map[string]interface{}) error {
// Validation (15 lines)
// ...
// Database operations (20 lines)
// ...
// Email notification (10 lines)
// ...
// Logging (5 lines)
// ...
return nil
}
// ✅ After - Extracted functions
func CreateUser(data map[string]interface{}) error {
user, err := validateAndParseUser(data)
if err != nil {
return err
}
if err := saveUser(user); err != nil {
return err
}
if err := sendWelcomeEmail(user); err != nil {
return err
}
logUserCreation(user)
return nil
}
Pattern 4: Early Returns (Deep Nesting)
Signal: Nesting > 2 levels
// ❌ Before - Deeply nested
func ProcessItem(item Item) error {
if item.IsValid() {
if item.IsReady() {
if item.HasPermission() {
// Process
return nil
} else {
return errors.New("no permission")
}
} else {
return errors.New("not ready")
}
} else {
return errors.New("invalid")
}
}
// ✅ After - Early returns
func ProcessItem(item Item) error {
if !item.IsValid() {
return errors.New("invalid")
}
if !item.IsReady() {
return errors.New("not ready")
}
if !item.HasPermission() {
return errors.New("no permission")
}
// Process
return nil
}
Pattern 5: Switch Extraction (Long Switch)
Signal: Switch statement with complex cases
// ❌ Before - Long switch in one function
func HandleRequest(reqType string, data interface{}) error {
switch reqType {
case "create":
// 20 lines of creation logic
case "update":
// 20 lines of update logic
case "delete":
// 15 lines of delete logic
default:
return errors.New("unknown type")
}
return nil
}
// ✅ After - Extracted handlers
func HandleRequest(reqType string, data interface{}) error {
switch reqType {
case "create":
return handleCreate(data)
case "update":
return handleUpdate(data)
case "delete":
return handleDelete(data)
default:
return errors.New("unknown type")
}
}
func handleCreate(data interface{}) error { /* ... */ }
func handleUpdate(data interface{}) error { /* ... */ }
func handleDelete(data interface{}) error { /* ... */ }
Refactoring Decision Tree
When linter fails, ask these questions (see reference.md for details):
- •
Does this read like a story?
- •No → Extract functions for different abstraction levels
- •
Can this be broken into smaller pieces?
- •By responsibility? → Extract functions
- •By task? → Extract functions
- •By category? → Extract functions
- •
Does logic run on primitives?
- •Yes → Is this primitive obsession? → Extract type
- •
Is function long due to switch statement?
- •Yes → Extract case handlers
- •
Are there deeply nested if/else?
- •Yes → Use early returns or extract functions
After Refactoring
Verify
- • Re-run
task lintwithfix- Should pass - • Run tests - Should still pass
- • Check coverage - Should maintain or improve
- • Code more readable? - Get feedback if unsure
May Need
- •New types created → Use @code-designing to validate design
- •New functions added → Ensure tests cover them
- •Major restructuring → Consider using @pre-commit-review
Output Format
🔧 REFACTORING COMPLETE Linter Issues Resolved: ✅ user/service.go:45 - Cyclomatic complexity (15 → 8) ✅ user/handler.go:23 - Cognitive complexity (25 → 12) Refactoring Applied: 1. Storifying: Extracted validateUser, saveUser, notifyUser 2. Extract Type: Created Port and ServiceAddress types 3. Early Returns: Reduced nesting in ProcessItem Files Modified: - user/service.go (+30, -45 lines) - user/port.go (new file, +25 lines) - user/address.go (new file, +35 lines) Next Steps: 1. Re-run linter: task lintwithfix → Should pass 2. Run tests: go test ./... → Should pass 3. If new types created → Consider @code-designing review 4. Proceed to @pre-commit-review phase
Learning from Examples
For real-world refactoring case studies that show the complete thought process:
Example 1: Storifying Mixed Abstractions
- •Transforms a 48-line fat function into lean orchestration + leaf type
- •Shows how to extract
IPConfigtype for collection and validation logic - •Demonstrates achieving 100% unit test coverage without mocking
Example 2: Primitive Obsession with Multiple Types
- •Transforms a 60-line function into a 7-line story by extracting 4 leaf types
- •Shows the Type Alias Pattern for config-friendly types
- •Demonstrates eliminating switch statement duplication
- •Fixed misleading function name (
validateCIDR→alignCIDRArgs)
See examples.md for complete case studies with thought process.
Integration with Other Skills
- •@code-designing: When refactoring creates new types, validate design
- •@testing: Ensure refactored code maintains test coverage
- •@pre-commit-review: Final validation before commit
See reference.md for complete refactoring patterns and decision tree.