Skip to content

Archive

Maintainability

114 articles
Software Engineering 11 Sep 2026 8 min read

Using a Facade to Reduce Dependency Surface

Using a Facade to Reduce Dependency Surface A feature starts with one call into a library. A few months later, every caller knows which three components to create, which methods must run first, which defaults belong together, and which low-level errors need translation. The subsystem still works, but its internal structure has leaked into the rest of the codebase. A facade is a deliberately simpler interface placed in front of a more complicated subsystem. Callers depend on the operations they actually need instead of coordinating the subsystem themselves. This article explains how to recognize that design problem, build a focused facade, and decide when the extra layer is useful rather than ceremonial.

Software Engineering 11 Sep 2026 9 min read

Stable Dependencies Principle: Point Toward Stability

Stable Dependencies Principle: Point Toward Stability A dependency graph is not only a map of which component calls which other component. Its direction also determines which parts of a system can change independently. Consider a reporting component used by ten other components. Many callers depend on it, so changing its public contract can require coordinated work across the codebase. That reporting component has become relatively stable: not necessarily because its code rarely changes, but because many other components constrain how freely its contract can change.

Software Engineering 11 Sep 2026 8 min read

Shotgun Surgery: When One Change Touches Many Files

Shotgun Surgery: When One Change Touches Many Files A pricing rule changes from “free shipping above 50” to “free shipping above 75.” The rule is simple, but the pull request touches checkout, order previews, invoice generation, customer notifications, and tests in several modules. Miss one place and customers see contradictory behavior. That pattern is called shotgun surgery: one logical change requires many small edits scattered through the codebase. The problem isn’t the number of files by itself. The problem is that one decision has many owners.

Software Engineering 11 Sep 2026 10 min read

Shotgun Surgery: Reduce Scattered Change

Shotgun Surgery: Reduce Scattered Change A small requirement can produce a surprisingly large patch. Adding one order state means editing validation, formatting, notification, audit, and reporting code in different modules. Each edit may be simple, yet missing one can leave the system inconsistent. This recurring shape is often called shotgun surgery: one conceptual change forces small edits across many places. The practical problem isn’t the number of files by itself. It is that a single responsibility has been scattered, so developers must reconstruct the full change surface each time that responsibility evolves.

Software Engineering 11 Sep 2026 8 min read

Replacing Inheritance with Delegation

Replacing Inheritance with Delegation A class needs three useful methods from another class, so extending that class seems convenient. Months later, the subclass also inherits methods it shouldn’t expose, depends on initialization details it doesn’t control, and breaks when the superclass changes an internal assumption. The problem isn’t inheritance itself. The problem is using an is-a relationship to obtain code reuse when the real relationship is uses-a. Replacing inheritance with delegation makes that relationship explicit: the object keeps a collaborator and forwards only the behavior it actually needs.

Software Engineering 11 Sep 2026 8 min read

Replace Nested Conditionals with Guard Clauses

Replace Nested Conditionals with Guard Clauses A method often starts simple and becomes deeply nested one condition at a time. A null check wraps a permission check. That wraps a state check. The actual operation ends up several indentation levels from the method boundary, even though it is the main path a reader cares about. Guard clauses handle exceptional or disqualifying conditions near the top of a method and exit immediately. The remaining code can then describe the normal path with less structural noise.

Software Engineering 11 Sep 2026 8 min read

Replace Magic Numbers with Named Domain Values

Replace Magic Numbers with Named Domain Values A condition such as attempts >= 5 is easy to execute and surprisingly hard to review. Why five? Is it a security policy, a technical limit, a temporary experiment, or just an arbitrary value copied from somewhere else? The code contains the number but not the reason it exists. A magic number is a numeric literal whose meaning isn’t clear from its context. Replacing one well means more than moving it into a constant. The goal is to preserve the value’s meaning, units, and ownership so a future change has an obvious place to happen.

Software Engineering 11 Sep 2026 8 min read

Humble Object Pattern for Hard-to-Test Boundaries

Humble Object Pattern for Hard-to-Test Boundaries Some code is difficult to test for reasons that have little to do with its business rules. A screen handler may depend on a UI framework. A file watcher may need operating-system events. A scheduled job may be invoked by infrastructure that is awkward to reproduce in a unit test. A common mistake is to put more logic inside that difficult boundary. Tests then need the framework, filesystem, clock, process, or device just to check an ordinary decision.

Software Engineering 11 Sep 2026 8 min read

Common Closure Principle: Group Code That Changes Together

