Skip to content

Archive

Software Design

88 articles
Software Engineering 09 Sep 2026 9 min read

Feature Flags Need a Removal Plan

A feature flag can make a risky change easier to release. You deploy both the old and new behavior, choose which one runs at runtime, and change that choice without rebuilding the application. The same mechanism creates a maintenance problem. Every flag adds another condition the code may execute under. If the flag remains after the decision is settled, developers must keep reasoning about behavior that no longer needs to be optional.

Software Engineering 09 Sep 2026 9 min read

Designing Operations to Avoid Partial State

A function can report an error and still leave trouble behind. The first few steps may have changed state before a later step failed, so the caller receives a failure while the system now contains a mixture of old and new values. This is partial state: an operation did not complete, but some of its intended changes became visible. Partial state makes retrying, debugging, and reasoning about invariants harder because “the operation failed” no longer tells you what state remains.

Software Engineering 09 Sep 2026 9 min read

Decision Tables for Complex Business Rules

Business rules often start as a few harmless conditions. Then another exception arrives, followed by a special customer type, a threshold, and a fallback. The code still runs, but reviewing it becomes difficult because the real question is no longer “what does this if statement do?” It is “have we handled every meaningful combination of conditions, and do any rules disagree?” A decision table makes those combinations explicit before they are buried in branching code. It lists the conditions that matter, the relevant combinations of those conditions, and the outcome for each combination.

Software Engineering 09 Sep 2026 9 min read

Command-Query Separation for Predictable Operations

A method that returns a value can look harmless even when calling it changes the system. That mismatch creates bugs that are difficult to see at the call site: remaining = cart.removeItem(itemId) Does removeItem only remove the item? Does it return the removed item, the number of remaining items, or a success flag? More importantly, can code safely call it while merely trying to inspect the cart?

Software Engineering 09 Sep 2026 7 min read

Adding Behavior with the Decorator Pattern

A component often starts with one clear responsibility and then attracts optional behavior. A client sends a request; later the system also needs logging. Some deployments need metrics. Others need caching or access checks. Putting every option inside the original component can make its core job harder to see, while creating subclasses for every combination quickly becomes awkward. The Decorator pattern offers another shape. A decorator implements the same contract as the component it wraps, performs additional work, and delegates the main operation to that wrapped component. Because callers still see the same contract, decorators can be added, removed, and combined without teaching callers about each feature.

Software Engineering 08 Sep 2026 9 min read

Turning Validation into Trusted Data

A request enters an application with an email address, a quantity, and a delivery method. The request handler validates all three fields. Later, the pricing code checks the quantity again. The notification code checks the email again. A background job checks the delivery method again. The system has validation, but developers still cannot tell which values are safe to use without checking them first. A more useful design goal is to make validation change what the program knows about the data. Unchecked input crosses a boundary, validation establishes specific facts, and successful validation produces a representation that preserves those facts. Code after that boundary can then rely on the representation instead of repeatedly rediscovering the same conditions.

Software Engineering 08 Sep 2026 9 min read

Testing Hard-to-Test Code with Humble Objects

Some code is difficult to test for reasons that have little to do with the behavior you care about. A user-interface callback depends on a framework event loop. A scheduled job reads the clock, queries a service, writes a file, and decides whether to alert someone. A device handler receives data through an operating-system API before applying a simple rule. When the decision and the awkward environment live in the same unit, every test inherits the environment’s complexity. The test may need framework setup, timing control, filesystem state, or several mocks just to reach a small branch.

Software Engineering 08 Sep 2026 9 min read

Refactoring Feature Envy by Moving Behavior

A method belongs to InvoiceService, but most of its code asks an Invoice for fields, combines those fields according to invoice rules, and barely uses the service’s own state. Every time the invoice model changes, the service changes with it. This is a common design smell called feature envy: behavior lives in one place but depends heavily on the data or rules owned by another place. The name is less important than the maintenance problem. Knowledge that belongs together is split across a boundary, so one conceptual change requires coordinated edits.

Software Engineering 08 Sep 2026 8 min read

Parse Inputs into Trusted Data at System Boundaries

Input validation often begins as a few sensible checks and slowly spreads through a codebase. A controller checks that an amount is positive. A service checks it again. A helper receives the same primitive value and checks it a third time because it cannot tell whether the earlier checks ran. The problem is not that validation is useless. The problem is that the program keeps carrying data in a form that does not record what has already been established.

Software Engineering 08 Sep 2026 10 min read

Making Resource Ownership Explicit

A function opens a file and returns a parser. A factory creates a client backed by a connection pool. A component starts a worker and hands another component a handle. Everything works until shutdown, an exception, or a refactor exposes a basic question that the design never answered: who is responsible for releasing the resource? Resource leaks are often described as missing cleanup calls. That is only the visible failure. The deeper design problem is ambiguous ownership. If several parts of the program can use a resource but none clearly owns its lifetime, cleanup becomes guesswork.

Software Engineering 08 Sep 2026 10 min read

Making Invalid States Hard to Represent

A function receives an Order and immediately checks whether its total is negative, its currency is missing, and its status is compatible with its payment state. Another function repeats some of those checks. A third forgets one. The codebase contains validation everywhere, yet invalid combinations still appear. The deeper problem is not a shortage of if statements. The program allows states that its own rules say should never exist, then asks every consumer to defend itself against them.

Software Engineering 08 Sep 2026 8 min read

Keeping Behavior Close to the Data It Uses

A method can live in the wrong place even when its code is correct. One common sign is a method that repeatedly reads another object’s fields, interprets those values, and makes a decision that is really about that other object. This creates a maintenance problem. The data and the rules governing that data change for related reasons, but the code is stored in different places. A small change to the meaning of the data can then require developers to find and update distant callers.

