Some checks failed
release / tag (push) Has been cancelled
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]
287 lines
6.3 KiB
Markdown
287 lines
6.3 KiB
Markdown
# 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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:
|
|
```typescript
|
|
// 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:
|
|
```typescript
|
|
// 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.
|