Skip to content

Archive

Software Engineering

462 articles
Software Engineering 05 Sep 2026 10 min read

Replacing Primitive Data with Domain Types

Many bugs begin with a value that is technically valid for its programming-language type but invalid for the job it represents. A quantity is negative. A percentage is passed as 20 where another function expects 0.20. Two string arguments are swapped because both look identical to the type system. The problem is not that strings and numbers are poor types. They are useful building blocks. The problem appears when a primitive value has accumulated rules or meaning that the primitive cannot express.

Software Engineering 05 Sep 2026 8 min read

Replacing Magic Values with Named Concepts

A literal value can be perfectly clear when it describes the mechanics of a calculation. index + 1 usually needs no explanation. But the same syntax becomes harder to understand when a value carries a business rule or engineering decision: if retries > 3, price * 0.85, or timeout = 30. The problem is not that numbers or strings appear in code. The problem is that some literals have meaning that the code does not name. These are often called magic values.

Software Engineering 05 Sep 2026 9 min read

Replacing Boolean Flag Parameters with Explicit Operations

A boolean parameter looks harmless because it carries only two values. The problem is that those values often control two different behaviours while saying almost nothing at the call site. Consider set_access(user, true). Does true mean grant access, require approval, make access permanent, or enable logging? A developer has to remember the parameter name or inspect the function before the call becomes clear. This problem becomes more expensive when the flag selects different validation, side effects, or failure rules. One function then behaves like two operations hidden behind a small parameter.

Software Engineering 05 Sep 2026 9 min read

Replace Query-Then-Act with Intention-Revealing Operations

An object can expose perfectly reasonable getters and still make a system difficult to change. The problem appears when callers repeatedly read those values, interpret them, and then decide which mutation is allowed. Suppose several parts of an application do this: if order.status == "pending" and order.paymentReceived: order.status = "confirmed" The caller is not merely using data. It knows the rule for confirming an order. If another caller needs the same behavior, that rule is likely to be copied. When the rule changes, every copy becomes a place that can disagree.

Software Engineering 05 Sep 2026 8 min read

Refactoring Tangled Methods with Method Objects

A long method is not automatically a design problem. Sometimes a calculation is easiest to understand when its steps stay together. Trouble starts when one method accumulates many temporary values, later steps depend on several earlier results, and extracting any part requires passing a long list of arguments. At that point, ordinary Extract Method refactoring can feel blocked by the method’s local state. A method object is one way through that problem: move the computation into a short-lived object, turn the important local variables into fields, and then extract parts of the computation into small methods on that object.

Software Engineering 05 Sep 2026 8 min read

Reducing Object Graph Coupling with the Law of Demeter

A small change to an object model can cause surprising edits far away from the changed class. A developer moves an address under a customer profile, for example, and code in pricing, notifications, and reporting all breaks because each caller navigates the same chain of objects. The immediate problem looks like missing properties. The deeper problem is that those callers know the shape of an object graph they do not own.

Software Engineering 05 Sep 2026 10 min read

Reducing Change Amplification by Keeping Decisions Together

A small requirement can produce a surprisingly large patch. Changing one pricing rule might require edits in an API handler, a validator, a report formatter, three tests, and a scheduled job. None of the edits is difficult by itself, yet missing one can leave the system inconsistent. This is change amplification: one conceptual change requires modifications in many places. The practical problem is not the number of files alone. It is that knowledge about one decision is scattered, so developers must rediscover every place that encodes it whenever the decision changes.

Software Engineering 05 Sep 2026 9 min read

Protecting Your Model with an Anti-Corruption Layer

Integrating another system often starts with a small amount of mapping code. Then its field names appear in business logic. Its status values enter conditionals. Its error codes shape application decisions. Months later, changing providers or even upgrading the integration requires edits across the codebase. The problem is not simply that the application has an external dependency. The deeper problem is that the external system’s model has become part of the application’s own model.

Software Engineering 05 Sep 2026 10 min read

Ports and Adapters for Testable Application Boundaries

A business rule often begins as a few lines of code and gradually becomes entangled with everything around it. A pricing decision reads directly from a database. An order workflow calls a payment SDK from the middle of its logic. Tests need a web server, network access, and several configuration files just to exercise one decision. The problem is not that databases, frameworks, or SDKs are bad. The problem is that application decisions have become dependent on details that change for different reasons.

Software Engineering 05 Sep 2026 9 min read

Moving Behavior Closer to the Data It Uses

A function often starts in a reasonable place and becomes awkward as the system grows. It reads several fields from another object, interprets those fields, applies rules to them, and repeats the same pattern whenever a new requirement appears. The problem is not simply that the function is long. The deeper problem is responsibility placement: one part of the system owns the data, while another part knows too much about what that data means.

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

Designing with Preconditions and Postconditions

A function can have a precise type signature and still leave its most important rules implicit. A transfer operation may accept two accounts and an amount, yet the signature does not tell you whether the amount may be zero, whether the source needs enough funds, or what must be true after a successful transfer. When these rules remain scattered through comments, conditionals, and tests, callers have to reconstruct the contract themselves. That makes misuse easier and changes harder to reason about.

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

Designing Idempotent Operations for Safe Retries

A caller sends a request to create a payment. The server processes it, but the response is lost because the connection closes. The caller now has a difficult choice: retry and risk charging twice, or stop and risk leaving the payment incomplete. This is not mainly a networking problem. It is an operation-design problem. When a caller cannot tell whether an attempt succeeded, retrying is only safe when the system has a way to recognize that the new attempt represents the same intent.

Software Engineering 05 Sep 2026 9 min read

Characterization Tests for Legacy Code

Refactoring unfamiliar code creates an uncomfortable problem: you want to improve the implementation, but you may not know which parts of its current behavior callers depend on. Existing documentation can be incomplete, and a thin test suite may describe only the obvious cases. A characterization test helps by recording what the software does now. Instead of starting from a specification of what the code should do, you exercise existing behavior, observe the result, and turn that observation into a test. The test then warns you when a later change alters that behavior.

Software Engineering 04 Sep 2026 10 min read

Versioning Contracts, Not Just Releases

A version number is useful only when its consumers can make a decision from it. Suppose a library changes from 2.4.1 to 2.5.0. A developer considering the upgrade wants to know something practical: can existing code keep working, or must it change? The answer does not come from how much code the maintainer edited. It comes from whether the release changed a contract that consumers depend on. That contract includes more than function names. It can include accepted inputs, returned values, error behavior, configuration, file formats, command-line options, extension points, and other observable behavior that the project promises to preserve.

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

Snapshot Tests as Reviewed Contracts

Some outputs are easy to test with a few assertions. A price calculation can be checked against one number. A validation rule can be checked against one error code. Other outputs are structured and wide: a rendered document, a compiler diagnostic, a serialized configuration, or a formatted report may contain dozens of fields and lines. Writing an assertion for every detail can make the test harder to read than the behavior it protects.

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

Separating Commands from Queries

A method named getBalance looks harmless. A developer may call it twice, use it while debugging, or add it to a log statement without expecting the program to change. If that method also clears pending adjustments, increments a counter, or refreshes state, those ordinary actions can alter behaviour. The underlying design problem is not simply a poor method name. Reading information and changing state have different consequences, yet one operation is doing both.

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.