Dependencies Show Cleanliness

Getting your code organized is challenging. But when it feels as clean as your room after organizing it Saturday morning, it'll feel worth it, and you can go watch cartoons. A key to clean code is clean dependencies.

Modules and dependencies

A module knows stuff and does stuff. Getting the contents of a module right is important, of course. Does everything in the module relate? Is the public API appropriate?

But not all the work of an application will be done a single module. There will be other modules, abstracting away parts of the work to be done. Importing a module B into a module A will create an A to B dependency (ie, A -> B directionality).

Dependency direction

If we're drawing boxes with arrows to boxes in a diagram of our modules, we want arrows to flow in one direction. Otherwise, it's a web, a mess.

When flowing in a direction, I mean from certain types or categories of modules to others.

One of the most important categories is specificity, and dependencies should flow from most specific to most general.

In Bob Martin terms, this directionality is sometimes called the onion architecture, represented by concentric circles. In this architecture, there's a dependency rule: "Nothing in an inner circle can know anything at all about something in an outer circle."

That means that the innermost circle is most general. The outermost is most specific. The arrows flow inward.

A similar Martinism is the SOLID principle of "dependency inversion". In part, it means that low-level modules (specific) rely on high-level modules (general), but not vice versa.

When organizing a codebase, the layering can feel reversed, but nesting is also useful. File-based routing and nested routes can feel natural here. Here, the deepest subdirectory is most specific, and then highest level directory is most general.

To start, prefer to keep code specific, local or as a peer. As code needs to be reused, and you want to pay that cost of reuse, move it higher, more general. And as you go, make sure to keep the direction of dependencies uni-directional.

As a final concrete example of code categories, let's take a frontend application, where the depedencies will point up to the most general at the top. Broadly, the categories could be:

  • Data utils (most general)

  • Common UI

  • Data source integration

  • Data model

  • Data to view mapping

  • A ViewModel

  • A page, its UI (most specific)

What other tips for organizing your dependencies do you have? What kind of cost and benefit from your approach have you observed?