07

Local reasoning / inline-first

#
  • heuristic
  • opinionated
  • fragment

Scope. “An interface only once there are two implementations” is mechanical. A single implementation is justified when there is a genuine consumer-owned port, plugin boundary, security capability or stable external API. The criterion is a proven axis of change, not a class count.

Conflicts with: Local reasoning ↔ dataflow, Feature flags ↔ local reasoning

The metric: how many files you must open to understand a snippet. An abstraction appears on the third repetition, not the first. An interface with a single implementation costs navigation and buys no flexibility.

Code example (fragment)
fragment
Go
// The Go community says it outright: "accept interfaces, return structs" —
// the consumer declares the interface, and only once implementations >= 2

// ANTIPATTERN: an interface with a single implementation — two places to read
type UserService interface {
    QueryUser(ctx context.Context, id UserID) (User, error)
}
type userServiceImpl struct{ db *sql.DB }

// NORM: a direct call, one place to read
func (h *Handler) route(ctx context.Context, id UserID) (User, error) {
    return h.db.QueryUser(ctx, id)
}
Java
// ANTIPATTERN: an interface with exactly one implementation and one caller —
// +1 file, +1 jump while reading, zero substitutability in practice
public interface UserService { User find(UserId id); }
public final class UserServiceImpl implements UserService { ... }

// NORM: call the class directly. The interface arrives with the
// second implementation — or at the I/O boundary, where a test needs it (5)
public final class UserService { public User find(UserId id) { ... } }
Rust
// ANTIPATTERN: dyn for a single implementation — an indirect call
// at runtime and a jump across files while reading
struct Handler { store: Box<dyn Store> }

// NORM: a concrete type while there is one implementation. When a second
// appears, a generic: monomorphization gives flexibility at no runtime cost
struct Handler { store: PostgresStore }
Python
# ANTIPATTERN: an ABC hierarchy for a single subclass
class Store(ABC):
    @abstractmethod
    def get(self, key: str) -> bytes | None: ...

class PostgresStore(Store): ...     # the only implementation

# NORM: the class directly. When there are two, a Protocol: it needs no
# inheritance and is declared by the consumer, not the implementation (12)
class PostgresStore:
    def get(self, key: str) -> bytes | None: ...

Last technical review: 2026-08-04 · Reference · 112 principles