Skip to content

Archive

Code Quality

63 articles
Software Engineering 08 Sep 2026 8 min read

Replacing Repeated Absence Checks with the Null Object Pattern

Optional collaborators often begin innocently. A service may have a notifier when notifications are enabled, a metrics recorder when monitoring is configured, or an audit sink in environments that need auditing. The first absence check is easy to understand. The twentieth can make the real behavior harder to see. The Null Object pattern is one way to remove that repetition. Instead of representing “no collaborator” with a null-like value and asking every caller to handle it, provide an object that implements the same contract with behavior appropriate for absence.

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

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

Adding Behavior Safely with Sprout Methods

A small feature can become a risky change when it must be added inside a large method that has weak tests. The obvious edit may be only ten lines, but those lines become mixed with old branches, mutable state, database calls, or other behavior you do not fully understand. Testing the new rule then requires exercising the entire method. One useful response is a sprout method: put the new behavior in a new, focused method and make the existing code call it. The old method changes only enough to connect the new behavior. The new method can often be tested directly with much less setup.

Software Engineering 06 Sep 2026 7 min read

Using Guard Clauses to Keep Control Flow Flat

A function often starts simple and becomes difficult to read as conditions accumulate. One check wraps another, the main work moves several indentation levels to the right, and a developer must remember which conditions are still true while reading the code. A guard clause handles a case that should stop or divert the current operation near the point where that case becomes known. Instead of wrapping the normal path in another conditional, the function deals with the exceptional, invalid, or inapplicable case and exits that path early.

Software Engineering 06 Sep 2026 9 min read

Replacing Branching with Lookup Tables

A chain of conditionals is not automatically a design problem. Sometimes each branch expresses genuinely different behavior, and an if or switch is the clearest way to show it. But another kind of branching appears when the logic is identical in every branch and only a value changes. That distinction matters. If the program is repeatedly asking, “Which constant belongs to this case?”, the conditionals are acting as a hand-written lookup mechanism. A lookup table can make the relationship explicit: keys represent cases, values represent the data associated with them.

Software Engineering 06 Sep 2026 8 min read

Replacing Behavior Switches with Polymorphism

A switch statement is not a design problem by itself. When a program has a small, stable set of cases, one explicit conditional can be easier to read than a hierarchy of types. Trouble starts when the same type distinction controls behavior in several places. Adding one new case then means finding every switch that knows about that type. Missing one produces a system where the new case works in some operations but not others.

Software Engineering 06 Sep 2026 8 min read

Reducing Temporal Coupling in APIs

Some APIs look simple because each method is simple. The difficulty appears only when you try to use them: one method must run before another, initialization must happen at exactly the right time, and an innocent-looking call fails because an earlier step was missed. This kind of order dependency is called temporal coupling. Two operations are temporally coupled when their correctness depends on when or in what order they happen. Some ordering is inherent to the problem, but hidden ordering makes code harder to understand, test, and change.

Software Engineering 06 Sep 2026 8 min read

Grouping Related Parameters into a Parameter Object

A function can have individually reasonable parameters and still be difficult to use correctly. The problem often appears when several values travel together through many calls and only make sense as a group. Consider code that repeatedly passes startTime, endTime, and timezone. Each value has a clear type, but callers must remember their relationship: the end must not precede the start, and both timestamps are interpreted using the same timezone rule.

Software Engineering 05 Sep 2026 10 min read

Replacing Primitive Values with Domain Types

Many programs represent important concepts with ordinary strings, numbers, and booleans. That is convenient at first. A customer ID is a string, an email address is a string, and a quantity is an integer, so using those primitive types seems sufficient. The trouble starts when values that have different meanings share the same representation. A function can receive a product ID where it expected a customer ID. Validation rules spread across callers. A number that means cents can be confused with one that means whole currency units. The compiler or runtime may see perfectly valid primitives even though the program has made a domain mistake.

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

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 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 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 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 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.