Zero-cost Future Proofing: Meaningful Namespaces

Photo of metal letters on a shelf with neon lights inside spelling out "SALE"
Photo by Claudio Schwarz / Unsplash

Sandi Metz famously said "duplication is far cheaper than the wrong abstraction," and my experience has been that following that advice has usually worked out well. You can't future-proof well until you know what you need to future-proof against. A trade-off that makes thing A easier will make thing B harder—and if you end up needing to do thing B more often than A, you'll regret the trade-off.

There are, however, a few proofs against future changes that I have never regretted. While they haven't always paid dividends down the road, they cost so little to do up-front, their trade-offs so minuscule, that they're almost always worthwhile. These are choices where either path takes almost the exact same amount of time.

Foremost among these choices is creating meaningful namespaces.

Namespacing, Meaningfully

Namespaces here are generic ways of grouping related code. In some languages, like C++ and PHP, namespaces are a first-class concept. In others, like Python and Go and Typescript, modules or packages may serve a similar purpose. In languages like C, identifiers might all share a common prefix, like json_ for json-c, that helps conceptually group them.

Meaningful namespaces are usually derived from some semantic or functional grouping. In some cases, functional grouping has had downsides in the long term that semantic grouping has not, but I'll get into those.

A semantic group is something like creating an invoices namespace to link together Invoice and LineItem and Payment types and methods, or a users namespace to hold User and Confirmation and PasswordResetRequest types. If you're familiar with Django "apps" or Flask blueprints, they encourage semantic namespaces.

A functional group is something like models/, views/, and controllers/. Rails apps are usually set up this way, following the model-view-controller (MVC) pattern. I also find it works well with Go microservices, using an "onion" layered pattern. (Most Django projects also follow this pattern within each app.)

What is the Trade-off?

There are two main benefits I've gotten from namespacing: requiring some upfront structural thought, and avoiding collisions.

Avoiding collisions is the simpler of the two: sometimes the words you want to use for one thing are the same as the words you used for something else. This is especially true with generic words like "state" or "status," "data," etc.

Requiring upfront structural thought doesn't sound immediately like a benefit—it sounds like a cost! I don't see it that way for two reasons:

  1. Understanding the structure of the problem is the work. Sometimes we make a rough draft on a whiteboard with boxes and arrows and sometimes in code with proofs-of-concepts. However we go about it, figuring out the structure of the data and the problem before we've built too much leads to less churn than discovering that structure as we go*.
  2. This is a balancing act: you can spend too much time on it. You don't need a high-fidelity plan of every file and what code will live in it. Understanding how the major entities and functional areas relate to each other is enough. There is no exact cut-off, and depending on your situation and constraints you may choose to err on the side of faster or more thorough. Unfortunately, this is one where intuition really is the payoff of experience.

The costs, on the other hand, usually take the form of slightly-longer identifiers or a couple of additional imports. In either case, good developer tools can bring those costs down to near zero.

From time to time, yes, you will be wrong and need to move things around, move something from one namespace to another, create a new namespace, or remove one. That will not be completely eliminated by upfront thinking. However, the overall reduction in churn outweighs theses potentially more-expensive refactors.