Skip to content

Archive

Maintainability

114 articles
Software Engineering 05 Sep 2026 9 min read

Keeping Framework Code Humble at System Boundaries

A user clicks Save. The handler reads text fields, validates an order, calculates a discount, writes data, chooses an error message, and updates the screen. Testing one business rule now requires constructing a UI framework and arranging several unrelated details. The rule itself is not difficult. It is difficult to reach because it is mixed with code that must talk to the outside world. The Humble Object pattern addresses this problem by keeping framework-dependent code deliberately small and moving important decisions into ordinary code that can be exercised directly. This article explains the mental model, shows how to find a useful boundary, and examines where the pattern helps and where it only adds indirection.

Software Engineering 05 Sep 2026 8 min read

Isolating Volatile Dependencies Behind Stable Boundaries

A dependency can be technically easy to call and still be expensive to change. A pricing library may rename operations between releases. A shipping provider may expose a model that changes as its API evolves. An internal rules engine may be rewritten while the business workflow around it stays largely the same. When application code uses those changing details everywhere, each dependency change becomes an application-wide edit. The problem is not simply that the dependency changes. The problem is that knowledge of how it works today has spread into code that has different reasons to change.

Software Engineering 05 Sep 2026 8 min read

Flattening Nested Conditionals with Guard Clauses

A function can be correct and still make its reader carry too much context. One common cause is deeply nested conditionals: before understanding the line in front of you, you must remember every condition that surrounds it. A guard clause handles a condition that prevents the main work from continuing, then exits the current operation early. Used carefully, guard clauses turn exceptional, invalid, or already-finished cases into short branches and leave the normal path at a shallower indentation level.

Software Engineering 05 Sep 2026 8 min read

Designing Modules Around Information Hiding

A module can have a small public API and still be difficult to change. The problem appears when callers must know details that supposedly belong inside the module: a storage layout, a retry rule, a naming convention, a calculation step, or the order in which internal operations happen. When those details change, callers change too. The boundary exists in the code, but it does not contain the knowledge that creates maintenance work.

Software Engineering 05 Sep 2026 9 min read

Designing Interfaces Around Client Needs

A shared component often starts with a small interface. As more callers arrive, new methods are added until every caller sees operations it never uses. The interface still works, but it now connects unrelated needs through one contract. That matters because an interface is a dependency. When a client depends on a broad contract, changes to unrelated parts of that contract can affect its compilation, tests, mocks, generated code, or understanding of the component even when the client needs only one capability.

Software Engineering 04 Sep 2026 8 min read

Translating Errors at Abstraction Boundaries

A function can hide how data is stored and still leak the storage technology through every error it returns. When that happens, callers become coupled to details the abstraction was supposed to contain. Suppose an order service asks a repository to load an order. The repository promises to find orders; it does not promise that callers understand SQL drivers, HTTP clients, file formats, or whichever mechanism happens to sit underneath it. If a missing order appears as a driver-specific NoRows exception today and an HTTP 404 exception after a migration, callers must change even though the repository’s meaning did not.

Software Engineering 04 Sep 2026 9 min read

Separating Decisions from Side Effects

Business logic often becomes difficult to test for a simple reason: the code that decides what should happen is mixed with the code that makes it happen. A function reads the clock, queries storage, applies pricing rules, sends an email, and writes an audit record. To test one discount rule, the test must now arrange several unrelated dependencies. When a failure occurs, it is harder to tell whether the decision was wrong or an external operation failed.

Software Engineering 04 Sep 2026 10 min read

Separate Decisions from Side Effects

A function reads an order, checks inventory, chooses a shipping method, updates a record, sends an email, and writes a log entry. A small rule change arrives: express shipping is now allowed only when every item is in stock. The rule itself is simple. Testing it is not. To exercise the decision, a test may need a database, an inventory service, an email substitute, and careful setup for several unrelated operations.

Software Engineering 04 Sep 2026 6 min read

Replacing Repeated Null Checks with a Null Object

