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.
answer
- Dots ≠ coupling
- Builder/stream chains: same object or plain value
- order.getCustomer().charge() = 2 dots, real violation
- Test: does the line know foreign structure?
- Better rule: don't call methods on a getter's result
basics
~10 sDots 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 sThe "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// 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
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.
Give one concrete false positive (builder or stream) and one concrete false negative (order.getCustomer().charge(...)), and state the structural-knowledge test.
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.
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.