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.
Automatically refactors code when linter fails complexity checks. Applies patterns like early returns, function extraction, and type creation to reduce cyclomatic/cognitive complexity until linter passes.
/plugin marketplace add buzzdan/ai-coding-rules/plugin install go-linter-driven-development@ai-coding-rulesThis skill is limited to using the following tools:
code-design-ref.mdexamples.mdreference.mdReference: See reference.md for complete decision tree and all patterns.
Examples: See examples.md for real-world refactoring case studies.
</objective>
<quick_start>
IMPORTANT: This skill operates autonomously - no user confirmation needed. </quick_start>
<when_to_use>
<learning_resources> Choose your learning path:
<analysis_phase> Before applying any refactoring patterns, automatically analyze the context:
<system_context_analysis> AUTOMATICALLY ANALYZE:
<type_discovery> Proactively identify hidden types in the code:
POTENTIAL TYPES TO DISCOVER:
Data being parsed from strings → Parse* types Example: ParseCommandResult(), ParseLogEntry()
Scattered validation logic → Validated types Example: Email, Port, IPAddress types
Data that always travels together → Aggregate types Example: UserCredentials, ServerConfig
Complex conditions → State/status types Example: DeploymentStatus with IsReady(), CanProceed()
Repeated string manipulation → Types with methods Example: FilePath with Dir(), Base(), Ext() </type_discovery>
<analysis_output> The analysis produces a refactoring plan identifying:
<refactoring_signals> <linter_failures> Complexity Issues:
Architectural Issues:
Design Smells:
<code_smells>
<automation_flow> This skill operates completely autonomously once invoked:
<iteration_loop> AUTOMATED PROCESS:
<pattern_priority> For Complexity Failures (cyclomatic, cognitive, maintainability):
For Architectural Failures (noglobals, singletons):
For Design Smells (dupl, goconst):
<no_manual_intervention>
<refactoring_patterns>
<pattern name="storifying" signal="Mixed abstraction levels"> ```go // 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>
<pattern name="extract_type" signal="Primitive obsession or unstructured data">
<juiciness_test version="2">
**BEHAVIORAL JUICINESS** (rich behavior):
- Complex validation rules (regex, ranges, business rules)
- Multiple meaningful methods (≥2 methods)
- State transitions or transformations
- Format conversions (different representations)
**STRUCTURAL JUICINESS** (organizing complexity):
- Parsing unstructured data into fields
- Grouping related data that travels together
- Making implicit structure explicit
- Replacing map[string]interface{} with typed fields
**USAGE JUICINESS** (simplifies code):
- Used in multiple places
- Significantly simplifies calling code
- Makes tests cleaner and more focused
**Score**: Need "yes" in at least ONE category to create the type
</juiciness_test>
```go
// NOT JUICY - Don't create type
func ValidateUserID(id string) error {
if id == "" {
return errors.New("empty id")
}
return nil
}
// Just use: if userID == ""
// JUICY (Behavioral) - Complex validation
type Email string
func ParseEmail(s string) (Email, error) {
if s == "" {
return "", errors.New("empty email")
}
if !emailRegex.MatchString(s) {
return "", errors.New("invalid format")
}
if len(s) > 255 {
return "", errors.New("too long")
}
return Email(s), nil
}
func (e Email) Domain() string { /* extract domain */ }
func (e Email) LocalPart() string { /* extract local */ }
// JUICY (Structural) - Parsing complex data
type CommandResult struct {
FailedFiles []string
SuccessFiles []string
Message string
ExitCode int
}
func ParseCommandResult(output string) (CommandResult, error) {
// Parse unstructured output into structured fields
}
Warning Signs of Over-Engineering:
See Example 2 for complete case study. </pattern>
<pattern name="extract_function" signal="Function > 50 LOC or multiple responsibilities"> ```go // 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>
<pattern name="early_returns" signal="Deep nesting > 2 levels">
```go
// 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>
<pattern name="switch_extraction" signal="Switch statement with complex cases">
```go
// 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 { / ... */ }
</pattern>
<pattern name="dependency_rejection" signal="noglobals linter fails or global/singleton usage">
**Goal**: Create "islands of clean code" by incrementally pushing dependencies up the call chain
**Strategy**: Work from bottom-up, rejecting dependencies one level at a time
- DON'T do massive refactoring all at once
- Start at deepest level (furthest from main)
- Extract clean type with dependency injected
- Push global access up one level
- Repeat until global only at entry points
```go
// BEFORE - Global accessed deep in code
func PublishEvent(event Event) error {
conn, err := nats.Connect(env.Configs.NATsAddress)
// ... complex logic
}
// AFTER - Dependency rejected up one level
type EventPublisher struct {
natsAddress string // injected, not global
}
func NewEventPublisher(natsAddress string) *EventPublisher {
return &EventPublisher{natsAddress: natsAddress}
}
func (p *EventPublisher) Publish(event Event) error {
conn, err := nats.Connect(p.natsAddress)
// ... same logic, now testable
}
// Caller pushed up (closer to main)
func SetupMessaging() *EventPublisher {
return NewEventPublisher(env.Configs.NATsAddress) // Global only here
}
Result: EventPublisher is now 100% testable without globals
Key Principles:
See Example 3 for complete case study. </pattern>
</refactoring_patterns>
<decision_tree> When linter fails, ask these questions (see reference.md for details):
Does this read like a story?
Can this be broken into smaller pieces?
Does logic run on primitives?
Is function long due to switch statement?
Are there deeply nested if/else?
<testing_integration> When creating new types or extracting functions during refactoring:
ALWAYS invoke @testing skill to write tests for:
<island_definition> A type is an "island of clean code" if:
Examples of testable islands:
NATSClient with injected natsAddress string (no other dependencies)Email type with validation logic (no dependencies)ServiceEndpoint that uses Port value object (both are testable islands)OrderService with injected Repository and EventPublisher (all testable)Note: Islands can depend on other value objects and still be isolated! </island_definition>
<workflow> REFACTORING → TESTING: 1. Extract type during refactoring 2. Immediately invoke @testing skill 3. @testing skill writes appropriate tests 4. Verify tests pass 5. Continue refactoring </workflow> </testing_integration><stopping_criteria> STOP when ALL of these are met:
Warning Signs of Over-Engineering:
Pragmatic Approach: IF linter passes AND code is readable: STOP - Don't extract more EVEN IF you could theoretically extract more: STOP - Avoid abstraction bloat </stopping_criteria>
<output_format>
CONTEXT ANALYSIS
Function: CreateUser (user/service.go:45)
Role: Core user creation orchestration
Called by:
- api/handler.go:89 (HTTP endpoint)
- cmd/user.go:34 (CLI command)
Potential types spotted:
- Email: Complex validation logic scattered
- UserID: Generation and validation logic
REFACTORING APPLIED
Patterns Successfully Applied:
1. Early Returns: Reduced nesting from 4 to 2 levels
2. Storifying: Extracted validate(), save(), notify()
3. Extract Type: Created Email and PhoneNumber types
Patterns Tried but Insufficient:
- Extract Function alone: Still too complex, needed types
Types Created (with Juiciness Score):
Email type (JUICY - Behavioral + Usage):
- Behavioral: ParseEmail(), Domain(), LocalPart() methods
- Usage: Used in 5+ places across codebase
- Island: Testable in isolation
- → Invoke @testing skill to write tests
Types Considered but Rejected (NOT JUICY):
- UserID: Only empty check, good naming sufficient
- Status: Just string constants, enum adequate
METRICS
Complexity Reduction:
- Cyclomatic: 18 → 7
- Cognitive: 25 → 8
- LOC: 120 → 45
- Nesting: 4 → 2
FILES MODIFIED
Modified:
- user/service.go (+15, -75 lines)
Created (Islands of Clean Code):
- user/email.go (new, +45 lines) → Ready for @testing skill
AUTOMATION COMPLETE
Stopping Criteria Met:
- Linter passes (0 issues)
- All functions < 50 LOC
- Max nesting = 2 levels
- Code reads like a story
- No more juicy abstractions
Ready for: @pre-commit-review phase
</output_format>
<integration> **Invoked By (Automatic Triggering)**: - **@linter-driven-development**: Automatically invokes when linter fails (Phase 3) - **@pre-commit-review**: Automatically invokes when design issues detected (Phase 4)Iterative Loop:
Invokes (When Needed):
See reference.md for complete refactoring patterns and decision tree. </integration>
<success_criteria> Refactoring is complete when ALL of the following are true:
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.