Software Engineering 08 Sep 2026 9 min read

Information Hiding as a Design Tool

A module can have private fields and still expose too much. Callers may know which storage format it uses, which sequence of operations is required, or which implementation rule determines a result. When that hidden-looking detail changes, code outside the module must change with it. Information hiding is a design principle for preventing that spread. The idea is simple: identify a design decision that other code should not need to know, then place that decision behind a boundary whose public contract can remain stable when the decision changes.

Software Engineering 08 Sep 2026 9 min read

Functional Core, Imperative Shell for Testable Business Logic

Business logic often becomes difficult to test for a reason that has little to do with the rule itself. A function that decides whether to approve a refund may also read a database, check the clock, call another service, write an audit record, and send a message. The decision is now mixed with the machinery required to obtain inputs and apply outputs. Tests must control all of that machinery just to ask, “What should happen for this refund?”

Software Engineering 08 Sep 2026 11 min read

Designing Read-Your-Writes Consistency

A user changes their delivery address, sees a success message, and opens the order page. The old address appears. They refresh a few seconds later and the new value finally shows up. Nothing necessarily lost the write. The system may have accepted it correctly and then served the next read from a copy that had not caught up yet. From the user’s perspective, however, a successful update appeared to reverse itself.

Software Engineering 08 Sep 2026 8 min read

Designing for Change Locality

A product rule changes from “free delivery above $50” to “free delivery above $60.” The code change sounds small, but the developer has to edit a checkout service, an order validator, a receipt formatter, and two unrelated utility modules. Missing one location leaves the system internally inconsistent. The problem is not simply that several files changed. Some changes legitimately cross many files. The warning sign is that one conceptual decision is represented in several places that must change together.

Software Engineering 07 Sep 2026 10 min read

Reducing Temporal Coupling by Making Order Explicit

Some APIs look simple because each method is simple. The difficulty appears only when you try to use them correctly: one method must run before another, a third method is legal only after some state change, and cleanup must happen at the end. The code is then coupled not only to what operations exist, but also to when they happen. This is temporal coupling: correctness depends on operations occurring in a particular order. Some ordering is unavoidable. You cannot read a file before opening it, and a transaction cannot commit before it begins. The design problem is hidden or unnecessarily fragile ordering, where callers must remember rules that the API does not make clear.

Software Engineering 07 Sep 2026 9 min read

Reducing Coupling with Connascence

Two pieces of code are coupled when a change in one can require a change in the other. That definition is useful, but it leaves an important engineering question unanswered: which coupling should you fix first? A shared constant, a parameter order, and a distributed workflow can all create coupling. Treating them as equally harmful leads to unnecessary abstractions in some places and fragile dependencies in others. Connascence gives a more precise mental model. Two software elements are connascent when they must agree in some way for the system to work correctly. By asking what must agree, how difficult that agreement is to maintain, how far apart the elements are, and how many elements participate, you can make better refactoring decisions.

Software Engineering 07 Sep 2026 10 min read

Reducing Change Coupling with the Law of Demeter

A line of code can be short and still know too much. Consider a shipping service that needs the destination country for an order: country = order.customer().profile().shippingAddress().countryCode() The line works, but it depends on several structural decisions at once: an order has a customer, the customer has a profile, the profile owns the shipping address, and the address exposes a country code. If any link in that path changes, the shipping service may need to change even though its actual responsibility did not.

Software Engineering 07 Sep 2026 10 min read

Keeping Behavior Close to Data with Tell, Don’t Ask

A class can hide its fields and still force every caller to understand its rules. The usual symptom is code that asks an object for several values, makes a decision with those values, and then tells the object what state to change. That design spreads knowledge. When the rule changes, every place that reconstructed the rule may need to change too. Tell, Don’t Ask is a design heuristic for reducing that problem. Instead of asking an object for internal information so another object can decide what should happen, tell the object the intent and let the component that owns the relevant rules make the decision.

Software Engineering 06 Sep 2026 9 min read

Designing Tolerant Readers for Evolving Contracts

A service reads a response from another system. It needs two fields, but its deserializer models twenty. A harmless producer change adds a field, changes an unused field, or expands an enum that the consumer never acts on. The consumer still breaks because it accidentally depended on more of the contract than its job required. A tolerant reader avoids that unnecessary coupling. It reads the smallest part of an external representation that the consumer needs and rejects changes only when they threaten assumptions the consumer actually relies on.

Software Engineering 06 Sep 2026 9 min read

Designing Modules with Information Hiding

A module can have private fields and still expose too much of its design. Callers may know how its data is stored, which steps must happen in which order, or which third-party concepts sit underneath it. When one of those decisions changes, code outside the module must change too. Information hiding is the practice of placing a design decision behind a boundary so other code depends on what the module provides, not on how it provides it. The goal is not secrecy. The goal is to contain the cost of change.

Software Engineering 06 Sep 2026 9 min read

Designing APIs with Preconditions and Postconditions

An API can have clear parameter names and still leave its most important rules unstated. Can a withdrawal amount be zero? Must an account already be open? If a call succeeds, is the balance guaranteed to have changed, or has the request merely been accepted for later processing? When those questions are unclear, callers make assumptions. Different callers may make different assumptions, and failures appear far from the decision that caused them.

Software Engineering 05 Sep 2026 10 min read

Splitting Mixed Work into Explicit Phases

A function often starts with one job and gradually becomes a pipeline hidden inside a block of code. It reads raw input, interprets it, applies business rules, prepares parameters, performs an external action, and formats the result. Each step may be reasonable, but mixing them makes the whole function harder to understand and change. A useful response is Split Phase: separate work that happens for different reasons into explicit stages, and pass a meaningful result from one stage to the next.