ESSAY · Software Craft · Systems Thinking

Boundaries Make Software Clearer

Good boundaries reduce the number of things we must understand at one time.

· 4 MIN READ

Tangled charcoal lines become orderly after crossing a narrow terracotta boundary on paper.

Software can be difficult for two very different reasons. The problem itself may be difficult, or the system may hide where one responsibility ends and another begins. We cannot always simplify the problem. We can usually make its boundaries clearer.

I think of a boundary as an agreement. One part offers a small set of useful behavior. Other parts depend on that behavior without needing to know every internal detail. The boundary can be a function, a module, a command, a data format, or even a rule about where certain code is allowed to live.

The exact form is less important than the clarity of the agreement.

Why unclear boundaries create cost

When a module has no clear edge, every change becomes a search problem. We must discover who reads its data, who copies its rules, and who depends on an internal detail that was never meant to be public.

This cost grows quietly. The first shortcut may save an hour. The tenth change may require reading half the repository before we feel safe.

Unclear boundaries also make testing less useful. A test can confirm one function, but the real behavior may be spread across page code, configuration, and a second helper. A passing test then gives less confidence than it appears to give.

The problem is not simply “too much coupling.” Some coupling is necessary. A system is made of relationships. The useful question is whether those relationships are deliberate and visible.

Start with responsibility

Before I split code, I try to describe the responsibility in one sentence. A strong sentence uses a clear verb and a clear object.

“Resolve public content relationships” is more useful than “handle content.” “Exchange an OAuth code for a token” is more useful than “manage authentication.” The stronger sentence gives the module a reason to exist and suggests what should remain outside it.

If the sentence needs several unrelated verbs, the module may contain more than one responsibility. If the sentence is vague, the design may still be vague.

This is not a strict rule about file length. A large module can still have one deep responsibility. A small module can still be confusing if it only moves complexity into its callers.

Keep policy close to the decision

Many systems repeat the same policy in several surfaces. Visibility is a simple example. A content record may appear in a route, index, feed, search document, sitemap, or related-content list.

If every surface decides what “public” means, they can disagree. A better boundary gives one module ownership of that decision and lets the surfaces ask for an already filtered result.

This does not mean building one enormous utility file. It means giving the rule a named home. The public interface can remain small even when the internal work is detailed.

A boundary should hide work, not meaning

Abstraction is useful when it hides decisions that callers should not repeat. It is harmful when it hides the meaning of the operation.

A function called processData may hide a lot of work, but it tells the caller almost nothing. A function called eligibleForFeed carries a domain decision in its name. Its implementation can change while the question remains stable.

Good names do not solve architecture, but they reveal whether the architecture has a clear idea.

Test the agreement

A useful boundary has behavior that can be tested through its public interface. We should be able to ask what happens with a valid input, an invalid input, a missing relationship, or a hidden record without reaching into private implementation details.

These tests protect more than code. They protect the agreement between modules.

When a refactor breaks many tests that inspect internal steps, the tests may be coupled to the wrong side of the boundary. When a major behavior change breaks no test, the public agreement may not be covered well enough.

Boundaries are not walls

It is possible to over-separate a system. Too many tiny layers can turn one clear operation into a journey through many files. A boundary earns its place when it hides meaningful complexity, protects a rule, or allows change to happen in one area without surprising another.

I do not aim for the highest number of modules. I aim for the smallest set of responsibilities that makes the system easier to explain.

A simple review habit

When I review a design, I ask three questions:

  1. What decision does this part own?
  2. What does another part need to know about it?
  3. What knowledge would be dangerous to copy elsewhere?

The answers often show where the boundary should move.

Clear boundaries do not remove complexity. They place complexity where it can be understood. That is often the difference between a system that merely works today and one that can be changed with care tomorrow.

PUBLIC SEARCH

Find a path

Start typing to search the public index.