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]
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:
- Start with the obvious solution
- Only add complexity when REQUIRED
- Prefer boring, well-understood approaches
- 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:
- Development time - Building unused features
- Maintenance burden - Code that must be maintained
- Cognitive load - More to understand
- 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:
- Deliberate - Conscious trade-off for speed
- Accidental - Mistakes, lack of knowledge
- 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):
-
Runs all the tests
- If it doesn't work, nothing else matters
-
Expresses intent
- Clear names, obvious structure
- Code tells the story
-
No duplication
- DRY (but Rule of Three)
- Single source of truth
-
Minimal
- Fewest classes and methods possible
- Remove anything unnecessary
If these four are true, the design is simple enough.