# Clean Code

> Clean code rules - KISS, DRY, YAGNI, SOLID, descriptive names, small functions, early return, custom errors, effective comments, readable tests (AAA)

- Skill: `14bryanespinoza/clean-code` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/clean-code`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/clean-code/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/clean-code

---


# Clean Code — Rules and Conventions

---

## 1. Philosophy

1. **Code is read more than written** — optimize for readability.
2. **Explicit over implicit** — clear intent beats clever shortcuts.
3. **Consistency over preference** — follow project conventions, not personal style.
4. **Refactor mercilessly** — technical debt compounds; pay it down continuously.
5. **Tests are documentation** — they specify behavior better than comments.

---

## 2. Naming

### Principles

| Principle               | Example                              |
| ----------------------- | ------------------------------------ |
| **Intention-revealing** | `calculateShippingCost` not `calc()` |
| **Pronounceable**       | `userCount` not `usrCnt`             |
| **Searchable**          | `MAX_RETRY_ATTEMPTS` not `3`         |
| **No mental mapping**   | `isActive` not `flag`                |
| **Domain vocabulary**   | `Order.place()` not `createOrder()`  |

### Conventions

| Type                    | Convention        | Example                          |
| ----------------------- | ----------------- | -------------------------------- |
| **Variables/Functions** | camelCase         | `getUserById`, `maxRetries`      |
| **Classes/Interfaces**  | PascalCase        | `UserService`, `OrderRepository` |
| **Constants**           | SCREAMING_SNAKE   | `MAX_RETRY_ATTEMPTS`             |
| **Types/Interfaces**    | PascalCase        | `User`, `OrderDto`               |
| **Type parameters**     | PascalCase        | `<TUser, TOrder>`                |
| **Private fields**      | \_camelCase       | `_cache`, `_config`              |
| **Boolean**             | is/has/can/should | `isActive`, `hasPermission`      |

### Rules

- **No abbreviations** — `userId` not `uid`, `calculateTotal` not `calcTot`
- **No type in name** — `users` not `userList`, `getUser` not `getUserObject`
- **One word per concept** — `fetch`, `retrieve`, `get` — pick one
- **No magic numbers** — `const MAX_RETRIES = 3`

---

## 3. Functions

### Principles Functions

- **Small** — 10-20 lines max
- **Single responsibility** — does one thing well
- **Pure when possible** — same input → same output, no side effects
- **Few parameters** — 0-2 ideal, 3 max (use object for more)

### Patterns

```ts
// ✅ Good: small, focused, descriptive
function calculateShippingCost(order: Order, address: Address): Money {
  const baseRate = getBaseRate(address.zone);
  const weightSurcharge = calculateWeightSurcharge(order.totalWeight);
  return baseRate.add(weightSurcharge);
}

// ❌ Bad: large, multiple responsibilities
function processOrder(order, address, user, sendEmail, saveToDb) {
  // 50 lines of validation, calculation, email, db save...
}
```

### Rules Functions

- **Extract until you can't** — if function does A then B, split
- **Guard clauses first** — early return for edge cases
- **No output parameters** — return values, don't mutate args
- **No flag arguments** — `renderUser(user, true)` → `renderAdminUser(user)`

---

## 4. Early Return / Guard Clauses

```ts
// ✅ Good: flat, readable
function processPayment(payment: Payment): Result {
  if (!payment.isValid()) return err(new InvalidPaymentError());
  if (payment.amount.lte(0)) return err(new InvalidAmountError());
  if (payment.expired()) return err(new ExpiredPaymentError());

  return process(payment);
}

// ❌ Bad: nested, hard to follow
function processPayment(payment: Payment): Result {
  if (payment.isValid()) {
    if (payment.amount.gt(0)) {
      if (!payment.expired()) {
        return process(payment);
      }
    }
  }
  return err(new PaymentError());
}
```

### Rules Early Return

- **Return early** — reduce nesting
- **Validate inputs first** — fail fast
- **One exit point not required** — early returns improve readability

---

## 5. Error Handling

### Custom Errors

```ts
// errors/domain.ts
export class DomainError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly context?: Record<string, unknown>,
  ) {
    super(message);
    this.name = this.constructor.name;
  }
}

export class OrderNotFoundError extends DomainError {
  constructor(id: OrderId) {
    super(`Order ${id} not found`, "ORDER_NOT_FOUND", { orderId: id.value });
  }
}

export class InsufficientInventoryError extends DomainError {
  constructor(productId: ProductId, requested: number, available: number) {
    super(`Insufficient inventory for ${productId}`, "INSUFFICIENT_INVENTORY", {
      productId: productId.value,
      requested,
      available,
    });
  }
}
```

### Result Pattern

```ts
// types/result.ts
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

function ok<T>(value: T): Result<T, never> {
  return { ok: true, value };
}
function err<E>(error: E): Result<never, E> {
  return { ok: false, error };
}