Optional collaborators often begin with one harmless check. A service sends a notification only when a notifier is configured, so the code asks whether the notifier exists before calling it. Later, the same check appears in five methods, then twelve. The repeated condition is telling you something: absence has a defined behavior. In this case, “no notifier” means “do nothing when asked to notify.” The Null Object pattern represents that behavior with an object that follows the same interface as the real collaborator but performs the appropriate neutral action. This article shows how to recognize that situation, apply the pattern without hiding errors, and decide when a normal null check is still the clearer design.

Software Engineering 04 Sep 2026 8 min read

Replacing Long Parameter Lists with Parameter Objects

A function can become difficult to call correctly even when its implementation is simple. The problem often appears as a growing parameter list: several values travel together, callers must remember their order and meaning, and the same group is passed through multiple layers. One useful refactoring is a parameter object: a small type that groups parameters which belong to one concept. The goal is not to make a function signature shorter at any cost. The goal is to give related data a name, make invalid combinations harder to create, and give future changes a natural home.

Software Engineering 04 Sep 2026 8 min read

Replacing Boolean Parameters with Explicit Operations

A boolean parameter can make an API compact while making each call harder to understand. Consider this call: save(document, true) What does true mean? Overwrite an existing document? Validate before saving? Publish immediately? The caller knows only if they remember the parameter definition or inspect the function.

Software Engineering 04 Sep 2026 8 min read

Removing Temporal Coupling from APIs

Some APIs look simple because each method is simple. The difficulty appears only when you try to use them: configure() must run before connect(), connect() before start(), and stop() is valid only after startup succeeds. When correctness depends on operations happening in a particular order, the code has temporal coupling. The word temporal refers to time or sequence: one operation is valid only because another operation happened earlier. Temporal coupling is not automatically a design flaw. Many real processes have genuine ordering constraints. The engineering problem is hidden temporal coupling: callers must remember an important sequence that the API does not make clear or enforce.

Software Engineering 04 Sep 2026 8 min read

Removing Hidden Temporal Coupling

Some code works only when its methods are called in the right order. The individual methods may look valid, yet swapping two calls or forgetting an earlier call produces an error much later. This is temporal coupling: one operation depends on another operation having happened earlier. The coupling is not automatically a design flaw. Opening a transaction before committing it is a real lifecycle constraint. The problem is hidden temporal coupling, where the API allows an invalid sequence and leaves callers to remember an undocumented rule.

Software Engineering 04 Sep 2026 9 min read

Remove Duplicated Knowledge, Not Similar Code

Developers often learn the DRY principle as “do not repeat yourself.” That shorthand can lead to the wrong refactoring: two blocks look similar, so they are merged into one abstraction. Later, the two cases evolve for different reasons, and the shared abstraction fills with flags and exceptions. The more useful idea is narrower: avoid having the same piece of knowledge represented independently in multiple places. That distinction changes how you refactor. Similar code is only a clue. The real question is whether the copies encode one decision that must stay consistent, or different decisions that merely happen to look alike today.

Software Engineering 04 Sep 2026 8 min read

Reducing Change Amplification in Code

A requirement can sound small and still produce a surprisingly large patch. Add one order state, rename one business concept, or change one validation rule, and suddenly five modules, several tests, and a serializer all need coordinated edits. The problem is not simply that the codebase is large. It is change amplification: one conceptual change causes many implementation changes because knowledge about that concept is spread across the system. This article shows how to recognize change amplification, trace it back to duplicated knowledge, and reduce it without forcing unrelated code into one giant abstraction.

Software Engineering 04 Sep 2026 7 min read

Making Time an Explicit Dependency

Code that asks the system clock for the current time looks harmless. The difficulty appears when behavior depends on the answer. A test for an expired reservation may pass now and fail later. A boundary such as midnight can become awkward to reproduce. Business logic becomes tied to an environmental input that callers cannot see or control. A useful design move is to treat the current time as a dependency. Instead of letting decision-making code reach for a clock whenever it needs one, obtain the time at a clear boundary and pass the relevant value or clock into the code that makes the decision.

