Skip to content

Topic archive

Software Engineering

Software Engineering covers developer tooling, architecture, maintainability, engineering workflows, protocols, and practices that improve how software is designed and built.

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

Parse Input into Trusted Types

A common validation problem is not that a program forgets to check input. It is that the program checks the input, keeps the same weak representation, and then has to remember what was already proved. Suppose an order accepts a quantity as an integer. The boundary rejects zero and negative values, but every later function still receives an ordinary integer. Nothing in that representation distinguishes a checked quantity from 0, -3, or an integer created somewhere else. The validation happened, but the result of that validation was not captured.

Software Engineering 04 Sep 2026 9 min read

Parse Boundary Data into Trusted Types

Validation often starts as a small check near the edge of a program. As the system grows, the same fact gets checked again in handlers, services, helpers, and background jobs because none of those places can tell whether the value they received has already been validated. The result is defensive code everywhere and uncertainty about what a function may safely assume. A useful design technique is to parse boundary data into a trusted type. Instead of checking a raw value and then continuing to pass that raw value around, convert it into a representation that can exist only after the required checks succeed. Core code receives that representation and can rely on the facts it expresses.

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

Designing Failure Containment Boundaries

A component fails. Soon unrelated requests become slow, worker queues stop moving, and healthy features begin returning errors. The original defect may be small, but the system has allowed its effects to spread. Failure containment is the design practice of limiting how far a fault can propagate. The goal is not to prevent every failure. That is unrealistic. The goal is to make a local failure stay local enough that the rest of the system can continue useful work or fail in a controlled way.

Software Engineering 04 Sep 2026 9 min read

Creating Seams for Safe Legacy Changes

Legacy code is often difficult to change for a reason that has little to do with the business rule you need to edit. The code may create its own database client, read the clock directly, call a remote service in the middle of a calculation, or depend on another component that is expensive or unreliable in tests. You may understand the desired change and still be unable to test it in isolation.

Software Engineering 04 Sep 2026 9 min read

Assertions That Expose Broken Assumptions

A program can produce the wrong result long after the code that caused the problem has run. A function corrupts an internal value, several operations accept it, and an unrelated component eventually fails. By then, the stack trace points at the consequence rather than the cause. Assertions help shorten that distance. An assertion states an internal condition that the programmer believes must be true at a particular point in the program. If the condition is false, the program reports a broken assumption immediately instead of continuing as though the state were valid.

Software Engineering 03 Sep 2026 8 min read

Validating at Boundaries to Contain Invalid Data

Many software failures begin far from the place where they are eventually noticed. A malformed value enters through an API, configuration file, message, command, or user action. The program accepts it, passes it through several layers, and fails later when some unrelated operation assumes the value is valid. By then, the original cause is harder to see. A useful design principle is to validate data at the boundary where it enters a trusted part of the system. A boundary is any point where code receives information whose assumptions it does not yet control. The goal is not to scatter checks everywhere. It is to turn uncertain input into either a known-valid value or an explicit failure before the rest of the program relies on it.

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

Replacing Legacy Systems with the Strangler Pattern

Replacing a large legacy system in one release is attractive on a diagram and dangerous in practice. The old system usually contains years of behaviour, undocumented edge cases, operational knowledge, and integrations that are difficult to reproduce all at once. The strangler pattern takes a different approach: place a boundary in front of the existing system, move one capability at a time to a new implementation, and gradually reduce the responsibilities of the old system until it can be retired.

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.

Software Engineering 03 Sep 2026 11 min read

Reducing Temporal Coupling in Code

Some APIs look simple because each method is simple. The difficulty appears only when the methods must be called in exactly the right order. A report builder might require loadData() before render(). A client might require connect() before send(). A job might require prepare() before execute() and execute() before publish(). When those rules exist but are not visible in the interface, callers must remember history: What has already happened to this object? That dependency on operation order is called temporal coupling.

Software Engineering 03 Sep 2026 12 min read

Put Decisions Next to the Data They Need

A method can look perfectly reasonable while depending on far more knowledge than it should. Imagine checkout code that asks a customer object for membershipLevel, joinedAt, and totalOrders, then combines those values to decide whether the customer receives priority support. The calculation works, but checkout now knows both the customer’s data and the rule that gives that data meaning. When the rule changes, every caller that reconstructed it becomes a possible edit site.

Software Engineering 03 Sep 2026 7 min read

Mutation Testing for Stronger Test Suites

A test suite can execute every important line of code and still miss serious defects. Coverage tells you which code ran, but it does not tell you whether the tests would notice if that code behaved incorrectly. Mutation testing examines that gap. It makes small, systematic changes to production code and runs the tests against each changed version. If the tests fail, the mutation is killed. If they still pass, the mutation survives and points to a place where the suite may not distinguish correct behaviour from incorrect behaviour.