Common Closure Principle: Group Code That Changes Together A codebase can have tidy classes and still make routine changes expensive. A new pricing rule might require edits in a controller module, a generic utilities package, a shared validation package, and a reporting package. Each edit is small, but one business decision now crosses several boundaries. The Common Closure Principle offers a practical way to judge those boundaries: code that tends to change for the same reason should tend to live in the same component. A component can be a package, module, library, or another unit that a team changes and releases together.

Software Engineering 10 Sep 2026 11 min read

Tolerant Reader Pattern for Evolving Contracts

Tolerant Reader Pattern for Evolving Contracts A producer adds an optional field to a response. No existing meaning changed, yet an older consumer starts rejecting every message because its parser expected exactly five fields. The producer made a seemingly compatible change, but the consumer had quietly coupled itself to details it never used. The Tolerant Reader pattern addresses that problem from the consumer side. A tolerant reader describes and validates the information it actually depends on while allowing unrelated parts of an incoming representation to vary. Used carefully, this makes contracts easier to evolve without turning validation into guesswork.

Software Engineering 10 Sep 2026 8 min read

Tell, Don’t Ask: Keep Decisions with the Data

Tell, Don’t Ask: Keep Decisions with the Data A checkout service reads an order’s status, total, and payment state, decides whether cancellation is allowed, and then changes the order. Later, a support tool needs the same operation and copies most of that decision logic. The two callers eventually disagree about one rule. Tell, Don’t Ask is a software design principle that helps prevent this kind of drift. Instead of asking an object for internal state so another object can make a decision about it, prefer telling the object what outcome you want and letting the object enforce the rules that belong to its state.

Software Engineering 10 Sep 2026 9 min read

Split Phase Refactoring to Separate Computation Stages

Split Phase Refactoring to Separate Computation Stages A function starts by interpreting input, then gradually accumulates validation, business rules, formatting, and output logic. None of those steps is necessarily complicated. The difficulty comes from having them interleaved: changing how input is interpreted can unexpectedly affect code that should only care about the interpreted result. Split Phase is a refactoring that separates one computation into distinct stages. The first phase produces an explicit intermediate result. The next phase consumes that result without needing to know how it was produced.

Software Engineering 10 Sep 2026 10 min read

Specification Pattern for Composable Business Rules

Specification Pattern for Composable Business Rules Business rules often begin as a few readable conditions. Then the same decisions appear in validation, eligibility checks, filtering, and workflow code. Small differences creep in: one path checks account status but forgets the credit limit; another copies the whole expression and changes only one threshold. The Specification pattern gives a business rule a name and an interface, then allows rules to be combined into larger decisions. It is useful when the same rule matters in several places or when complex policy is easier to understand as a composition of smaller concepts.

Software Engineering 10 Sep 2026 10 min read

Replace Primitive Obsession with Domain Types

Replace Primitive Obsession with Domain Types A customer ID, an email address, and a currency code can all be represented as strings. That doesn’t make them interchangeable. When a codebase treats every meaningful value as a generic string, integer, or boolean, callers have to remember rules that the type itself doesn’t express. This problem is often called primitive obsession: using general-purpose primitive values where the domain has a more specific concept. The practical fix isn’t to wrap every string in a class. It’s to introduce a domain type when doing so gives the program a useful place to enforce meaning and rules.

Software Engineering 10 Sep 2026 7 min read

Reducing Change Amplification in Software Design

A small requirement arrives: add a new delivery status called delayed. The rule itself is simple, but implementing it means editing validation, display labels, notification logic, reporting code, and several tests in unrelated directories. Nothing is individually difficult. The risk comes from having to remember every place that represents the same idea. This is change amplification: one conceptual change requires many coordinated code changes. Some amplification is unavoidable, but repeated scattering is a useful design signal. Learning to recognize it helps you decide where a refactoring can make future changes smaller and less error-prone.

Software Engineering 10 Sep 2026 9 min read

Information Hiding: Designing Modules Around Change

Information Hiding: Designing Modules Around Change A module can have a small API and still be difficult to change. The problem appears when callers know details they shouldn’t need to know: which storage format is used, how identifiers are assembled, which retry sequence is required, or what intermediate states exist inside a workflow. Once that knowledge escapes, an internal change becomes a coordinated change across the codebase. Information hiding is a design principle for preventing that spread. A module hides a design decision by giving other code a stable way to use the capability without depending on the decision itself. This article develops a practical way to recognize leaked decisions, choose useful module boundaries, and avoid interfaces that merely disguise the implementation.

