Skip to content

Archive

Software Engineering

462 articles
Software Engineering 10 Sep 2026 8 min read

Replace Conditional with Polymorphism When Branches Represent Types

Replace Conditional with Polymorphism When Branches Represent Types A conditional isn’t a design problem just because it has several branches. Sometimes if or switch is the clearest way to express a decision. Trouble starts when the same type-based decision appears in several places and every new variant requires editing all of them. Replace Conditional with Polymorphism is a refactoring that moves variant-specific behavior behind a common operation. Instead of asking an object what kind it is and then deciding what to do, callers ask it to perform the behavior directly.

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

Reconciliation Loops for Self-Healing Systems

Reconciliation Loops for Self-Healing Systems A one-shot operation works well when every step succeeds. Real systems are less cooperative. A process crashes after creating half its resources, an external API times out after accepting a request, an operator changes something manually, or a dependency becomes unavailable and recovers later. If correctness depends on one command completing perfectly, every interruption creates another recovery path to design and operate. A reconciliation loop uses a different model. Instead of saying, “perform these steps once,” the system repeatedly asks, “what should be true, what is true now, and what is the smallest safe action that moves reality toward the desired state?” That shift is useful for controllers, background jobs, provisioning systems, synchronizers, and any workflow where state can drift after the initial operation.

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

Negative Caching for Repeated Failures

Negative Caching for Repeated Failures Caching usually brings successful results to mind: load a value once, keep it for a while, and avoid repeating expensive work. But repeated failures can be just as expensive as repeated successes. Suppose a service receives thousands of requests for an object that does not exist. If every request queries the same downstream system, the absence of that object becomes a source of load. The same pattern appears with invalid identifiers, unavailable optional resources, failed name lookups, and other outcomes that are expensive to rediscover but unlikely to change immediately.

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

Differential Testing for Behavior-Preserving Changes

Differential Testing for Behavior-Preserving Changes Replacing working code is risky when the requirement is “change the implementation, not the behavior.” A rewritten parser may accept a different edge case. A faster pricing engine may round one value differently. A new library may return the same records in a different order. Ordinary tests help, but they only cover cases and assertions someone thought to write. Differential testing adds another source of evidence: run the old and new implementations on the same inputs, compare their observable results, and investigate differences.

Software Engineering 10 Sep 2026 10 min read

Designing Graceful Degradation for Partial Failures

Designing Graceful Degradation for Partial Failures A page needs product details, recommendations, reviews, and delivery estimates. The product service is healthy, but the recommendation service times out. Should the whole page fail? Sometimes yes. If the missing dependency is required to produce a correct result, failing the operation is the right behavior. But when the missing part is genuinely optional, turning one local failure into a complete outage throws away useful work.

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

Creating Seams to Test Hard-to-Change Code

Creating Seams to Test Hard-to-Change Code A method can contain a simple business rule and still be difficult to test. The difficulty often comes from everything attached to the rule: the current clock, a network client, a filesystem call, a global configuration object, or a constructor that creates its own dependencies. When rewriting the surrounding code would be risky, a seam can give you a smaller move. A seam is a place where you can change which behavior the code uses without changing the code that makes the business decision. In tests, that lets you replace an awkward dependency with controlled behavior.

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

Choosing Sociable or Solitary Unit Tests

Choosing Sociable or Solitary Unit Tests A unit test fails after a harmless refactor. The production behaviour is still correct, but the test expected an internal collaborator to receive exactly three calls. Elsewhere, a different unit test passes even though two real classes no longer work together, because both sides were replaced with mocks. Both tests are isolated in some sense, yet they give poor feedback for different reasons. The useful question isn’t simply whether unit tests should use mocks. It is where the test boundary should be.

Software Engineering 09 Sep 2026 11 min read

Using Hedged Requests to Reduce Tail Latency

Most calls to a dependency may finish quickly while a small fraction take much longer. A request that depends on one of those slow calls inherits the delay even when another healthy instance could have answered sooner. Increasing the timeout does not solve this problem. Retrying only after the timeout may also be too late: by then, the caller has already spent most of its latency budget. A hedged request is a deliberately delayed duplicate of an operation that is still in progress. The original request starts normally. If it has not completed after a chosen delay, the caller sends one additional equivalent request, usually to another eligible instance. The first acceptable result wins, and the remaining work is cancelled or ignored.

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

Transactional Outbox for Reliable Message Publishing

Transactional Outbox for Reliable Message Publishing A common service operation has to do two things: change its own data and tell another part of the system what happened. For example, an order service may mark an order as paid and publish an OrderPaid message. The awkward part is that the database and message broker usually have separate commit mechanisms. If the service updates the database and then publishes, it can crash between those steps. If it publishes first, the database update can fail afterward. Either order can leave the two systems disagreeing about what happened.

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.

Software Engineering 09 Sep 2026 8 min read

Reducing Positional Coupling in Function Calls

A function can be perfectly implemented and still be easy to call incorrectly. One common cause is a parameter list in which several values have the same representation and their meaning depends mainly on position. Consider a reporting function that accepts three dates. At the call site, the values may all look valid even when two of them are accidentally reversed. A compiler or type checker often cannot help because each argument still has the expected type.

Software Engineering 09 Sep 2026 8 min read

Reducing Coupling with the Law of Demeter

A small change to one class should not routinely force edits in code that is several objects away. Yet this happens when callers navigate through collaborators to reach deeper objects: order.customer().address().country().code() The line is compact, but the caller now knows that an order exposes a customer, a customer exposes an address, an address exposes a country, and a country exposes a code. If that structure changes, the caller may need to change even when the business question it asks stays the same.

Software Engineering 09 Sep 2026 11 min read

Propagating Deadlines Through Call Chains

A request can have a timeout at every network call and still take far longer than the caller intended. The problem appears when each layer starts a fresh timeout. A frontend gives service A 800 milliseconds. Service A spends 300 milliseconds doing local work, then gives service B another 800 milliseconds. Service B spends 250 milliseconds and gives service C yet another 800 milliseconds. Every individual timeout looks reasonable, but the chain no longer has an 800-millisecond limit.