Two Wrongs

Default To Large Modules

Default To Large Modules

When you design a system of decent size, whether it’s software or something else, you are going to have to decompose it into subsystems, or modules.11 A module is a somewhat independent component that does something on its own, and collaborates or interacts with other modules to make the larger system perform its desired function. In terms of software design, we usually also think of modules as information-hiding and proving a public interface. In other words, to use a module, you shouldn’t have to know much about how it works internally. What you do need to know is how it’s intended to be used. Aside from terminology differences, most people would agree.

The $10,000 question is: when you start to design a system and decompose it into modules, should you

  1. Decompose the system into a few, larger modules, or
  2. Decompose the system into many, smaller modules?

One poignant follow-up question might be, “Which mistake is easier to undo later?” In other words, if you decompose the system into too few large modules, is that an easier mistake to deal with than decomposing it into too many, smaller modules?

A different way to phrase the same question is,

Is it easier to join together two modules that were decomposed too far, or to split up one module that wasn’t decomposed far enough?

My instict would be to that it’s easier to join together two modules than split one up. I’ve had to try to tease apart big hairy interconnected modules before and it wasn’t any fun. But something in the back of my mind wasn’t content with that answer.

It took a few months of thinking about it, but finally I realised why the instinctive answer is wrong.

Splitting Modules

When we want to split modules up, we have the problem of two logical modules A′ and A″ being tied up in one physical module A.

modularity-decomp-split-01.png

The difficulty in splitting them apart is that A′ expects access to the internals of A″ and similarly, A″ reaches into the internals of A′.

modularity-decomp-split-02.png

Which means we have to spend effort finding out how A′ and A″ can talk to each other sensibly, through a cohesive and invariant-preserving interface, rather than directly deal with each other’s privates..

modularity-decomp-split-03.png

Joining Modules

But that’s also what happens when you join modules together! Then we have the physical modules B′ and B″ that should be merged into a logical module B.

modularity-decomp-join-01.png

This might at first seem trivial. The difficulty lies in that the sibling module C might expect to deal with the external interfaces of B′ and B″.

modularity-decomp-join-02.png

These interfaces will become internal to B.

modularity-decomp-join-03.png

And now we’re back at the same situation as when we tried to split a module apart.

Comparison

At this point, it might seem like a wash: the problems encountered when splitting and joining modules are the same, so both mistakes are equally hard to fix.

However that’s missing one more piece of the puzzle.

When we need to break a big module A apart, we only have to deal with A′ and A″ trying to access the internals of each other.

When two smaller modules B′ and B″ need to be joined, there’s a potentially countless number of collaborators C, D, E, F, etc. that expects to interface with the internals of B.

modularity-decomp-join-04.png

In particular, if you choose as a matter of rule to err the side of many small modules, this is going to be the case that you find lots of collaborators wanting to touch the internals of your joined modules.

Conclusion

This is one of the reasons I’m a proponent of decomposing a system into a design with as few modules as possible as I start out, and discovering what additional module boundaries exist as I go. That mistake is simply less expensive.

Another reason to start with larger modules is that if you design fewer interactions, it’s less opportunity to get interactions wrong. And interactions are where you find the really hairy bugs in complex systems.