Software Engineering 10 Sep 2026 9 min read

Information Hiding: Design Modules Around Change

Information Hiding: Design Modules Around Change A module can have private fields and still expose nearly every design decision it makes. If callers know which storage keys exist, how records are ordered, which retry sequence is used, or how an identifier is encoded, changing those decisions means changing the callers too. Information hiding is the design practice of keeping such decisions behind a boundary. The goal isn’t secrecy. The goal is to make a decision replaceable without forcing unrelated code to understand or change with it.

Software Engineering 10 Sep 2026 8 min read

Functional Core, Imperative Shell for Testable Code

Functional Core, Imperative Shell for Testable Code A function that calculates a decision, reads the clock, queries a database, sends a message, and writes a log can be difficult to test for a simple reason: its business rules and its interactions with the outside world are tangled together. Testing one rule may require arranging several dependencies that have nothing to do with that rule. Functional core, imperative shell is a design approach for separating those concerns. The functional core contains deterministic decision-making: given the same explicit inputs, it produces the same result without performing external side effects. The imperative shell handles effects such as reading data, obtaining the current time, calling services, and persisting results.

Software Engineering 10 Sep 2026 9 min read

Functional Core, Imperative Shell for Managing Side Effects

Functional Core, Imperative Shell for Managing Side Effects Business logic is often easy to describe and hard to test because it sits between database reads, network calls, clocks, queues, and file writes. A pricing rule that should be a few comparisons can become tangled with fetching a customer, saving an order, and sending a notification. Functional core, imperative shell is a design approach for separating those concerns. The functional core makes decisions from explicit input values and returns values describing the result. The imperative shell obtains those inputs and performs the required side effects.

Software Engineering 10 Sep 2026 8 min read

Composed Method: Keep Code at One Level of Abstraction

Composed Method: Keep Code at One Level of Abstraction A method can be only thirty lines long and still be difficult to read. The problem often isn’t its length. It is that the method keeps changing altitude: one line describes a business step, the next manipulates a collection, then another formats a storage key, and then the code returns to business logic. The Composed Method pattern addresses that problem by making a method read as a sequence of operations at roughly the same level of abstraction. The top-level method explains what happens. Smaller methods hold the details of how each step happens.

Software Engineering 10 Sep 2026 8 min read

Command-Query Separation: Make Side Effects Visible

Command-Query Separation: Make Side Effects Visible A method named getNextInvoiceNumber() looks like a read. If calling it also increments the stored number, logging it twice can change application behavior. A debugger expression can consume a value. A harmless-looking retry can advance state again. The problem isn’t mutation by itself. Software has to change state. The problem is making a caller guess whether asking for information also changes something. Command-query separation is a design principle that reduces this ambiguity. A query returns information without changing observable state. A command changes state and doesn’t need to return domain information about that change. This article shows how that distinction makes APIs easier to reason about, where the rule is useful, and where forcing it creates more complexity than it removes.

Software Engineering 09 Sep 2026 9 min read

Using Factories to Contain Construction Policy

Creating an object is sometimes just allocation plus a few obvious values. In that case, direct construction is easy to read and easy to maintain. But construction can gradually become a decision process: choose an implementation, supply required collaborators, apply defaults, validate combinations, and ensure every caller assembles the object the same way. When that policy is copied across callers, a change to construction becomes a search-and-edit exercise. One caller may miss a new dependency or keep an obsolete default even though the resulting objects share the same conceptual role.

Software Engineering 09 Sep 2026 9 min read

Turning Raw Input into Trusted Domain Values

A value often enters a program as a string, number, or loosely structured object and then travels through several layers. If every layer must ask whether that value is empty, malformed, or outside an allowed range, validation logic spreads through the codebase. Some callers repeat the checks, some forget them, and others quietly make different assumptions. A useful alternative is to treat external input as untrusted representation and convert it at a boundary into a value that represents a domain fact. After that conversion succeeds, downstream code can rely on the guarantees provided by the new value instead of repeatedly validating the original representation.

Software Engineering 09 Sep 2026 9 min read

Replacing Boolean Parameters with Explicit Choices

A call such as sendReport(report, true) may be perfectly valid code, yet it makes the reader stop. What does true mean? Send immediately? Include attachments? Compress the report? The answer exists somewhere in the called function’s contract, but it is not visible at the call site. Boolean parameters become a design problem when they represent an important choice between behaviors. They compress that choice into true or false, so callers must remember what each value means and the implementation often grows branches around the flag.