Claude Code Plugins

Community-maintained marketplace

Feedback

Reducing Complexity

@obra/clank
25
0

Managing complexity is software's primary technical imperative - all other goals are secondary

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name Reducing Complexity
description Managing complexity is software's primary technical imperative - all other goals are secondary
when_to_use Before and during any design or implementation. When solution feels complicated. When code is hard to understand. When you can't keep entire design in mind. When complexity is proliferating. When applying methods mechanically without understanding why.
version 1.0.0
languages all

Reducing Complexity

Overview

Managing complexity is the most important technical topic in software development. All other technical goals—performance, features, elegance—are secondary to managing complexity.

Core principle: "There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies, and the other is to make it so complicated that there are no obvious deficiencies." - C.A.R. Hoare

Always choose the first way.

When to Use

Use as guiding principle for EVERY technical decision:

  • Design decisions (class structure, subsystems, interfaces)
  • Implementation decisions (algorithms, data structures)
  • Refactoring decisions (what to simplify)
  • Architecture decisions (how to partition system)
  • Code review (primary evaluation criterion)

Warning signs of complexity overload:

  • Can't keep design in mind all at once
  • Need to understand entire system to change one part
  • Applying methods mechanically without knowing why
  • Code works but you're not sure how
  • Difficult to explain how code works
  • Many edge cases and special conditions
  • Deep nesting (if inside if inside if...)
  • Long parameter lists (> 7 parameters)
  • Large routines that do many things
  • Classes with unclear purpose

Two Types of Complexity

Essential Complexity

Inherent in the real-world problem itself. Can't be eliminated, only managed.

Examples:

  • Interfacing with complex, disorderly real world
  • Identifying all dependencies and exception cases
  • Solutions that must be exactly correct, not approximately
  • Intricate interactions between real-world entities

Approach: Minimize what anyone's brain must deal with at one time.

Accidental Complexity

Complexity we introduce through our design and implementation choices. CAN be eliminated.

Examples:

  • Clever abstractions that obscure meaning
  • Unnecessary layers of indirection
  • Over-engineering for hypothetical futures
  • Complex solutions to simple problems
  • Inconsistent interfaces
  • Global variables creating hidden dependencies
  • Poor naming making code cryptic

Approach: Keep accidental complexity from needlessly proliferating.

How to Attack Complexity

Two fundamental approaches, three practical strategies:

  1. Minimize essential complexity anyone must deal with at once
  2. Keep accidental complexity from proliferating

Implemented through three strategies:

Strategy 1: Break Into Simple Pieces

Dijkstra's insight: No one's skull is big enough to contain a modern program. Organize programs so you can safely focus on one part at a time.

Think of it as mental juggling: More mental balls to keep in air = more likely to drop one = design or coding error.

At each level:

  • System → Subsystems
  • Subsystem → Classes
  • Class → Routines
  • Routine → Statements

Goal: Make each piece simple enough to understand fully in isolation.

Strategy 2: Minimize What You Must Understand At Once

Good design lets you safely IGNORE most of the program while working on any one part.

Questions to ask:

  • Can I understand this routine without understanding the whole system?
  • Can I modify this class without affecting others?
  • Can I focus on this subsystem without knowing internals of others?

If answer is no: Design isn't doing its job. Increase encapsulation, reduce coupling.

Strategy 3: Hide Complexity Behind Abstractions

Each abstraction should:

  • Present a simple, consistent interface
  • Hide messy details behind that interface
  • Allow you to work at problem level, not implementation level

Example:

# ❌ Low-level, complex
current_font.attribute = current_font.attribute or 0x02

# ✅ High-level, simple
current_font.set_bold_on()

Working with fonts (problem domain) > manipulating bit fields (implementation domain)

Desirable Design Characteristics

All aimed at managing complexity:

Characteristic Why It Reduces Complexity
Minimal complexity Primary goal - avoid clever designs, prefer simple
Loose coupling Minimize connections between parts
High fan-in Reuse utility classes (don't duplicate)
Low fan-out Each class uses few other classes (< 7)
Leanness No extra parts - finished when nothing more can be removed
Stratification Consistent abstraction levels - don't mix high and low
Good abstraction Interface hides implementation details
Good encapsulation Implementation details truly hidden

Each characteristic makes it easier to focus on one thing at a time.

The Complexity Test

Before Implementing

Ask yourself:

  1. Necessity: Do we actually need this right now?
  2. Simplicity: What's the simplest way to solve this?
  3. Directness: Can we solve this more directly?
  4. Value: Does the complexity add proportional value?
  5. Maintenance: Will this be easy to understand later?

If you hesitate on any question, simplify further.

During Implementation

Complexity overload symptom: "Doggedly applying a method that is clearly irrelevant." Like a mechanic whose car breaks down, so he puts water in the battery and empties the ashtrays.

If you catch yourself doing things mechanically without understanding why → STOP.

  • Step back
  • Simplify the approach
  • Understand before proceeding

After Implementation

Good design feels elegant and obvious:

  • "Of course it works that way"
  • Clean, no extra parts
  • Each piece does one thing well
  • Can explain it simply

Quote: "When I am working on a problem I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." - R. Buckminster Fuller

Reducing Accidental Complexity

At System Level

Partition into subsystems with clear boundaries:

  • Business rules separate from UI separate from database
  • Define which subsystems can communicate (restrict, don't allow all-to-all)
  • Acyclic dependency graph (no circular dependencies)

Think of it as hoses with water: More hoses to disconnect when pulling out a subsystem = more complexity. Minimize connections.

At Class Level

Each class implements ONE abstract data type:

  • One clear responsibility
  • Consistent level of abstraction in interface
  • All methods work toward consistent purpose

Bad: Class with methods for command stack, report formatting, AND global data initialization ✅ Good: Separate classes, each with focused purpose

At Routine Level

Keep routines short and focused:

  • Do one thing and do it well (functional cohesion)
  • If routine does multiple things → split into multiple routines
  • Work at single level of abstraction (don't mix high and low-level operations)

Length guideline: Natural length determined by function, but if > 200 lines, strongly consider splitting.

At Statement Level

Write in terms of problem domain, not implementation:

  • Use well-named variables and routines
  • Extract complex expressions into named variables
  • Replace magic numbers with named constants
  • Keep statements at consistent abstraction level

Techniques for Simplification

When Technique
Routine too complex Extract parts into smaller routines
Class too complex Split into multiple focused classes
Parameter list too long Group related parameters into object
Deep nesting Extract nested logic into routines, use early returns
Duplicate code Extract into shared routine
Complex conditional Extract into well-named boolean function
Magic numbers Replace with named constants
Low-level operations Hide behind high-level abstraction

Common Sources of Accidental Complexity

Watch for and eliminate:

  1. Over-engineering

    • Building for hypothetical future requirements
    • Generic, flexible solutions when simple, specific would work
    • Layers of abstraction that don't reduce complexity
  2. Clever code

    • Obscure tricks instead of straightforward approach
    • Showing off knowledge instead of solving problem simply
    • Complex one-liners instead of clear multi-line code
  3. Inconsistency

    • Multiple ways to do same thing
    • Inconsistent naming, interfaces, error handling
    • Mixed abstraction levels
  4. Poor organization

    • Related code scattered far apart
    • Unrelated code grouped together
    • No clear structure
  5. Lack of encapsulation

    • Implementation details exposed
    • Everything depending on everything else
    • Global variables creating hidden connections

The Complexity-Flexibility Tradeoff

More flexibility usually = more complexity

Examples of flexibility vs complexity:

  • Hard-coded value → Named constant → Config file → Runtime input → Plugin system
  • Each step right adds flexibility AND complexity

Rule: Build in flexibility needed to meet requirements. Don't add flexibility beyond what's required.

Ask: Do we ACTUALLY need this flexibility, or are we speculating about future needs?

Red Flags - Simplify Now

  • Solution seems complicated (it probably is)
  • Hard to explain how it works
  • Many special cases and conditions
  • Can't understand impact of changes
  • "Clever" feeling design
  • Deep inheritance hierarchies
  • Long chains of function calls
  • Need to understand whole system to change one part
  • Lots of global state
  • Inconsistent patterns throughout codebase

All of these mean: Iterate on design to find simpler approach.

Measuring Complexity

Informal metrics:

  • Can you explain it simply?
  • Can you hold design in mind all at once?
  • Can you safely ignore other parts while working on this part?
  • Is it obviously correct?

Formal metrics (if you need them):

  • Cyclomatic complexity (< 10 per routine)
  • Fan-out (< 7 classes used by any class)
  • Lines of code per routine (natural length, but review if > 200)
  • Depth of nesting (< 3-4 levels)

Most important: If it FEELS complex, it probably IS complex.

Integration with Other Principles

Complexity reduction supports:

  • Testability - Simple code is easier to test
  • Maintainability - Simple code is easier to modify
  • Reliability - Simple code has fewer bugs
  • Readability - Simple code is easier to understand
  • Performance - Simple code is easier to optimize

Everything gets easier when you reduce complexity first.

When Complexity is Justified

Generally, simplicity wins. But some scenarios genuinely require complexity:

Performance-Critical Systems

When complexity is acceptable:

  • Measured performance problems (not hypothetical)
  • Requirements specify response time/throughput
  • Simple approach tried first and measured as insufficient
  • Complexity is localized and encapsulated

Example: High-frequency trading system needs complex cache with eviction policies because every microsecond matters AND this has been measured.

Rule: Measure first, optimize second. Start simple, add complexity only when evidence demands it.

Safety-Critical Systems

When complexity is justified:

  • Redundancy for fault tolerance (backup systems, validation layers)
  • Extensive error checking and recovery
  • Formal verification requirements

Example: Aviation software with multiple redundant systems and extensive validation.

Rule: Safety complexity must be systematic and well-documented, not ad-hoc.

Legacy Integration

When complexity can't be avoided immediately:

  • Interfacing with poorly-designed external systems
  • Gradual migration from complex legacy system
  • Compliance with existing complex protocols

Strategy: Contain the complexity (see Legacy Code Strategy below).

The Justification Test

Before accepting complexity as necessary:

  1. Have I measured? (not assumed performance problem exists)
  2. Have I tried simple first? (and confirmed it doesn't work)
  3. Is complexity localized? (or does it spread throughout system)
  4. Is it documented? (why this complexity exists)
  5. Is there a plan to reduce it? (or is it permanent)

If you can't answer YES to all five: The complexity probably isn't justified.

Legacy Code Strategy

You inherit complex codebase. What do you do?

Don't Make It Worse

Rule Zero: Every change either reduces complexity or holds it steady. Never add to the complexity.

When touching legacy code:

  • Leave it simpler than you found it
  • Extract magic numbers to constants
  • Add clarifying comments
  • Extract long methods into smaller ones
  • Improve names of things you touch

"Boy scout rule": Always leave code cleaner than you found it.

Contain and Isolate

Build barriers around complex legacy code:

  1. Create adapter layer - New code talks to adapter, adapter talks to legacy
  2. Hide complexity behind interface - Interface presents what legacy does, hides how
  3. Never let legacy patterns spread - New code uses modern patterns

Example from Code Complete:

# Old system has terrible naming and structure
# Don't let it spread - create clean interface layer

class LegacyUserSystem:  # Hidden behind interface
    def get_usr_dat_rec(self, id): ...  # Terrible legacy code

class UserRepository:  # Clean interface your code uses
    def get_user(self, user_id):
        # Adapter: translates to/from legacy
        legacy = LegacyUserSystem()
        user_data = legacy.get_usr_dat_rec(user_id)
        return User.from_legacy(user_data)  # Convert to clean model

Now new code only sees UserRepository.get_user() - simple and clear.

Prioritize What to Simplify

Can't simplify everything at once. What first?

  1. Code you touch most often - Highest ROI for simplification
  2. Code that's hardest to understand - Biggest complexity impact
  3. Code blocking new features - Enabling future work
  4. Code with most bugs - Complexity correlates with defects

Measure: Track which files are modified most frequently. Simplify those first.

Incremental Simplification

Don't rewrite entire system. Incrementally reduce complexity:

  • Extract method: Long routine → smaller focused routines
  • Extract class: God class → focused single-responsibility classes
  • Replace conditional with polymorphism: Long if/else chains → strategy pattern
  • Introduce parameter object: Long parameter lists → cohesive object
  • Replace magic numbers: Hard-coded values → named constants

Each refactoring makes one piece simpler. Do many, and complexity drops significantly.

The Imperative

Every other technical goal is secondary to managing complexity:

  • Not performance (optimize simple code later)
  • Not features (simple working features > complex broken features)
  • Not elegance (simple and clear > elegant and obscure)
  • Not cleverness (obvious and simple > clever and complex)

When in doubt, choose simpler.

Real-World Impact

From Code Complete:

  • Projects fail from uncontrolled complexity when no one understands what code does
  • Complexity ratio: 1 to 10^15 (bit to hundreds of megabytes)
  • Humans can't handle that span - must organize to focus on pieces
  • "Complexity overload" = applying methods mechanically without understanding

Quote: "When software-project surveys report causes of project failure, they rarely identify technical reasons as primary causes... But when projects do fail for reasons that are primarily technical, the reason is often uncontrolled complexity."

Remember

Two ways to reduce complexity:

  1. Minimize essential complexity anyone must deal with at once
  2. Keep accidental complexity from proliferating

Both require conscious, continuous effort.

Managing complexity isn't optional. It's THE fundamental technical imperative.