chore: bootstrap skills library — 19 skills + installer + CI auto-tag
Some checks failed
release / tag (push) Has been cancelled
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]
This commit is contained in:
376
clean-code/references/clean-code.md
Normal file
376
clean-code/references/clean-code.md
Normal file
@@ -0,0 +1,376 @@
|
||||
# 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 { ... }
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user