Files
skills/skills/clean-code/references/clean-code.md
T
mathiasandClaude Opus 5 56a947b2a0 refactor(plugin)!: ship as a Claude Code plugin, one dir link per harness
Skills move to skills/<name>/SKILL.md and the repo gains both manifests, so
it is now a marketplace hosting exactly one plugin whose root is the repo
root (source: "."). Claude Code installs it from git; no symlinks involved.

Why: install.sh wired the library into every consuming repo as an ABSOLUTE
symlink to $CHECKOUT_DIR. The checkout moved three times, most recently when
local-dev@1966d5a dropped the embedded .skills/ copy, and nothing migrated
the links — 243 dangling links across 12 repos, hidden from git by the
**/.claude/skills/ ignore rule. Worse, ~/dev spans two path roots
(/Users/mathias on the iCloud-shared Macs, /home/mathias on koala), so an
absolute link stored there is broken on one side by construction. Re-running
the installer cannot fix that; only removing the mechanism can.

install.sh keeps the harnesses with no plugin system and drops from 42 links
per harness to one directory link, via the convergent ~/.agents/skills
convention that Pi and OpenCode both read:

  ~/.agents/skills          -> $CHECKOUT_DIR/skills
  ~/.config/crush/skills    -> ~/.agents/skills
  ~/.config/opencode/skills -> ~/.agents/skills

wire_claude_repo is deleted outright rather than gated behind a flag — on a
mixed-root fleet it cannot be made correct. Antigravity and Vibe targets go
too; both unused. CHECKOUT_DIR keeps its host-local default deliberately:
pointing it into ~/dev would put a git working tree inside iCloud with two
concurrent writers.

Taskfile drops five near-identical wiring loops (they had already drifted)
and delegates to install.sh. `task check` gains frontmatter validation, a
stray-root-entry gate, and manifest/tag version parity — the loose
secret-hygiene.md sat unloadable for months because nothing checked layout.

Verified: claude plugin validate . passes clean; a probe skill loaded as
skills:zzprobe through --plugin-dir; task check green at 42 skills.

Refs: #23
Bump-Type: minor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
2026-08-23 14:29:51 +02:00

377 lines
7.5 KiB
Markdown

