Software Design Reference Core Designer · Seeing Structural Problems

Lesson 0003 · Core Designer · Module 1

The Law of Demeter

A method should only talk to its immediate neighbors. When code walks through strangers, it learns their geography — and every geography change breaks it.

Mission tie-in: chain-walking is one of the most common structural defects in AI-generated code. Master the tell, and you can spot it in any output in seconds.

Knowledge: no talking to strangers

The Law of Demeter limits a method's acquaintances to: itself, its own fields, its arguments, objects it creates, and the direct members of its own collections. Everything else is a stranger.

This is the failure it prevents — a "train wreck":

def is_free_shipping(order: Order) -> bool:
    city = order.customer.address.city
    return city in FREE_SHIPPING_CITIES

is_free_shipping reaches through order into customer, then address, then city. The caller now knows the internal geography of three objects it doesn't own. Change customer.address to customer.delivery_address and this breaks even though nothing about shipping changed.

The fix is tell, don't ask: move the behavior to the object that owns the data.

def is_free_shipping(order: Order) -> bool:
    return order.has_free_shipping_to(FREE_SHIPPING_CITIES)

# inside Order:
def has_free_shipping_to(self, cities: set[str]) -> bool:
    return self.customer.delivery_city() in cities

The caller talks to one neighbor (order). The chain still exists — but it lives inside the object that owns the data, where geography changes are contained. The Law of Demeter is not "no chains ever"; it is "nobody walks through strangers."

The same idea, at object level: a mediator object that only forwards calls is a violation with extra steps — moving the chain doesn't fix it, asking the owner does.
Field notes · what it looks like in real code
You see thisWhat it costsThe move
a.b.c.dThe caller depends on the shape of three objects it does not own.Ask the owner one question: a.answer().
get_x().get_y().get_z()The same wreck in getter clothing — chained accessors still walk strangers.Move the question into the first object.
a mock returning a mock returning a mockThe mock chain mirrors the call chain: proof of a train wreck.Flatten the interface first, then rewrite the test.
self.customer.address inside OrderLegal: customer is Order’s own field, a direct neighbour.Leave it — the law limits callers, not owners.

Skill: spot the stranger

Which snippet walks through a stranger?

Why does a train wreck hurt maintainability?

The best fix for order.customer.address.city is to:

Practice on your own code

Search a file you own (or an AI-generated one) for chains: patterns like a.b.c.d or repeated get_x().get_y(). Pick one, find the object that owns the final value, and move the question to it.

Reveal: a worked example
# before
if booking.flight.airline.contact_email:
    notify(booking.flight.airline.contact_email)

# after — the airline answers; Booking has no opinion on airline internals
if booking.flight.has_contact_email():
    notify(booking.flight.contact_email())

Your win

You can spot a train wreck at a glance and know the one fix that works: hand the question to the owner of the data. Add this to your code-review checklist and your AI-review checklist.

Read and watch deeper

Unsure whether a chain is a violation? Ask your agent-teacher to judge a snippet with you — the boundary cases are where the law becomes judgment.