home / skills / yanko-belov / code-craft / law-of-demeter

law-of-demeter skill

/skills/law-of-demeter

This skill helps you write safer TypeScript code by avoiding deep property access and encouraging explicit data access patterns.

npx playbooks add skill yanko-belov/code-craft --skill law-of-demeter

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.5 KB
---
name: law-of-demeter
description: Use when accessing nested object properties. Use when chaining method calls. Use when reaching through objects to get data.
---

# Law of Demeter (Don't Talk to Strangers)

## Overview

**Only talk to your immediate friends, not strangers.**

A method should only call methods on: itself, its parameters, objects it creates, or its direct components. Never reach through an object to access another object's internals.

## When to Use

- Accessing nested properties: `obj.a.b.c`
- Chaining method calls: `obj.getA().getB().getC()`
- Reaching through objects for data
- Long dot chains in your code

## The Iron Rule

```
NEVER chain through objects. Ask, don't reach.
```

**No exceptions:**
- Not for "it's simpler"
- Not for "it's just one chain"
- Not for "the data is there"
- Not for "fewer lines of code"

## Detection: The Chain Smell

If you see multiple dots, you're violating LoD:

```typescript
// ❌ VIOLATION: Reaching through objects
function getEmployeeCity(company: Company, employeeId: string): string {
  return company.employees
    .find(e => e.id === employeeId)
    ?.address.city;  // Reaching into employee, then into address
}

// More violations:
user.getProfile().getAddress().getZipCode();
order.getCustomer().getPaymentMethod().getLast4();
```

## The Correct Pattern: Ask, Don't Reach

Let objects expose what's needed:

```typescript
// ✅ CORRECT: Ask the object directly
class Employee {
  constructor(
    private name: string,
    private address: Address
  ) {}
  
  getCity(): string {
    return this.address.city;  // Employee asks its own address
  }
}

class Company {
  getEmployeeCities(): Map<string, string> {
    return new Map(
      this.employees.map(e => [e.id, e.getCity()])
    );
  }
  
  getEmployeeCity(employeeId: string): string | undefined {
    return this.employees.find(e => e.id === employeeId)?.getCity();
  }
}

// Usage: Ask company, don't reach through it
const city = company.getEmployeeCity(employeeId);
```

## Why Chains Are Bad

| Problem | Impact |
|---------|--------|
| **Tight coupling** | Caller knows internal structure |
| **Fragile code** | Structure changes break all callers |
| **Hidden dependencies** | Not obvious what's needed |
| **Hard to test** | Must mock entire chain |
| **Null danger** | Each `.` is a potential null |

## Allowed Method Calls

A method `m` of class `C` should only call methods on:

1. **`this`** - C's own methods
2. **Parameters** - Objects passed to `m`
3. **Created objects** - Objects `m` creates
4. **Components** - C's direct instance variables
5. **Globals** - Accessible global objects (sparingly)

```typescript
class OrderProcessor {
  constructor(private logger: Logger) {}  // Component

  process(order: Order): Receipt {         // Parameter
    this.validate(order);                  // this
    const receipt = new Receipt(order);    // Created
    this.logger.log('Processed');          // Component
    return receipt;
  }

  // ❌ NOT ALLOWED: order.customer.address.city
  // ✅ ALLOWED: order.getShippingCity()
}
```

## Pressure Resistance Protocol

### 1. "It's Simpler"
**Pressure:** "One line with dots is simpler than adding methods"

**Response:** Simple to write ≠ simple to maintain. Chains create fragile code.

**Action:** Add methods that expose needed data.

### 2. "It's Just One Chain"
**Pressure:** "It's only two dots, not a big deal"

**Response:** Two dots = two objects you're coupled to. Both can change and break you.

**Action:** Even short chains should be eliminated.

### 3. "The Data Is Right There"
**Pressure:** "The structure has the data, why wrap it?"

**Response:** Structure changes. Wrapping isolates you from changes.

**Action:** Ask the owner for the data.

### 4. "It's Read-Only"
**Pressure:** "I'm just reading, not modifying"

