skip to content

Why is "count the dots on the line" a bad test for Law of Demeter violations? Give examples of long chains that are fine and short chains that are not.

level: middleimportance: should knowfreq 38%

answer

  1. Dots ≠ coupling
  2. Builder/stream chains: same object or plain value
  3. order.getCustomer().charge() = 2 dots, real violation
  4. Test: does the line know foreign structure?
  5. Better rule: don't call methods on a getter's result

basics

~10 s

Dots don't measure coupling. list.filter(...).map(...).count() has many dots but no new dependencies, while a.getB().doIt() has few dots yet reaches into a stranger. What matters is whether you're walking another object's structure.

solid answer

~50 s

The "one dot" heuristic is a proxy that misfires in both directions. **Long chains that are fine:** - *Fluent builders / self-returning APIs* — `builder.name(x).age(y).build()`: every call returns the same object, so no stranger is introduced. - *Collection and stream pipelines* — `orders.filter(paid).map(total).sum()`: each step returns a new value of a type you already depend on, not a deeper node of someone else's graph. - *Chains on a single value object* — `date.plusDays(1).atStartOfDay()`. **Short chains that are violations:** - `order.getCustomer().charge(amount)` — two dots, but the caller now knows Order holds a Customer and knows Customer's API. - Even one dot can violate the *class* form when the returned type is a stranger you should never have named. The real test: **does this line depend on how another object is internally composed?** If yes, it's a violation regardless of dot count. Also useful: are you *asking for data to act on it yourself* (violation) rather than *telling an object to act* (fine)?

code

pseudocode · 11 lines
pseudocode
// MANY dots, NOT a violation - same builder object throughout
req = HttpRequest.builder().url(u).header("A","B").timeout(5).build()

// MANY dots, NOT a violation - each step returns a plain value
total = orders.filter(isPaid).map(amount).sum()

// TWO dots, IS a violation - reaches into Order's internal graph
order.getCustomer().charge(total)

// Fix: tell, don't ask
order.chargeCustomer(total)

go deeper

for a junior

It's enough to say dots aren't the point — chaining on the same object (builders, lists) is fine; reaching through a getter into another object is not.

for a middle

Give one concrete false positive (builder or stream) and one concrete false negative (order.getCustomer().charge(...)), and state the structural-knowledge test.

for a senior

Offer replacement heuristics — mock-of-mock, rename test, ask-vs-tell — and explain why dot-counting lint rules actively harm codebases by creating noise while missing real violations.

for a principal

Frame it as the general failure mode of syntactic proxies for semantic properties; connect to what static analysis can actually verify (class-level form) and where suppressions become necessary.

## Why the dot rule exists — and why it's wrong The Law of Demeter's actual statement is about *which objects you send messages to*. That's hard to eyeball, so someone invented a syntactic shortcut: **"one dot per line."** It caught on because it's checkable in a code review in one second. It is also wrong roughly half the time. ### Direction 1 — false positives: long chains that are perfectly fine **a) Fluent interfaces / method cascading.** ``` HttpRequest.builder().url(u).header("A","B").timeout(5).build() ``` Each call returns **the same builder object** (or a new immutable builder of the same type). You never obtain a stranger; you keep talking to the same friend. The dots are punctuation, not graph edges. **b) Collection / stream / query pipelines.** ``` orders.filter { it.isPaid }.map { it.total }.sum() ``` Each stage returns a *value* — a new collection or number — not the internals of a foreign object. You already depend on `Order`; the pipeline introduces no new knowledge of who holds whom. **c) Value-object chains.** ``` date.plusMonths(1).withDayOfMonth(1) ``` Immutable value objects returning new instances of themselves. Same argument. **d) Deliberate data structures.** Parsed JSON, configuration trees, ASTs, DTOs at a boundary. These exist *to be navigated*; there is no encapsulation to violate, because they promise no behaviour. `config.get("db").get("host")` is not a design failure. ### Direction 2 — false negatives: short chains that violate it ``` order.getCustomer().charge(total) ``` Two dots, one stranger, and now the caller knows (i) an Order holds a Customer and (ii) how to charge a Customer. Change either fact and this breaks. ``` request.getSession().setAttribute(k, v) ``` A classic in web code. Small-looking, but couples every handler to the session model. Even a *single* dot can violate the class-level form when it forces you to name a type you have no business naming. ### The tests that actually work 1. **Structural-knowledge test.** "Does this line assume anything about how another object is *composed*?" If yes → violation. 2. **Same-object test.** "Does each call in the chain return the same object, a new value of a type I already use, or a *deeper node of someone else's graph*?" Only the third is a violation. 3. **Ask-vs-tell test.** Am I pulling data out to act on it myself, or telling an object to act? Pulling is the smell; the chain is just how the pull looks. 4. **Mock test.** "To unit-test this, would I need a mock that returns a mock?" If yes → violation. 5. **Rename test.** "If a type three hops away gets renamed or restructured, does this file change?" If yes → violation. ### Why this matters practically Teams that adopt "one dot" as a lint rule end up doing two bad things at once: they wrap harmless pipelines in pointless locals (`val paid = orders.filter(...); val totals = paid.map(...)`), and they miss real two-dot violations because those "only have two dots". The rule creates busywork and false confidence. If you want a mechanical rule, a better one is: **"never call a method on something a getter gave you."** That's much closer to the actual law, and it doesn't fire on builders, pipelines, or value objects.

  • If dot-counting is unreliable, can static analysis check the Law of Demeter at all?
    Yes, but only the class-level form: a tool can verify that every type named inside a method is the class itself, a supertype, a field type, a parameter type, or an instantiated type. That is checkable and meaningful, unlike dot counts — though it still flags legitimate factory and collection usage, so it needs suppressions.
  • Why don't fluent builders violate the Law of Demeter despite chaining heavily?
    Because each call returns the same object (or an equivalent instance of the same type), so you never acquire a stranger. You are repeatedly messaging one friend, and no knowledge of another object's internal composition is encoded.

Counting dots to find coupling is like judging a sentence's complexity by counting commas. A long list of adjectives has many commas and says one simple thing; a short sentence with no commas can hide three nested assumptions.

context