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

Metamorphic Testing When Exact Answers Are Hard

Some software is easy to test because the expected answer is obvious. If a function adds two numbers, a test can call it with 2 and 3 and assert that the result is 5. Other software has outputs that are expensive or awkward to predict. A route planner may examine thousands of possible paths. A search ranker may score hundreds of candidates. A numerical routine may produce a result that is difficult to calculate independently without reimplementing the same algorithm.

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

Finding Regressions with Binary Search

A regression appears in the current build, but the same behavior worked two weeks ago. Since then, the team has merged 80 changes. Reading all 80 diffs is possible, but it is slow and gives every change equal attention even though only one boundary in history matters: the point where the behavior changed from working to broken. When revisions are ordered and you can classify a revision reliably as good or bad, you can search that history with the same idea as binary search. Test a revision near the middle. Its result tells you which half can still contain the first bad revision. Repeat until only the transition remains.

Software Engineering 08 Sep 2026 9 min read

Enforcing Architecture with Fitness Functions

A team can agree on a sensible architecture and still watch it erode one small change at a time. A developer imports an internal module because it is convenient. Another adds a direct dependency across layers. Months later, the diagram still shows clean boundaries, but the code no longer follows them. Code review can catch these changes, but reviewers must remember every architectural rule and notice every violation. For properties that matter repeatedly, memory is a weak enforcement mechanism.

Software Engineering 08 Sep 2026 10 min read

Differential Testing to Compare Implementations

Replacing an implementation creates an uncomfortable testing problem. You may know that the new code should preserve existing behavior, but writing an expected result for every possible input can be expensive. A parser may accept thousands of valid forms. A pricing engine may combine many rules. A rewritten library may have years of accumulated edge cases. In these situations, an implementation you already trust can help test another implementation. Differential testing runs the same input through two or more implementations that are expected to behave equivalently, then compares their observable results. A disagreement does not automatically prove which implementation is wrong. It does something more basic and extremely useful: it gives you a concrete case that requires explanation.

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

Designing a Test Portfolio for Useful Feedback

A test suite can contain thousands of tests and still give poor engineering feedback. It may run slowly, fail for unrelated reasons, miss important integration mistakes, or make small implementation changes expensive because too many tests depend on internal details. The usual response is to argue about test categories: more unit tests, fewer end-to-end tests, or a particular shape such as a testing pyramid. Those models can be useful reminders, but a fixed ratio does not tell you whether a specific test earns its cost.

Software Engineering 08 Sep 2026 8 min read

Choosing Coverage Criteria for Conditional Logic

A test suite can report high coverage and still miss an important bug. The problem is often not the percentage itself, but what the percentage measures. Consider a decision such as isMember && hasCredit. A test may execute the if statement, both outcomes of the decision, or each individual condition in different ways. Those are different kinds of evidence. Treating them as interchangeable makes coverage numbers more reassuring than they should be.

Software Engineering 07 Sep 2026 10 min read

Using Error Budgets to Balance Reliability and Change

Teams often agree that reliability matters and still struggle to decide what to do when reliability competes with product work. Should a risky release proceed? Should engineers stop feature work after a bad incident? Is one failed request enough to justify a freeze? Without a shared rule, these decisions can become arguments between vague goals: “move faster” versus “make it more reliable.” An error budget turns a reliability target into a limited allowance for unsuccessful service. The budget does not make failures desirable. It makes the acceptable amount of unreliability explicit so a team can reason about risk and change using the same constraint.

Software Engineering 07 Sep 2026 11 min read

Understanding Two-Phase Commit

Suppose one business operation must update two independent transactional resources. Writing to the first and then the second creates an uncomfortable failure case: the first write may commit while the second fails. Reversing the order only moves the problem. Two-phase commit, usually shortened to 2PC, is a coordination protocol for making one commit-or-abort decision across multiple participants that can each prepare and commit a local transaction. Its purpose is atomicity across those participants: under the protocol’s assumptions, they do not intentionally finish with some participants committed and others aborted for the same transaction.

Software Engineering 07 Sep 2026 9 min read

Testing Failure Semantics, Not Just Errors

A test that expects an error can still miss the most damaging part of a failure. An order operation may return payment declined correctly while also marking the order as paid. A file import may report that parsing failed after writing half of its records. A retry may succeed but send the same notification twice. In each case, the visible error is correct. The failure semantics are not. Failure semantics describe what the system promises about state, side effects, and subsequent operations when something goes wrong. Testing them means asking more than “did this fail?” This article shows how to identify those promises and turn them into focused tests.

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

Property-Based Testing for Behavioral Invariants

Example-based tests are excellent when you know the cases that matter. You choose an input, state the expected result, and protect that behavior from regression. The weakness is also clear: the test checks only the examples you thought to write. Some defects hide between those examples. A parser works for ordinary names but fails on an empty segment. A range-normalization function works for the three values in the test file but produces an invalid range for an unusual ordering. A serializer handles familiar records but loses information for one combination of optional fields.

Software Engineering 07 Sep 2026 9 min read

Modeling Special Cases as Explicit Behavior

A system often starts with one ordinary case and one exception. A customer has a normal pricing plan unless the account is a guest. A shipment has a delivery date unless it is pickup-only. A report has an owner unless it is generated by the system. The first exception is usually harmless. The problem appears when the same check spreads through the code. Every caller asks whether it has the exceptional case before deciding what to do. Adding a new operation then means finding another place where the condition must be repeated.

Software Engineering 07 Sep 2026 9 min read

Managing Feature Flags as Temporary Code

A feature flag can make a risky change easier to release. Instead of making deployment and exposure happen at the same moment, the team can deploy code while keeping new behavior disabled, enable it for a limited audience, observe the result, and turn it off without rebuilding the application. That flexibility has a cost. Every flag introduces another condition that can affect behavior. If old flags remain indefinitely, developers must reason about combinations that no longer serve a useful purpose.

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

Evolving Data Shapes with Expand and Contract

A data change can look trivial in code and still be dangerous in a running system. Renaming a field, splitting one value into two, or changing a message shape may require several application versions, background jobs, and consumers to coexist while the change is in progress. The risky assumption is that the whole system changes at once. In practice, deployments take time, workers may finish old jobs after new code is live, and independently deployed consumers may upgrade later. If one release removes the old shape while something still depends on it, a locally correct change becomes a system failure.