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 { ... }
|
||||
}
|
||||
```
|
||||
334
clean-code/references/code-smells.md
Normal file
334
clean-code/references/code-smells.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# Code Smells & Anti-Patterns
|
||||
|
||||
## What Are Code Smells?
|
||||
|
||||
Indicators that something MAY be wrong. Not bugs, but design problems that make code hard to understand, change, or test.
|
||||
|
||||
## The Five Categories
|
||||
|
||||
### 1. Bloaters
|
||||
Code that has grown too large.
|
||||
|
||||
| Smell | Symptom | Refactoring |
|
||||
|-------|---------|-------------|
|
||||
| **Long Method** | > 10 lines | Extract Method |
|
||||
| **Large Class** | > 50 lines, multiple responsibilities | Extract Class |
|
||||
| **Long Parameter List** | > 3 parameters | Introduce Parameter Object |
|
||||
| **Data Clumps** | Same group of variables appear together | Extract Class |
|
||||
| **Primitive Obsession** | Primitives instead of small objects | Wrap in Value Object |
|
||||
|
||||
### 2. Object-Orientation Abusers
|
||||
Misuse of OO principles.
|
||||
|
||||
| Smell | Symptom | Refactoring |
|
||||
|-------|---------|-------------|
|
||||
| **Switch Statements** | Type checking, large switch/if-else | Replace with Polymorphism |
|
||||
| **Parallel Inheritance** | Adding subclass requires adding another | Merge Hierarchies |
|
||||
| **Refused Bequest** | Subclass doesn't use parent methods | Replace Inheritance with Delegation |
|
||||
| **Alternative Classes** | Different interfaces, same concept | Rename, Extract Superclass |
|
||||
|
||||
### 3. Change Preventers
|
||||
Code that makes changes difficult.
|
||||
|
||||
| Smell | Symptom | Refactoring |
|
||||
|-------|---------|-------------|
|
||||
| **Divergent Change** | One class changed for many reasons | Extract Class (SRP) |
|
||||
| **Shotgun Surgery** | One change touches many classes | Move Method/Field together |
|
||||
| **Parallel Inheritance** | (see above) | Merge Hierarchies |
|
||||
|
||||
### 4. Dispensables
|
||||
Code that can be removed.
|
||||
|
||||
| Smell | Symptom | Refactoring |
|
||||
|-------|---------|-------------|
|
||||
| **Comments** | Explaining bad code | Rename, Extract Method |
|
||||
| **Duplicate Code** | Copy-paste | Extract Method, Pull Up Method |
|
||||
| **Dead Code** | Unreachable code | Delete |
|
||||
| **Speculative Generality** | "Just in case" code | Delete (YAGNI) |
|
||||
| **Lazy Class** | Class that does almost nothing | Inline Class |
|
||||
|
||||
### 5. Couplers
|
||||
Excessive coupling between classes.
|
||||
|
||||
| Smell | Symptom | Refactoring |
|
||||
|-------|---------|-------------|
|
||||
| **Feature Envy** | Method uses another class's data extensively | Move Method |
|
||||
| **Inappropriate Intimacy** | Classes know too much about each other | Move Method, Extract Class |
|
||||
| **Message Chains** | `a.getB().getC().getD()` | Hide Delegate |
|
||||
| **Middle Man** | Class only delegates | Inline Class |
|
||||
|
||||
---
|
||||
|
||||
## The Seven Most Common Code Smells
|
||||
|
||||
### 1. Long Method
|
||||
|
||||
**Symptom:** Method > 10 lines, doing multiple things.
|
||||
|
||||
```typescript
|
||||
// SMELL
|
||||
function processOrder(order: Order) {
|
||||
// Validate
|
||||
if (!order.items.length) throw new Error('Empty');
|
||||
if (!order.customer) throw new Error('No customer');
|
||||
|
||||
// Calculate
|
||||
let total = 0;
|
||||
for (const item of order.items) {
|
||||
total += item.price * item.quantity;
|
||||
if (item.discount) {
|
||||
total -= item.discount;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply tax
|
||||
const taxRate = getTaxRate(order.customer.state);
|
||||
total = total * (1 + taxRate);
|
||||
|
||||
// Save
|
||||
db.orders.insert({ ...order, total });
|
||||
|
||||
// Notify
|
||||
emailService.send(order.customer.email, 'Order confirmed');
|
||||
}
|
||||
|
||||
// REFACTORED
|
||||
function processOrder(order: Order) {
|
||||
validateOrder(order);
|
||||
const total = calculateTotal(order);
|
||||
saveOrder(order, total);
|
||||
notifyCustomer(order);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Large Class
|
||||
|
||||
**Symptom:** Class with many responsibilities, > 50 lines.
|
||||
|
||||
```typescript
|
||||
// SMELL: God class
|
||||
class User {
|
||||
// User data
|
||||
name: string;
|
||||
email: string;
|
||||
|
||||
// Authentication
|
||||
login() { }
|
||||
logout() { }
|
||||
resetPassword() { }
|
||||
|
||||
// Preferences
|
||||
setTheme() { }
|
||||
setLanguage() { }
|
||||
|
||||
// Notifications
|
||||
sendEmail() { }
|
||||
sendSMS() { }
|
||||
|
||||
// Billing
|
||||
charge() { }
|
||||
refund() { }
|
||||
}
|
||||
|
||||
// REFACTORED: Separate classes
|
||||
class User { name: string; email: string; }
|
||||
class AuthService { login(); logout(); resetPassword(); }
|
||||
class UserPreferences { setTheme(); setLanguage(); }
|
||||
class NotificationService { sendEmail(); sendSMS(); }
|
||||
class BillingService { charge(); refund(); }
|
||||
```
|
||||
|
||||
### 3. Feature Envy
|
||||
|
||||
**Symptom:** Method uses another class's data more than its own.
|
||||
|
||||
```typescript
|
||||
// SMELL: Order envies Customer
|
||||
class Order {
|
||||
calculateShipping(customer: Customer): number {
|
||||
if (customer.country === 'US') {
|
||||
if (customer.state === 'CA') return 10;
|
||||
return 15;
|
||||
}
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
|
||||
// REFACTORED: Move to Customer
|
||||
class Customer {
|
||||
getShippingCost(): number {
|
||||
if (this.country === 'US') {
|
||||
if (this.state === 'CA') return 10;
|
||||
return 15;
|
||||
}
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
|
||||
class Order {
|
||||
calculateShipping(): number {
|
||||
return this.customer.getShippingCost();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Primitive Obsession
|
||||
|
||||
**Symptom:** Using primitives for domain concepts.
|
||||
|
||||
```typescript
|
||||
// SMELL
|
||||
function createUser(email: string, age: number, zipCode: string) {
|
||||
// No validation, easy to pass wrong values
|
||||
if (!email.includes('@')) throw new Error();
|
||||
if (age < 0) throw new Error();
|
||||
}
|
||||
|
||||
// REFACTORED: Value objects
|
||||
class Email {
|
||||
constructor(private value: string) {
|
||||
if (!value.includes('@')) throw new InvalidEmail();
|
||||
}
|
||||
}
|
||||
|
||||
class Age {
|
||||
constructor(private value: number) {
|
||||
if (value < 0 || value > 150) throw new InvalidAge();
|
||||
}
|
||||
}
|
||||
|
||||
function createUser(email: Email, age: Age, address: Address) {
|
||||
// Type system prevents invalid data
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Switch Statements
|
||||
|
||||
**Symptom:** Switching on type, repeated across codebase.
|
||||
|
||||
```typescript
|
||||
// SMELL
|
||||
function getArea(shape: Shape): number {
|
||||
switch (shape.type) {
|
||||
case 'circle': return Math.PI * shape.radius ** 2;
|
||||
case 'rectangle': return shape.width * shape.height;
|
||||
case 'triangle': return 0.5 * shape.base * shape.height;
|
||||
}
|
||||
}
|
||||
|
||||
function getPerimeter(shape: Shape): number {
|
||||
switch (shape.type) { // Same switch again!
|
||||
case 'circle': return 2 * Math.PI * shape.radius;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// REFACTORED: Polymorphism
|
||||
interface Shape {
|
||||
getArea(): number;
|
||||
getPerimeter(): number;
|
||||
}
|
||||
|
||||
class Circle implements Shape {
|
||||
constructor(private radius: number) {}
|
||||
getArea(): number { return Math.PI * this.radius ** 2; }
|
||||
getPerimeter(): number { return 2 * Math.PI * this.radius; }
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Inappropriate Intimacy
|
||||
|
||||
**Symptom:** Classes know too much about each other's internals.
|
||||
|
||||
```typescript
|
||||
// SMELL
|
||||
class Order {
|
||||
process() {
|
||||
const inventory = new Inventory();
|
||||
// Reaching into inventory's internals
|
||||
for (const item of this.items) {
|
||||
const stock = inventory.stockLevels[item.sku];
|
||||
if (stock.quantity < item.quantity) {
|
||||
throw new Error('Out of stock');
|
||||
}
|
||||
inventory.stockLevels[item.sku].quantity -= item.quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REFACTORED: Tell, don't ask
|
||||
class Inventory {
|
||||
reserve(items: OrderItem[]): ReserveResult {
|
||||
// Inventory manages its own state
|
||||
for (const item of items) {
|
||||
if (!this.canReserve(item)) {
|
||||
return ReserveResult.outOfStock(item);
|
||||
}
|
||||
}
|
||||
this.deductStock(items);
|
||||
return ReserveResult.success();
|
||||
}
|
||||
}
|
||||
|
||||
class Order {
|
||||
process(inventory: Inventory) {
|
||||
const result = inventory.reserve(this.items);
|
||||
if (!result.isSuccess()) {
|
||||
throw new OutOfStockError(result.failedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Speculative Generality
|
||||
|
||||
**Symptom:** "Just in case" abstractions that aren't used.
|
||||
|
||||
```typescript
|
||||
// SMELL: Over-engineered for hypothetical needs
|
||||
interface PaymentProcessor {
|
||||
process(): void;
|
||||
rollback(): void;
|
||||
audit(): void;
|
||||
generateReport(): void;
|
||||
scheduleRecurring(): void;
|
||||
}
|
||||
|
||||
class StripeProcessor implements PaymentProcessor {
|
||||
process() { /* actual code */ }
|
||||
rollback() { throw new Error('Not implemented'); }
|
||||
audit() { throw new Error('Not implemented'); }
|
||||
generateReport() { throw new Error('Not implemented'); }
|
||||
scheduleRecurring() { throw new Error('Not implemented'); }
|
||||
}
|
||||
|
||||
// REFACTORED: YAGNI
|
||||
interface PaymentProcessor {
|
||||
process(): void;
|
||||
}
|
||||
|
||||
class StripeProcessor implements PaymentProcessor {
|
||||
process() { /* actual code */ }
|
||||
}
|
||||
// Add other methods when actually needed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prevention Strategies
|
||||
|
||||
1. **Follow Object Calisthenics** - Rules prevent most smells
|
||||
2. **Practice TDD** - Tests reveal design problems early
|
||||
3. **Review in pairs** - Fresh eyes catch smells
|
||||
4. **Refactor continuously** - Don't let smells accumulate
|
||||
5. **Apply SOLID** - Prevents structural smells
|
||||
6. **Use static analysis** - Tools catch common issues
|
||||
|
||||
---
|
||||
|
||||
## When You Find a Smell
|
||||
|
||||
1. **Confirm it's a problem** - Not all smells need fixing
|
||||
2. **Ensure test coverage** - Before refactoring
|
||||
3. **Refactor in small steps** - Keep tests passing
|
||||
4. **Commit frequently** - Easy to revert if needed
|
||||
Reference in New Issue
Block a user