In my last two posts, I introduced Uncle Bob Martin’s principles for dividing classes into packages.
Part 1 talks about three principles related to package cohesion.
Part 2 talks about three principles related to package coupling.
Let’s discuss the implications of applying these principles.
Why Should I Care?
As with any development principles, you get to decide for yourself whether you want to follow them. If you think they’ll make your code and your systems better, then follow them. If not, don’t.
Reuse-Release Equivalence Principle (REP): While the REP isn’t that well known by name, most of the developers I run into seem to follow it. There are many tools available for releasing packages, versioning them, and specifying dependencies. There’s Semantic Versioning to help us know when it should be safe to update a dependency. These are all good things.
Common Reuse Principle (CRP): I have used code (and written some myself) that violates the CRP and it almost always bothers me. I hate depending on a huge package just to use one small part of it. If it’s my own code, I use this as a cue to refactor; but if it’s someone else’s code, I start looking for alternatives.
Common Closure Principle (CCP): I’ve felt the pain of not following the CCP. Having to make changes in all of the layers of a multi-tier application is not that much fun, especially in a compiled language. Almost every web application of size violates this principle in its core architecture, because the application is divided up based on implementation language or where the code ultimately runs, not based on what it does. I’m not sure that there’s a better way to do things, but it’s worth thinking about.
Acyclic Dependencies Principle (ADP): I’m a firm believer in the ADP. To me, cyclic dependencies are a sign that I don’t yet understand the structure of my application, and that I don’t have a good sense of the layers. I’ll sometimes create dependency cycles in the small, where several classes have an interdependent or symbiotic relationship. But in the large, interdependent packages are more trouble than they’re worth.
Stable Depencencies Principle (SDP)/Stable Abstractions Principle (SAP): I’m pretty sure that the SDP/SAP are valuable, but I’m not sure how to apply them in all situations yet. One of the systems I work on has several lower-level utility packages that are used by almost every other package. When we change one of these low-level packages, we have to “rebuild the world.” This takes time, and also generates new releases of some of our applications for no good reason. I need to keep these two principles in mind more when I’m working to see if I can apply them better in my day-to-day work.
Think about packages like ActiveSupport, jQuery, and Underscore.js. These are all concrete utility packages that continue to evolve and that are depended on by a lot of projects. Writing and using packages like this violates both SDP and SAP, and yet I’m not sure there’s a better way to do things. I’d love to hear some ideas about that.
How Do I Apply The Principles?
Applying any principle mindlessly is not likely to work out well. This is true of these packaging principles as well. There are sometimes-conflicting forces at play and we need to make some tradeoffs.
If you take the CRP and CCP to their extremes, you end up with numerous very small packages. The REP then adds overhead to managing and maintaining so many small packages, because you have to separately release, version, and track each one.
On the other hand, CRP and CCP help with the ADP, because classes that are interdependent will generally want to be reused together and will have to change together. If you split those classes into different packages in violation of CRP and CCP, you’ll likely end up with ADP violations as well. So all three principles drive you to either group the interdependent classes into the same package or to break the interdependencies.
I don’t recommend breaking an application up into packages initially. Early on, you’re not really sure what goes together and how things should be organized. Having everything in one place makes it much easier to move the pieces around and hook them together in different ways. Separate packages can be an obstacle to refactoring.
Once you start feeling some pain or you start wanting to re-use some of your code in a different context, then it’s time to start splitting out packages. You can use the packaging principles to help you figure out where to draw the lines.
Don’t wait too long to start splitting out packages, though. Otherwise, the refactoring exercise will take more time and be more painful. Applying the SOLID principles principles as you go will make it easier, as these lower-level principles will keep your class relationships structured in a way that makes it easier to split out packages later.
How Do I Restructure My Code?
Once I’ve decided I want to apply these principles and break my system into packages, how do I do that? There are more techniques than I can write about in a reasonable amount of time, but I’ll outline a few ideas here.
As I mentioned above, the SOLID principles help a lot.
By following SOLID, you will ideally have classes that aren’t highly coupled with each other. This gives nice seams for splitting things into packages.
You can use the Dependency Inversion Principle (DIP) to tease apart interdependent classes. By extracting an interface or explicit role, you can have one package that defines and depends on the interface, and have another package that implements the interface or defines a role-player.
In languages where classes can be “re-opened”, “extended”, or “monkey-patched”, you can have a class that lives in one package, but add one or two methods to it in another package.
For example, I have packages that contain proxy classes for interacting with various physical devices. I have another package that is about monitoring the status of these devices. Each device has its own kind of status monitor.
In the status monitoring package, I extend each device class with a method that returns the correct status monitor for the device. This way, I can make use of polymorphism by asking a device for its status monitor, but I don’t have any dependency cycles because that method lives in the same package as the status monitoring classes.
If I have other code that doesn’t care about status monitoring, I can still use the device proxies without pulling in any unwanted dependencies.
Think about your system in terms of vertical slices of functionality, rather than horizontal slices of infrastructure. Agile methodologies have been saying to implement your systems this way for more than 15 years, because you can deliver value much faster if you don’t spend the first six weeks implementing the persistence layer of your application.
What would happen if you based your package structure on vertical slices instead of on infrastructure? I’m not sure, because I haven’t tried it yet. But it’s an interesting thought. If you’ve tried it, I’d love to hear how it worked out.
I hope this review of Uncle Bob’s packaging principles has been helpful to you. I recommend that you at least consider them as you work on your systems and use them to produce better code.
* Agile Software Development: Principles, Patterns, and Practices: This is Uncle Bob’s book that introduces all of these principles. All of the quoted principle summaries in the first two posts are taken from this book.
* Principles of OOD: Blog post by Uncle Bob briefly describing the principles.
* Object Mentor Articles: Numerous articles by Uncle Bob and others from Object Mentor on many topics. I learned a lot by reading these articles when they were written, and they have significantly shaped how I think about software today.