A function on class A that reads only data on class B — A.method() reaches through getters on B to compute something B should compute.
Each function lives where its data lives; coupling between modules drops.
Before the refactoring
class Order {account;isVip() {return this.account.tier === 'gold' && this.account.yearsActive >= 3;}}
After the refactoring
class Account {tier; yearsActive;isVip() { return this.tier === 'gold' && this.yearsActive >= 3; }}class Order { account; }order.account.isVip();
B's internals leak through public surfaces just to support A's method; refactoring B becomes a coordination across A's call sites.
Dependencies don't always travel cleanly — circular imports surface at the destination, and readers' 'where does this live' map briefly breaks.
Modules become more cohesive; tests stay focused; feature-envy patterns disappear.
Moving every cross-class read mechanically — including functions that legitimately compose data across multiple classes — fan-outs the move and breaks coherent abstractions.