# Message Chains

> Client code walks a chain like a.getB().getC().getD() to reach what it needs, coupling itself to every link in the path.

- kind: smell
- category: couplers
- aliases: Train Wreck, Law of Demeter violation
- canonical: https://aipatterns.dev/refactoring/smells/message-chains
- inspired-by: https://refactoring.guru/smells/message-chains

## Signs

- Expressions such as `order.customer.account.billingAddress.country` appear in client code.
- The same navigation path is repeated in several files.
- Null checks guard each step: `a?.b?.c?.d`.
- A change to how `Account` stores addresses breaks code that only wanted a country code.
- Tests must build a deep object graph to exercise one line.

## Why it happens

- The object model mirrors the database schema, and callers navigate it the way they would join tables.
- Getters exist for every field, so following them is the path of least resistance.
- Nobody asked the first object for what the client actually needed, so no method for it exists.

## Example

A tax calculator needs the buyer's country and digs for it.

```ts title="before.ts"
class Address {
  constructor(readonly country: string) {}
}
class Account {
  constructor(readonly billingAddress: Address) {}
}
class Customer {
  constructor(readonly account: Account) {}
}
class Order {
  constructor(readonly customer: Customer, readonly subtotal: number) {}
}

function vatFor(order: Order): number {
  const country = order.customer.account.billingAddress.country;
  return country === "DE" ? order.subtotal * 0.19 : 0;
}

function shippingZone(order: Order): string {
  return order.customer.account.billingAddress.country === "US" ? "domestic" : "intl";
}
```

Both functions know the full path from `Order` to `Address`.

```ts title="after.ts"
class Customer {
  constructor(private readonly account: Account) {}
  billingCountry(): string {
    return this.account.billingAddress.country;
  }
}
class Order {
  constructor(private readonly customer: Customer, readonly subtotal: number) {}
  billingCountry(): string {
    return this.customer.billingCountry();
  }
}

function vatFor(order: Order): number {
  return order.billingCountry() === "DE" ? order.subtotal * 0.19 : 0;
}

function shippingZone(order: Order): string {
  return order.billingCountry() === "US" ? "domestic" : "intl";
}
```

Clients now depend on `Order` alone. Only `Customer` knows about `Account`.

## Treatment

- If clients repeat a chain to reach one value, use [Hide Delegate](https://aipatterns.dev/refactoring/techniques/hide-delegate.md) on the first link so it answers directly.
- If the client does work with the value at the end of the chain, use [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md) on that work, then [Move Method](https://aipatterns.dev/refactoring/techniques/move-method.md) it to an object near the start of the chain.
- If you find yourself hiding delegates on every class, stop. That produces [Middle Man](https://aipatterns.dev/refactoring/smells/middle-man.md). Hide only the paths clients actually use.

## Payoff

- Clients depend on one neighbor instead of the whole object graph.
- Restructuring intermediate objects stops breaking distant code.
- Tests need shallower fixtures.

## When to ignore

- Fluent and builder APIs return `this` or a value of the same kind: `query.where(...).orderBy(...).limit(10)`. That is one object, not a chain of strangers.
- Pipelines over plain values, such as `items.filter(...).map(...)`, are transformations, not navigation.
- Immutable records and read models you only read, such as parsed JSON or configuration, where the shape is the contract. Wrapping every path adds methods without reducing coupling.
- Hiding every link would turn each class into a forwarding layer. Prefer the occasional chain.

## Agent checklist

- [ ] Confirm the chain crosses several distinct types, not a fluent API on one type.
- [ ] Count how many call sites use the same path before adding a delegating method.
- [ ] Name the new method for what the client wants (`billingCountry`), not for the path.
- [ ] Reduce visibility of the intermediate getters once no client uses them.
- [ ] Avoid creating a [Middle Man](https://aipatterns.dev/refactoring/smells/middle-man.md) with many pure forwarders.
