Files
skills/clean-code/references/clean-code.md
Mathias d6a71e370e
Some checks failed
release / tag (push) Has been cancelled
chore: bootstrap skills library — 19 skills + installer + CI auto-tag
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]
2026-05-24 14:59:54 +02:00

7.5 KiB

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.

// 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.

// 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

// 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.

// 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.

// 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.

// BAD
const genymdhms = generateYearMonthDayHourMinuteSecond();

// GOOD
const timestamp = generateTimestamp();

7. Austerity

Avoid unnecessary filler words.

// 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

// 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.

// 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.

// 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.

// 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.

// 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.

// 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.

// 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.

// 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.

// 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.

// 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.

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 { ... }
}