Skip to content

Archive

Software Design

88 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

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

Tell, Don’t Ask: Keep Decisions with the Object That Owns the Data A class can expose perfectly reasonable getters and still make a codebase harder to change. The trouble appears when callers fetch several values, interpret them, make a business decision, and then tell the object how to update itself. The data lives in one place, but the rule that gives the data meaning lives somewhere else. Tell, Don’t Ask is a design guideline for reducing that split. Instead of asking an object for internal state so a caller can decide what should happen, prefer telling the object the meaningful operation you want performed. The object can then apply the rules that belong with its state.

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 Data Clumps with Parameter Objects

Replace Data Clumps with Parameter Objects A method takes startDate, endDate, and timezone. Another method takes the same three values. A third passes them unchanged to a lower layer. Soon, changing what “reporting period” means requires editing signatures across the codebase. This is a common design smell called a data clump: several values repeatedly appear together because they are really parts of one concept, but the code still treats them as unrelated pieces. A parameter object gives that concept a name and a boundary.

Software Engineering 11 Sep 2026 9 min read

Parameterize from Above: Make Dependencies Explicit

Parameterize from Above: Make Dependencies Explicit A method can look self-contained while quietly deciding which clock, repository, client, or filesystem implementation the application must use. That hidden construction becomes painful when a test needs a controlled collaborator or production needs a different implementation. Parameterize from Above is a small design move: instead of constructing a dependency inside the code that uses it, accept that dependency from a caller at a higher level. The caller becomes responsible for choosing and constructing the collaborator.

Software Engineering 11 Sep 2026 7 min read

Middle Man Code Smell: Remove Delegation That Adds No Value

Middle Man Code Smell: Remove Delegation That Adds No Value Delegation is useful when one object asks another object to do work. It can separate responsibilities and keep implementation details behind a boundary. But delegation can become ceremony when an object does little more than forward most calls to another object. That situation is commonly called the Middle Man code smell. The extra layer adds names, files, navigation, and maintenance work without owning a meaningful decision.

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 9 min read

Feature Envy: Move Behavior Closer to the Data It Uses

Feature Envy: Move Behavior Closer to the Data It Uses A method can live in one class while spending most of its time inspecting another. It asks that other object for several values, combines them according to rules about that object, and perhaps repeats the same pattern elsewhere. The code works, but changing the data often means hunting down behavior in unrelated places. This is the design smell commonly called feature envy. The name matters less than the question behind it: does this behavior belong closer to the data and rules it depends on?

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 11 Sep 2026 7 min read

Command-Query Separation for Predictable Methods

Command-Query Separation for Predictable Methods A method named getBalance() looks harmless. A caller expects it to report a value. If calling it also recalculates fees, updates an account, and writes an audit record, that caller has to understand much more than the name suggests. Command-query separation is a design principle for avoiding this kind of surprise. A command asks the system to change state. A query asks for information and does not change observable state. Keeping those responsibilities separate makes call sites easier to reason about and gives method contracts a clearer shape.

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 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 8 min read

Parse at Boundaries to Protect Domain Invariants

Parse at Boundaries to Protect Domain Invariants A request arrives with a string that is supposed to be an order quantity. One function checks that the string contains a number. Another checks that the number is positive. A third assumes both checks already happened. Months later, a new caller reaches the third function directly and passes zero. The problem isn’t simply missing validation. The program keeps carrying a weak representation after it already knows something stronger about the value.

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 10 min read

Design by Contract: Make Assumptions Explicit

Design by Contract: Make Assumptions Explicit A function often depends on rules that its type signature doesn’t fully express. A withdrawal amount must be positive. A completed operation must leave the balance consistent. An object may require its reserved quantity to stay between zero and the quantity on hand. When those rules live only in developers’ heads, failures appear far from their cause. Design by Contract gives the rules names and assigns responsibility for them. The useful mental model is simple: the caller promises to meet the operation’s entry conditions, and the operation promises a valid result while preserving the object’s valid state.

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 8 min read

Making Complex Rules Visible with Decision Tables

Conditional code often starts clearly. One condition becomes two, then a special case appears, and eventually nobody can answer a simple question with confidence: have we covered every meaningful combination? The problem is not necessarily that if statements are bad. The problem is that branching code makes a set of rules visible one execution path at a time. When several independent conditions affect one decision, developers must mentally reconstruct the whole rule set from those paths.

Software Engineering 09 Sep 2026 8 min read

Isolating Hard-to-Test Code with a Humble Object

Some code is difficult to test for reasons that have little to do with the behavior you care about. A screen handler may require a UI framework. A file watcher may need operating-system events. A message consumer may only run inside a broker callback. Tests become slow or fragile because ordinary business decisions are trapped inside code that is expensive to execute in isolation. The Humble Object pattern addresses this by separating the difficult boundary from the logic behind it. The boundary object stays deliberately small: it translates an external event into plain data, calls ordinary code, then translates the result back. The decisions move into code that can be exercised without the framework or environment.