Files
skills/solid/references/complexity.md
Mathias d6a71e370e
Some checks failed
release / tag (push) Has been cancelled
chore: bootstrap skills library — 19 skills + installer + CI auto-tag
Phase 1 of mathias/skills extraction (infra#62 Track D — homelab
next-step plan addendum). Imports ~/dev/.skills/ verbatim (19 skill
dirs + SKILLS_INDEX.md) and adds the installation surface:

- Taskfile.yml — install / update / list / release / check targets
- install.sh — bootstrap installer for hosts without Task. Idempotent
  symlink wirer; default checkout at ~/.local/share/skills/ on every
  host; SKILLS_REF env var pins a tag (default: main).
- .gitea/workflows/release.yml — auto-tag every push to main by
  Bump-Type footer (major/minor/patch, default patch). Skipped when
  commit contains [skip-release].
- README — usage, versioning, contribution flow, secret-hygiene rule.

Phase 1 wires Claude Code only (~/.claude/skills/<name> global +
<repo>/.claude/skills/<name> per-repo). Phase 2 adds Crush, opencode,
antigravity, and gitea-resident agents (cobalt-dingo, agentsquad)
once their skill conventions are researched.

Public repo, markdown-only — no secrets, no client names. Verified
via pre-push grep before initial push.

[skip-release]
2026-05-24 14:59:54 +02:00

6.3 KiB

Managing Complexity

The Two Types of Complexity

Essential Complexity

Inherent to the problem domain. Cannot be removed, only managed.

  • Business rules
  • Domain logic
  • User requirements

Accidental Complexity

Introduced by our solutions. CAN and SHOULD be minimized.

  • Poor abstractions
  • Unnecessary indirection
  • Framework ceremony
  • Technical debt

Goal: Minimize accidental complexity while clearly expressing essential complexity.


Detecting Complexity

1. Change Amplification

Small changes require touching many files.

Symptom: "To add this field, I need to update 15 files."

Cause: Scattered responsibilities, poor abstraction boundaries.

2. Cognitive Load

Code is hard to understand, requires holding too much in memory.

Symptom: "I need to understand 10 other classes to understand this one."

Cause: Tight coupling, hidden dependencies, unclear naming.

3. Unknown Unknowns

Behavior is surprising, side effects are hidden.

Symptom: "I changed this, and something completely unrelated broke."

Cause: Global state, hidden dependencies, implicit contracts.


The XP Values for Fighting Complexity

From Extreme Programming:

1. Communication

Code should communicate clearly. Names, structure, tests all contribute.

2. Simplicity

Do the simplest thing that could possibly work.

3. Feedback

Fast feedback loops catch complexity early. TDD, CI, code review.

4. Courage

Refactor aggressively. Don't let complexity accumulate.

5. Respect

Respect future readers (including yourself). Write for humans first.


KISS - Keep It Simple, Silly

"The simplest solution that works is usually the best."

How to Apply:

  1. Start with the obvious solution
  2. Only add complexity when REQUIRED
  3. Prefer boring, well-understood approaches
  4. Question every abstraction
// Over-engineered
class UserServiceFactoryProvider {
  private static instance: UserServiceFactoryProvider;

  static getInstance(): UserServiceFactoryProvider { ... }
  createFactory(): UserServiceFactory { ... }
}

// KISS
class UserService {
  getUser(id: string): User { ... }
}

YAGNI - You Aren't Gonna Need It

"Don't build features until they're actually needed."

Warning Signs:

  • "We might need this later"
  • "It would be nice to have"
  • "Just in case"
  • "For future extensibility"

The Cost of YAGNI Violations:

  1. Development time - Building unused features
  2. Maintenance burden - Code that must be maintained
  3. Cognitive load - More to understand
  4. Wrong abstraction - Guessing future needs incorrectly
// YAGNI violation: Building for hypothetical needs
class User {
  // "We might need these someday"
  middleName?: string;
  secondaryEmail?: string;
  faxNumber?: string;
  linkedinProfile?: string;
  twitterHandle?: string;
}

// YAGNI: Only what's needed NOW
class User {
  name: string;
  email: Email;
}

DRY - Don't Repeat Yourself (with The Rule of Three)

"Every piece of knowledge should have a single, unambiguous representation."

BUT: The Rule of Three

Don't extract duplication until you see it THREE times.

Why? The wrong abstraction is worse than duplication.

Duplication #1 → Leave it
Duplication #2 → Note it, leave it
Duplication #3 → NOW extract it

Example:

// First time - leave it
function processUserOrder(order) {
  validate(order);
  calculateTax(order);
  save(order);
}

// Second time - note the similarity, but leave it
function processGuestOrder(order) {
  validate(order);
  calculateTax(order);
  save(order);
  sendGuestEmail(order);
}

// Third time - NOW extract
function processCorporateOrder(order) {
  validate(order);
  calculateTax(order);
  save(order);
  applyCorporateDiscount(order);
}

// After three, extract the common parts
function processOrder(order: Order, postProcessing: (o: Order) => void) {
  validate(order);
  calculateTax(order);
  save(order);
  postProcessing(order);
}

Separation of Concerns

"Each module should address a single concern."

Concerns to Separate:

  • Business logic vs Infrastructure
  • What (policy) vs How (mechanism)
  • Input vs Processing vs Output
  • Data vs Behavior

Example:

// BAD: Mixed concerns
class OrderProcessor {
  process(order: Order) {
    // Validation
    if (!order.items.length) throw new Error('Empty');

    // Business logic
    let total = 0;
    for (const item of order.items) {
      total += item.price * item.quantity;
    }

    // Persistence
    const db = new Database();
    db.query(`INSERT INTO orders...`);

    // Notification
    const email = new EmailClient();
    email.send(order.customer.email, 'Order confirmed');
  }
}

// GOOD: Separated concerns
class OrderProcessor {
  constructor(
    private validator: OrderValidator,
    private calculator: OrderCalculator,
    private repository: OrderRepository,
    private notifier: OrderNotifier
  ) {}

  process(order: Order): ProcessResult {
    this.validator.validate(order);
    const total = this.calculator.calculateTotal(order);
    const savedOrder = this.repository.save(order);
    this.notifier.notifyConfirmation(savedOrder);
    return ProcessResult.success(savedOrder);
  }
}

Managing Technical Debt

Types of Technical Debt:

  1. Deliberate - Conscious trade-off for speed
  2. Accidental - Mistakes, lack of knowledge
  3. Bit rot - Code degrades over time

The Boy Scout Rule:

"Leave the code better than you found it."

Every time you touch code:

  • Improve one small thing
  • Fix one naming issue
  • Extract one method
  • Add one missing test

When to Pay Down Debt:

  • When it's in your path (you're already there)
  • When it's blocking new features
  • When it's causing bugs
  • During dedicated refactoring time

When NOT to Refactor:

  • Code that works and won't change
  • Code being replaced soon
  • When you don't have tests

The Four Elements of Simple Design

In priority order (from XP):

  1. Runs all the tests

    • If it doesn't work, nothing else matters
  2. Expresses intent

    • Clear names, obvious structure
    • Code tells the story
  3. No duplication

    • DRY (but Rule of Three)
    • Single source of truth
  4. Minimal

    • Fewest classes and methods possible
    • Remove anything unnecessary

If these four are true, the design is simple enough.