2013-03-26

4. Group by function, not kind


Today I'd like to write a little bit about physical design. Physical design is all about placing the needed behavior in the right place, and not so much about objects and how they interact - a concept I first read about in "Large Scale C++ Software Design" by John Lakos. The functionality needed must reside somewhere, but it's not irrelevant where it ends up. Good physical design guidelines can be crucial for the long term success of a project.

Here's a very simplified abstract representation of the source-layout in a project I worked with a couple of years back:

myapp/dialogues 
myapp/shapes 
myapp/views 
myapp/caches 
myapp/preferences

For each new feature we added to the system, we wanted to add one or more shapes, one or more views, one or more caches, one or more preferences relating to the feature, and perhaps a handful of dialogues as well. So what was wrong with this?

It doesn't scale well with the size of the codebase. As long as we had a handful of features, this was ok. But the application grew fast - we were adding new features, and every new feature sliced through most if not all of the directories. In addition the team grew, and we collided with each others works more often. Build-times increased to an uncomfortable level (this was C++). At a point we wanted to separate things in libraries and we realized that we had a huge problem. Shapes depended on preferences, preferences depended on views, and views depended on shapes - all directories had dependencies to most of the other directories. Circular dependency galore. An entangled mess we spent a fair amount of time to try to tease apart.

The solution that materialized, was to group our files according to new features. So over time the directory structure morphed in to something that looked more like this:

myapp/jump/dialogues 
myapp/jump/shapes 
myapp/jump/views 
myapp/jump/caches 
myapp/jump/preferences 
myapp/slide/[dialogues, shapes, views, caches, preferences]
myapp/talk/[dialogues, shapes, views, caches, preferences] 
myapp/common/

...where the actions - verbs - represents different features or groups of somewhat tightly related functionality - features. When we started organizing the physical design of the application this way - by the feature rather than the type of the different software entities - we experienced several immediate benefits. If I worked with the jump-feature, for example, I would be pretty certain that my changes did not break anything relating to the slide-feature. This organization also made it easier to remove duplications by discovering and salvaging common functionality, and add that to separate utility libraries. In short, this physical design inspired loosely coupled designs. On the other hand, the original (and maybe more intuitive) physical design led us to make tightly coupled logical designs.

I use this as a rule of thumb. It's obviously not always true, but true often enough to make notice. If I fail to follow it, I notice that the natural evolution of general utilities that helps me continue to keep speed up while I'm adding new features, fails to manifest. I also experience that circular dependencies between packages is more prone to pop up. If I manage to group by function first, I find that keeping the codebase decoupled, clean and non-cyclic is much easier.

No comments:

Post a Comment