Principles: Robert C. Martin
Post: Tim Ottinger and Jeff Langr
These three principles, devised by Uncle Bob, describe how to structure relationships between packages (arbitrary collections of classes). A related (forthcoming) card, Principles of Package Design, talks about the forces that bear on how to optimally group classes.
There arise occasional shouts of "But that was in C++!" Yet we find that these principles can apply also to dynamic languages, where abstraction works rather differently than in interface-declaring languages.
- Acyclic-Dependencies Principle (ADP) - You stay late to finish up some work, but come in the next morning only to find that your code is broken--someone else stayed later and checked in changes that make your code very unhappy. Fixing your code breaks a third developer's code, and his fixes break yours again. This "morning-after syndrome" can be an unending nightmare of changes breaking other code, which in turn must be changed, which in turn...
Cycles begin to make all packages dependent upon all other packages in the system. They dramatically increase build times to the point of pain (something one of us lives with daily on the C++ project he's working).
Some languages, like C#, have facilities to prevent circular dependencies among packages. But even modern, dynamic languages like Python can suffer in surprising ways from round-trip dependencies.
Dependency cycles among modules are a very serious problem, slowing development and wrecking deadlines. Note that mutual dependencies among classes are usually a problem if the classes are split across modules/packages/assemblies.
- Stable-Dependencies Principle (SDP) is based on the understanding that code has volatility, and that changes ripple through a system along dependency lines. From the point-of-view of volatility, dependency is transitive.
If the majority of the system's classes are dependent upon a volatile bit of code, then we can expect a high frequency of system breakage and rippling changes. If, instead, a system depends on nonvolatile code, then breakage should not ripple through the system very often at all.
Some modules should change frequently, and should be volatile. Other modules are essential core abstractions and should not change very often.
Therefore the package dependency graph should flow from instable packages (packages that are easy to change) to stable or "responsible" packages (packages that are hard to change). In a sense, this is the dependency inversion principle applied to packages: You should depend only upon things that are unlikely to change.
- Stable-Abstractions Principle (SAP) - Per Uncle Bob, this principle sets up a relationship between stability and abstraction. A stable package should be as abstract as possible, thus ensuring that its "stability does not prevent it from being extended." In contrast, an instable (not responsible) package should contain lots of concrete classes whose details can be easily changed.
This further reflects the Stable Dependencies principle, with the added observation that the reason for interfaces is to give safe, stable dependencies to clients of an abstraction. This drives us to the understanding that we should depend on the parts of the system built specifically to provide freedom from volatile bits (implementations).
In dynamic languages, SAP is a harder metric to automate. Clearly the principle still applies. There will be classes whose purpose is to provide a stable interface and other classes that provide easily-changeable behaviors. The only problem is that not having declared interfaces makes it harder to automate the process of assessing abstractness.
A "benefit" of working in C++ is that poor package coupling choices are all the more noticeable. Build times of several hours are not uncommon, yet these wasteful cycles can be quickly brought down with a bit of appropriate package reorganization. Without dramatically increasing build times, developers in languages like Java might not notice as quickly the cause of their morning-after sicknesses.
So how do we figure out where the problems are and what to do about them? The flip side of the card provides metrics that you can use as the basis for graphing the relationships between packages. Such a graph can point out nonsensical packages, such as maximally abstract packages that no other packages depend upon, or maximally concrete packages that all other packages depend upon. Uncle Bob's stability article provides some detail on how to put this graph together (but there are also tools that can help!).