# Clean Code Practices
## What is Clean Code?
Code that is:
- **Easy to understand** - reveals intent clearly
- **Easy to change** - modifications are localized
- **Easy to test** - dependencies are injectable
- **Simple** - no unnecessary complexity
## The Human-Centered Approach
Code has THREE consumers:
1. **Users** - get their needs met
2. **Customers** - make or save money
3. **Developers** - must maintain it
Design for all three, but remember: **developers read code 10x more than they write it.**
## Naming Principles
### 1. Consistency & Uniqueness (HIGHEST PRIORITY)
Same concept = same name everywhere. One name per concept.
```typescript
// BAD: Inconsistent names for same concept
getUserById(id)
fetchCustomerById(id)
retrieveClientById(id)
// GOOD: Consistent
getUser(id)
getOrder(id)
getProduct(id)
```
### 2. Understandability
Use domain language, not technical jargon.
```typescript
// BAD: Technical
const arr = users.filter(u => u.isActive);
// GOOD: Domain language
const activeCustomers = users.filter(user => user.isActive);
```
### 3. Specificity
Avoid vague names: `data`, `info`, `manager`, `handler`, `processor`, `utils`
```typescript
// BAD: Vague
class DataManager { }
function processInfo(data) { }
// GOOD: Specific
class OrderRepository { }
function validatePayment(payment) { }
```
### 4. Brevity (but not at cost of clarity)
Short names are good only if meaning is preserved.
```typescript
// BAD: Too cryptic
const usrLst = getUsrs();
// BAD: Unnecessarily long
const listOfAllActiveUsersInTheSystem = getActiveUsers();
// GOOD: Brief but clear
const activeUsers = getActiveUsers();
```
### 5. Searchability
Names should be unique enough to grep/search.
```typescript
// BAD: Common word, hard to search
const data = fetch();
// GOOD: Unique, searchable
const orderSummary = fetchOrderSummary();
```
### 6. Pronounceability
You should be able to say it in conversation.
```typescript
// BAD
const genymdhms = generateYearMonthDayHourMinuteSecond();
// GOOD
const timestamp = generateTimestamp();
```
### 7. Austerity
Avoid unnecessary filler words.
```typescript
// BAD: Redundant
const userData = user; // 'Data' adds nothing
class UserClass { } // 'Class' adds nothing
// GOOD
const user = user;
class User { }
```
---
## Object Calisthenics (9 Rules)
Exercises to improve OO design. Follow strictly during practice, relax slightly in production.
### 1. One Level of Indentation per Method
```typescript
// BAD: Multiple levels
function process(orders: Order[]) {
for (const order of orders) {
if (order.isValid()) {
for (const item of order.items) {
if (item.inStock) {
// process...
}
}
}
}
}
// GOOD: Extract methods
function process(orders: Order[]) {
orders.filter(o => o.isValid()).forEach(processOrder);
}
function processOrder(order: Order) {
order.items.filter(i => i.inStock).forEach(processItem);
}
```
### 2. Don't Use the ELSE Keyword
Use early returns, guard clauses, or polymorphism.
```typescript
// BAD: else
function getDiscount(user: User): number {
if (user.isPremium) {
return 20;
} else {
return 0;
}
}
// GOOD: Early return
function getDiscount(user: User): number {
if (user.isPremium) return 20;
return 0;
}
```
### 3. Wrap All Primitives and Strings
Primitives should be wrapped in domain objects when they have meaning.
```typescript
// BAD: Primitive obsession
function createUser(email: string, age: number) { }
// GOOD: Value objects
class Email {
constructor(private value: string) {
if (!this.isValid(value)) throw new InvalidEmail();
}
private isValid(email: string): boolean { ... }
}
class Age {
constructor(private value: number) {
if (value < 0 || value > 150) throw new InvalidAge();
}
}
function createUser(email: Email, age: Age) { }
```
### 4. First-Class Collections
Any class with a collection should have no other instance variables.
```typescript
// BAD: Collection mixed with other state
class Order {
items: OrderItem[] = [];
customerId: string;
total: number;
}
// GOOD: Collection is its own class
class OrderItems {
constructor(private items: OrderItem[] = []) {}
add(item: OrderItem): void { ... }
total(): Money { ... }
isEmpty(): boolean { ... }
}
class Order {
constructor(
private items: OrderItems,
private customerId: CustomerId
) {}
}
```
### 5. One Dot per Line (Law of Demeter)
Don't chain through object graphs.
```typescript
// BAD: Train wreck
const city = order.customer.address.city;
// GOOD: Tell, don't ask
const city = order.getShippingCity();
```
### 6. Don't Abbreviate
If a name is too long to type, the class is doing too much.
```typescript
// BAD
const custRepo = new CustRepo();
const ord = new Ord();
// GOOD
const customerRepository = new CustomerRepository();
const order = new Order();
```
### 7. Keep All Entities Small
- Classes: < 50 lines
- Methods: < 10 lines
- Files: < 100 lines
If larger, it's probably doing too much. Split it.
### 8. No Classes with More Than Two Instance Variables
Forces small, focused classes.
```typescript
// BAD: Too many variables
class Order {
id: string;
customerId: string;
items: Item[];
total: number;
status: string;
}
// GOOD: Composed of smaller objects
class Order {
constructor(
private id: OrderId,
private details: OrderDetails
) {}
}
class OrderDetails {
constructor(
private customer: Customer,
private lineItems: LineItems
) {}
}
```
### 9. No Getters/Setters/Properties
Objects should have behavior, not just data. Tell objects what to do.
```typescript
// BAD: Data bag with getters
class Account {
getBalance(): number { return this.balance; }
setBalance(value: number) { this.balance = value; }
}
// Caller does the work
if (account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
}
// GOOD: Behavior-rich object
class Account {
withdraw(amount: Money): WithdrawResult {
if (!this.canWithdraw(amount)) {
return WithdrawResult.insufficientFunds();
}
this.balance = this.balance.subtract(amount);
return WithdrawResult.success();
}
}
// Caller tells, object decides
const result = account.withdraw(amount);
```
---
## Comments
### When to Write Comments
**Only write comments to explain WHY, not WHAT or HOW.**
Code explains what and how. Comments explain business reasons, non-obvious decisions, or warnings.
```typescript
// BAD: Explains what (redundant)
// Add 1 to counter
counter++;
// GOOD: Explains why
// Compensate for 0-based indexing in legacy API
counter++;
```
### Prefer Self-Documenting Code
Instead of commenting, rename to make intent clear.
```typescript
// BAD: Comment needed
// Check if user can access premium features
if (user.subscriptionLevel >= 2 && !user.isBanned) { }
// GOOD: Self-documenting
if (user.canAccessPremiumFeatures()) { }
```
---
## Formatting
### Vertical Spacing
- Related code together
- Blank lines between concepts
- Most important/public at top
### Horizontal Spacing
- Consistent indentation
- Space around operators
- Max line length ~80-120 characters
### Storytelling
Code should read top-to-bottom like a story. High-level at top, details below.
```typescript
class OrderProcessor {
// Public API first
process(order: Order): ProcessResult {
this.validate(order);
this.calculateTotals(order);
return this.save(order);
}
// Supporting methods below, in order of appearance
private validate(order: Order): void { ... }
private calculateTotals(order: Order): void { ... }
private save(order: Order): ProcessResult { ... }
}
```