Software Engineering 04 Sep 2026 9 min read

Limiting Object Navigation to Protect Encapsulation

A method can depend on far more of a system than its parameter list suggests. The warning sign is often a chain that reaches through one object to inspect several others: order.customer.address.country.code The expression is short, but the caller now knows that an order has a customer, the customer has an address, the address has a country, and the country exposes a code. A change anywhere along that path can force the caller to change even when the caller’s real question has not changed.

Software Engineering 04 Sep 2026 9 min read

Keeping Framework Code Thin with Humble Objects

Some code is difficult to test for reasons that have little to do with the business rule it implements. A user-interface handler may need a framework event. A scheduled job may be created by a runtime. A message consumer may receive objects owned by a library. The code then mixes two jobs: interacting with an awkward environment and deciding what the application should do. When those jobs stay together, a small rule can require a large test setup. Developers may respond by skipping tests, mocking many framework details, or testing through slow integration paths even when the important logic is simple.

Software Engineering 04 Sep 2026 9 min read

Keeping Behavior Close to the Data It Needs

A method can belong to one module while doing most of its thinking with data owned by another. When that happens repeatedly, a small change to the data often forces changes in distant code as well. This design smell is commonly called feature envy: behavior appears more interested in another object or module than in the one that contains it. The useful lesson is broader than the name. When a piece of behavior depends heavily on particular data and the rules around that data, keeping them close usually makes those rules easier to find, protect, and change together.

Software Engineering 04 Sep 2026 9 min read

Feature Flags as Temporary Control Points

Deploying code and exposing new behavior do not have to happen at the same moment. That distinction matters when a change is difficult to reverse quickly, needs a gradual rollout, or should be available only to a small group while engineers observe its behavior. A feature flag provides a runtime decision point: the deployed code can contain both behaviors while configuration chooses which one is active for a particular request, user, tenant, or environment.

Software Engineering 04 Sep 2026 10 min read

Designing for Reversible Decisions

Software teams make decisions with incomplete information. A library looks suitable until production traffic exposes a limitation. A pricing rule changes after customers use it. A component boundary that seemed natural becomes awkward when a new workflow arrives. The problem is not that developers sometimes choose incorrectly. The harder problem is making an uncertain choice so expensive to change that the system must live with it long after the assumptions behind it have failed.

Software Engineering 03 Sep 2026 9 min read

Using Feature Flags Without Creating Permanent Complexity

A code change is often ready to deploy before it is ready to expose to every user. A team may want to test a new workflow with internal users, release it gradually, or keep unfinished behavior inactive while several changes reach production. A feature flag provides that control. It is a runtime decision that selects between behaviors without requiring a new deployment for every change in exposure. That sounds simple, but feature flags create their own engineering cost. Every active flag can introduce another path through the system. If flags accumulate, developers eventually have to reason about combinations of old and new behavior that nobody intended to keep forever.

Software Engineering 03 Sep 2026 11 min read

Using Decision Tables to Tame Complex Rules

Conditional logic often becomes difficult before it becomes large. Three or four business conditions can interact in enough combinations that a developer can no longer tell, by reading nested if statements, whether every case is covered or whether two branches contradict each other. A decision table is a compact way to make those combinations explicit. It lists the conditions that affect a decision, the meaningful combinations of those conditions, and the outcome for each combination.

Software Engineering 03 Sep 2026 6 min read

Refactoring Legacy Code with Characterization Tests

Legacy code is difficult to change when its important behaviour is poorly understood and existing tests do not provide enough confidence. The risk is not only that a refactoring introduces a bug. The deeper problem is that developers may not know which behaviours are intentional, accidental, or relied on by other parts of the system. Characterization tests help reduce that uncertainty. Instead of beginning with a specification of what the code should do, they capture what the code does today. That behavioural baseline can make structural improvement safer while the team learns the system.