// Usage
async function placeOrder(
  cmd: PlaceOrderCommand,
): Promise<Result<OrderId, DomainError>> {
  const customer = await customerRepo.findById(cmd.customerId);
  if (!customer) return err(new CustomerNotFoundError(cmd.customerId));

  const order = Order.place(customer, cmd.items);
  await orderRepo.save(order);
  return ok(order.id);
}
```

### Rules Error Handling

- **No throwing for control flow** — use Result pattern
- **Errors are values** — pass them, don't throw
- **Context in errors** — include relevant IDs, values
- **Typed errors** — discriminant union for exhaustive handling

---

## 6. Comments

### When to Comment

```ts
// ✅ Why (not what)
function calculateTax(amount: Money): Money {
  // Jurisdiction X requires rounding UP to nearest cent
  return amount.multiply(TAX_RATE).roundUp();
}

// ❌ What (code already says this)
function add(a: number, b: number): number {
  // Adds a and b
  return a + b;
}

// ✅ Warn about non-obvious behavior
function getUser(id: UserId): Promise<User> {
  // Cache-first: checks Redis before DB. Stale data possible for 5min.
  return cache.getOrFetch(`user:${id}`, () => db.findUser(id));
}
```

### Rules Comments

- **Code says what, comments say why**
- **No commented-out code** — delete it, git has history
- **No TODOs without issue link** — `// TODO(#123): refactor`
- **JSDoc for public APIs** — params, returns, throws

---

## 7. Testing (AAA Pattern)

```ts
// tests/order.test.ts
import { describe, it, expect } from "vitest";

describe("Order", () => {
  describe("place", () => {
    it("creates order with pending status", () => {
      // Arrange
      const customer = new Customer(new CustomerId("cust-1"), "John");
      const items = [new OrderItem(new ProductId("prod-1"), 2)];

      // Act
      const order = Order.place(customer.id, items);

      // Assert
      expect(order.status).toBe("pending");
      expect(order.items).toHaveLength(1);
      expect(order.customerId).toEqual(customer.id);
    });

    it("throws when items empty", () => {
      // Arrange
      const customer = new Customer(new CustomerId("cust-1"), "John");

      // Act & Assert
      expect(() => Order.place(customer.id, [])).toThrow(
        "Order must have items",
      );
    });
  });
});
```

### Rules Testing

- **AAA** — Arrange, Act, Assert
- **One assertion per test** — or related group
- **Descriptive names** — `creates_order_with_pending_status`
- **Test behavior, not implementation** — public API only
- **Fast, isolated, deterministic** — no DB, no network

---

## 8. Code Organization

### File Structure

```text
src/
├── domain/           # Pure business logic
│   ├── order/
│   │   ├── Order.ts
│   │   ├── OrderItem.ts
│   │   └── OrderRepository.ts (interface)
│   └── shared/
│       ├── Email.ts
│       └── Money.ts
├── application/      # Use cases / orchestration
│   └── orders/
│       ├── PlaceOrderCommand.ts
│       ├── PlaceOrderHandler.ts
│       └── GetOrderQuery.ts
├── infrastructure/   # External concerns
│   ├── repositories/
│   │   └── PrismaOrderRepository.ts
│   └── email/
│       └── SendGridEmailService.ts
└── presentation/     # HTTP, CLI, GraphQL
    └── orders/
        └── OrdersController.ts
```

### Rules Code Organization

- **Layer separation** — domain knows nothing of infrastructure
- **Dependency inversion** — domain defines interfaces, infra implements
- **Colocate related** — tests next to code, types next to usage

---

## 9. SOLID (Compact)

| Principle                 | Rule                                        |
| ------------------------- | ------------------------------------------- |
| **S**ingle Responsibility | One reason to change                        |
| **O**pen/Closed           | Open for extension, closed for modification |
| **L**iskov Substitution   | Subtypes substitutable for base             |
| **I**nterface Segregation | Many specific > one general                 |
| **D**ependency Inversion  | Depend on abstractions, not concretions     |

### Practical Application

```ts
// ❌ Violates DIP
class OrderService {
  private db = new Database(); // Concrete dependency
}

// ✅ Follows DIP
class OrderService {
  constructor(private repo: OrderRepository) {} // Interface
}
```

---

## 10. Methodology

Before using ANY clean code pattern not documented in
this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` +
   `context7_query-docs` for clean code, refactoring patterns.
2. **Official docs**: Clean Code (Martin), Refactoring (Fowler)
   — verify current patterns.
3. **Project config**: `tsconfig.json`, `eslint.config.js`,
   `package.json` — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against
   2 authoritative sources → DO NOT USE IT. Document as assumption or risk in
   report to orchestrator.

---

## 10. Prohibitions

- ❌ No `any` — use `unknown` + narrowing
- ❌ No magic numbers — use named constants
- ❌ No `console.log` in production code
- ❌ No `// TODO` without issue reference
- ❌ No dead code — delete, don't comment
- ❌ No `try/catch` for control flow — use Result
- ❌ No `any` in public APIs
- ❌ No commented-out code — git has history

---

## 11. References

> **Note:** For TypeScript rules, see [TypeScript](../typescript/SKILL.md)
> **Note:** For JavaScript conventions, see [JavaScript](../javascript/SKILL.md)
> **Note:** For Error handling, see [Security](../security/SKILL.md)
> **Note:** For Testing patterns, see [Testing](../testing/SKILL.md)

---

Last updated: 2026-08

