| name | refactoring |
| description | Linter-driven refactoring patterns to reduce complexity and improve code quality. Use when linter fails with complexity issues (cyclomatic, cognitive, maintainability) or when code feels hard to read/maintain. Applies storifying, type extraction, and function extraction patterns. |
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.