Skip to content

Archive

Software Design

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

Modeling Workflows with Explicit State Transitions

Many business objects have a lifecycle. An order may be created, approved, fulfilled, or cancelled. A support ticket may be open, assigned, resolved, or reopened. Problems begin when those lifecycle rules are represented only by a status field and scattered if statements. As the system grows, different code paths can start disagreeing about which changes are legal. One handler allows a cancelled order to be approved, another silently ignores the request, and a third checks a different set of statuses. The status values are visible, but the rules connecting them are not.

Python 03 Sep 2026 9 min read

Model Domain Constants Safely with Python enum

Strings and integers are convenient ways to represent states, modes, result codes, and permissions. They are also easy to mistype, mix with unrelated values, or pass through an API without making their meaning obvious. Python’s enum module lets a program give those values names and a controlled set of members. The benefit is not simply replacing constants with a class. A well-chosen enumeration defines the domain boundary: which values exist, how they compare, whether integer compatibility is intentional, and whether values may be combined.

Software Engineering 03 Sep 2026 9 min read

Hide Design Decisions Behind Stable Interfaces

A module can have a small public API and still be difficult to change. The problem appears when callers know details they do not actually need: a file layout, a cache key format, a third-party response shape, a particular algorithm, or the order of internal steps. Once those details escape, changing an implementation becomes a multi-module change. Code that should have been independent must now move together. Information hiding is a design principle for preventing that spread. The idea is simple: identify decisions that are likely to change, keep those decisions inside one boundary, and expose an interface based on what callers need rather than how the work is currently done.

Software Engineering 03 Sep 2026 10 min read

Directing Dependencies Toward Stable Code

A dependency can look harmless when it is introduced. A business rule calls a payment SDK directly, a reporting module knows the exact storage format, or an order workflow imports a concrete notification client. Each choice may save a small amount of code today. The cost appears later. When an external library, storage mechanism, or delivery channel changes, code that represents important business behaviour must change with it. Dependency direction is a way to reduce that coupling. The central idea is simple: code that expresses important, relatively stable policy should not have to know the details that are likely to change around it.

Software Engineering 03 Sep 2026 9 min read

Designing Modules Around Cohesion and Coupling

A codebase can be split into many files and still be difficult to change. A small feature may require edits in several modules, tests may need large fixtures, and one internal change may unexpectedly break distant code. The problem is often not the number of modules. It is where their boundaries are drawn. Two ideas are especially useful when evaluating those boundaries: cohesion and coupling. Cohesion asks whether the responsibilities inside a module belong together. Coupling asks how much one module depends on the details of another. Used together, they provide a practical way to decide where code should live and which dependencies deserve attention.

Software Engineering 03 Sep 2026 8 min read

Designing Invariants That Make Invalid States Hard to Represent

Many software defects are not caused by complicated algorithms. They happen because the program reaches a state that should never have been possible: an order has a negative quantity, a completed job has no completion time, or a configuration contains two options that cannot be enabled together. An invariant is a condition that must remain true for a particular object, module, or operation to be valid. Designing around invariants turns assumptions into enforceable rules. The result is usually less defensive code, clearer interfaces, and failures that occur closer to their cause.

Software Engineering 03 Sep 2026 12 min read

Designing for Localized Change

A small requirement can produce a surprisingly large code change. Suppose a product changes the rule for displaying customer names. The new rule sounds simple: show the preferred name when one exists, otherwise show the legal name. Yet implementing it requires edits in an API response mapper, an email formatter, an audit message, a report generator, and three tests that each reconstruct the same choice. The problem is not that five files changed. Some changes legitimately cross many files. The warning sign is that one decision had to be rediscovered and edited in several places.

Software Engineering 03 Sep 2026 8 min read

Controlling Mutation with Defensive Copies

A class can expose a small, carefully designed interface and still lose control of its own state. The problem appears when the class stores a mutable value that other code can also modify. Imagine an order object that accepts a list of line items. The constructor validates the list, calculates a total, and assumes the items will now change only through the order’s methods. If the caller still holds the same list, that assumption is false. The caller can modify the list directly, bypassing validation and leaving the order’s cached total inconsistent with its items.

Software Engineering 03 Sep 2026 9 min read

Choosing Composition Over Inheritance for Behavior Reuse

Reusing code can create a dependency that lasts much longer than the code being reused. A common example is inheritance: a new class extends an existing class because it needs some of its behavior. The first change is convenient, but later the subclass may inherit assumptions, state, and lifecycle rules that it never wanted. Composition offers a different relationship. Instead of saying that one type is a specialized form of another, an object receives or owns another object that provides a capability. The behavior can then be replaced without changing the object’s identity.

Python 02 Sep 2026 8 min read

Practical Function Specialization in Python with functools.partial

Functions often expose more parameters than a particular caller needs to choose. A parser may always use the same base, a callback may need access to one application object, or a formatting function may use a fixed prefix throughout one subsystem. Python’s functools.partial() can turn such a general callable into a more focused callable by binding some arguments in advance. The result remains callable, so it can be passed to APIs that expect a function-like object without introducing a wrapper function solely to carry configuration.

Software Engineering 02 Sep 2026 7 min read

Managing Dependencies as Explicit Boundaries

Third-party libraries save engineering time, but every dependency also adds a contract that your software must live with. That contract includes more than function signatures. It can include configuration formats, exceptions, lifecycle rules, performance characteristics, release policies, and assumptions that spread through application code. Dependency management is therefore partly a software design problem. The goal is not to avoid dependencies. It is to make important dependencies explicit, contained, and inexpensive to change.

Python 01 Sep 2026 5 min read

Python Protocols and Structural Subtyping for Flexible APIs

Python code often depends on behavior rather than a specific class hierarchy. A function may only need an object with a send() method, a read() method, or a pair of repository operations. typing.Protocol lets type checkers describe those behavioral requirements directly. A class satisfies a protocol by having compatible members; it does not need to inherit from the protocol. This is structural subtyping: “if it has the required shape, it can be used here.”

Python 01 Sep 2026 3 min read

Design Value Objects with Python Dataclasses

Python dictionaries are convenient for passing a few values around, but they become fragile when a value has invariants or behavior that deserves a name. dataclasses can express small domain value objects without repetitive constructors and representation methods. Start with domain meaning A price represented as a dictionary is easy to misuse: price = {"amount": 1299, "currency": "USD"} Any caller can omit a key or accidentally mix cents and dollars. A value object makes the contract explicit: