Null-check branches the agent must trace at every collaborator-using call site. The agent cannot statically verify all check sites stay consistent; new callers risk omitting the check and shipping NullPointerExceptions.
One Null Object class the agent verifies once; all call sites unconditionally invoke the collaborator interface. The agent's edit budget on a collaborator-using method drops because there's no per-call branch to reason about.
Before the refactoring
// Every consumer of Customer.discount and Customer.loyaltyProgram must check for null first.class Order {constructor(customer) {this.customer = customer;}finalPrice() {let price = this.subtotal();if (this.customer.discount !== null) {price = this.customer.discount.applyTo(price);}if (this.customer.loyaltyProgram !== null) {price -= this.customer.loyaltyProgram.pointsValue();}return price;}subtotal() { /* ... */ }}
After the refactoring
// Null Objects implement the same interface with safe defaults; callers stop branching.class NoDiscount {applyTo(price) {return price;}}class NoLoyaltyProgram {pointsValue() {return 0;}}class Order {constructor(customer) {// customer.discount and customer.loyaltyProgram are never null;// they are NoDiscount/NoLoyaltyProgram when absent.this.customer = customer;}finalPrice() {let price = this.subtotal();price = this.customer.discount.applyTo(price);price -= this.customer.loyaltyProgram.pointsValue();return price;}subtotal() { /* ... */ }}
N call sites × M methods × the null check = the agent's per-edit verification cost. Static analysis catches some missing checks but not all (especially after refactors that change which fields can be null).
A Null Object collapses two distinct runtime states (absent / present) into one type at the call site. The agent loses the ability to statically distinguish a real collaborator from its null stand-in; debugging requires reading the constructor / factory to know which is which.
Branch count at call sites drops to zero; the agent reads the Null Object's body once to know what 'absent' does, and trusts the type system thereafter. Adding a new caller is one method call, not one method-call-plus-branch.
A Null Object that quietly returns the wrong neutral element (zero where one was needed, identity where blocking was needed) produces silent failures the agent cannot detect from static reading. The pattern requires careful semantic alignment between 'absent' and the chosen default.