**Response:** Reading through chains still couples you to structure.

**Action:** Ask for what you need.

## Red Flags - STOP and Reconsider

If you notice ANY of these, refactor:

- Multiple dots: `a.b.c.d`
- Chained getters: `getA().getB().getC()`
- Optional chains: `a?.b?.c?.d`
- Null checks for nested access
- Structure knowledge in calling code
- Mocking chains in tests

**All of these mean: Add a method to ask directly.**

## Refactoring Chains

```typescript
// ❌ BEFORE: Chain
const zip = user.getProfile().getAddress().getZipCode();

// ✅ AFTER: Ask
// In User class:
getZipCode(): string {
  return this.profile.getZipCode();
}

// In Profile class:
getZipCode(): string {
  return this.address.zipCode;
}

// Usage:
const zip = user.getZipCode();
```

## Quick Reference

| Chain (Bad) | Ask (Good) |
|-------------|------------|
| `company.employees[0].address.city` | `company.getEmployeeCity(id)` |
| `order.customer.paymentMethod.last4` | `order.getPaymentLast4()` |
| `user.profile.settings.theme` | `user.getTheme()` |
| `car.engine.fuel.level` | `car.getFuelLevel()` |

## Common Rationalizations (All Invalid)

| Excuse | Reality |
|--------|---------|
| "It's simpler" | Chains are simpler to write, harder to maintain. |
| "Just one chain" | One chain = multiple couplings. |
| "Data is right there" | Expose it properly through methods. |
| "It's read-only" | Reading chains still couples you. |
| "Fewer lines" | Lines don't matter. Maintainability does. |
| "It's obvious what it does" | Obvious coupling is still coupling. |

## The Bottom Line

**Ask objects for what you need. Don't reach through them.**

When you need data from nested objects: add a method on the owner that returns it. Never chain through multiple objects. Each dot is a dependency you're taking on.

Overview

This skill enforces the Law of Demeter (don’t talk to strangers) for TypeScript code that accesses nested object properties or chains method calls. It helps detect and prevent chained property or method access that creates tight coupling, fragile tests, and null-safety risks. Use it to encourage objects to expose needed data via their own methods rather than letting callers reach through internals.

How this skill works

The skill inspects code for long dot-chains, chained getters, optional-chaining over multiple levels, and patterns that reach through objects to get data (e.g., obj.a.b.c or obj.getA().getB()). It flags violations and suggests refactor alternatives: add explicit accessor methods on the owning object, ask an object for the data instead of navigating its internals, or return higher-level values from public APIs. It highlights null-safety and testability issues caused by chains.

When to use it

  • When you see multiple dots in an expression (a.b.c or a?.b?.c).
  • When code chains method calls like obj.getA().getB().getC().
  • When callers access another object’s internals instead of asking it.
  • During code reviews to prevent hidden dependencies and fragile callers.
  • When unit tests must mock long object chains to validate behavior.

Best practices

  • Prefer asking the owning object for data via a clear method (e.g., order.getPaymentLast4()).
  • Limit method m in class C to call only this, its parameters, objects it creates, direct components, or globals.
  • Refactor short chains immediately — two dots still indicate coupling.
  • Expose small, intent-revealing accessors instead of exposing structure.
  • Avoid optional chains that hide structural coupling; return undefined explicitly from owner methods when appropriate.

Example use cases

  • Replace company.employees.find(...).address.city with company.getEmployeeCity(id).
  • Convert user.getProfile().getAddress().getZipCode() into user.getZipCode().
  • Refactor order.customer.paymentMethod.last4 into order.getPaymentLast4().
  • Add methods on domain objects so callers don’t need to know nested layouts.
  • During refactor sprints, remove chained accesses to improve testability and reduce breakage.

FAQ

Is one short chain really a problem?

Yes. Even short chains create coupling to multiple objects and can break when internal structures change.

Won’t adding many getters bloat classes?

Add only intent-focused accessors that reflect use cases. They isolate callers from structure and reduce long-term maintenance cost.