SAGE: Early Warning System for Design Mistakes
SAGE is a design quality assurance technique that can keep your work from becoming more difficult and complicated than it should have been. Looking at your software through the lens of SAGE quadrants can keep you safe from the most common setbacks and slow downs in software projects.
Design mistakes are subtle, and not immediately visible. Once a design mistake has been hardened and entrenched by afference, it’s often impossible to correct. SAGE makes design mistakes visible while it’s not yet too late to fix them.
What is SAGE
SAGE is an acronym that stands for Specialization, Afference, Generalization, and Efference. Each of these four are measurable software design qualities.
The four SAGE qualities can be visualized as quadrants, and used as a way to determine whether your design will be a magnet for trouble.
Afference and efference are opposites of each other, and generalization and specialization are opposite of each other.
Afference and efference form the coupling dimension, and generalization and specialization form the conceptual dimension
There are two quadrants that are harmful and two quadrants that are safe. If your code finds its way into the harmful quadrants, your productivity will start to suffer. The longer you leave these problems unaddressed, the more they radiate outward through the lines of coupling to adjacent parts of the implementation.
The happy quadrants have qualities that enable us to work without unintended consequences and unnecessary complications.
The unhappy quadrants feature the counter-productivity that radiates outward, expands, and compounds exponentially.
An Example: Model, View, Controller
Controllers are close to the user interface. So, they’re right next to the user. They represent specific things that a user wants to get done.
They’re the scenarios. They’re use cases. They correlate to features like “log in”, and “post a message”, and “make payment”. Controllers are the specialized parts.
Models are general. They’re used by numerous other classes, often controllers. Those other classes may have diverse goals and responsibilities, but they all use the model in the exact same way. A model is generally-applicable to a number of scenarios. Those scenarios have far more diverse and variant behaviors than the model does.
Nothing really makes calls to a controller except for the app server or web app framework infrastructure. There’s a front controller or router sitting in front of a controller that drives the controller. But there isn’t really much in the way of afference, and the afference that does exist is highly standardized.
Controllers are efferent. They call out to and coordinate other parts of the system, including models.
Models receive calls from potentially many controllers or other parts of the system. A model may have ten different controllers calling it. The model is general-purpose. It can be used in diverse scenarios. A controller represents only one of those diverse scenarios, and serves that single scenario.
The general-purpose code goes into the model: the model is general. The model is used by lots of other things: the model is afferent.
A good SAGE assessment of a healthy implementation built on MVC patterns will have the right things in the right places:
The General/Special Rule: Everything in Its Place
The underlying design rule is straight-forward: Put general-purpose things in general-purpose places, and put special-purpose things in special-purpose places.
Never get this mixed up and never make exceptions. If you find yourself between a rock and a hard place and tempted to make an exception then there’s a problem elsewhere with the design. Find it and fix it. Don’t let it fester in your code. You’re already seeing the secondary effects of viral infection due to adjacency.
If, borrowing from the MVC example, you end up with scenario-specific controller logic in general-purpose model code, that reduces the cohesion of the model, which in turn attracts parasitic afference to that model, which will make it difficult-to-impossible to improve or to correct. Where ever you put afference attractors, make sure that you’re certain of that design. You won’t get very many opportunities to make corrections if you get it wrong.
Coupling Axis: Afferent to Efferent
Afferent coupling is inbound or inward coupling. Efferent coupling is outward coupling.
Afferent coupling is like the walls closing in in the garbage compactor in Star Wars. The more that a piece of software is used or called by other pieces of software, the less liberty or license it has to be able change; either to be corrected for some defect or to be improved for some new realization. Every change means making changes to all the other parts of software. And those changes can have cascading effects along their lines of coupling.
Efferent coupling is the force exerted outward from some piece of software. A call made to another class is an outward expression from the perspective of the software making the call.
An afferent piece of software is the receiver of coupling. An efferent is the sender of coupling. The caller is efferent. The callee is afferent.
Some unit of software can be highly afferent or highly efferent or something in the middle with equal parts afference and efference. Those things in the middle ground might be naturally challenging to work on. Those challenges can be amplified beyond reach if coupling is out of alignment.
Conceptual Axis: General to Special
General and special apply to software in a couple of ways. There’s the generalization and specialization of abstraction, sub-classing, polymorphism, role interfaces, and such. And there’s the notion of something being general-purpose or special-purpose. Both of these dimensions are reflected by the conceptual axis.
A special-purpose piece of software carries out some particular usage scenario. It typically coordinates an assortment of other objects or things to get its job done.
General-purpose things are often also generalizations, aka: abstractions. Special-purpose things are often also concrete, like subclasses of some abstract adapter.
Things that are highly afferent and general are natural and more resilient to change. Things that are highly efferent and specific are also natural and more resilient to change.
Building upon design mistakes that are afferent will project the mistakes into the software that is adjacent to the mistake. And because software is not limited by physical space, a design mistake can infect an infinite number of other pieces of software that makes calls to the flawed pieces.
When the right coupling is in the right place, the adjacent software improves sympathetically, and doesn’t radiate sickness outward to other parts of the implementation. It stays healthy and easy to work with.
If you mix up the general and the special, and put special-purpose code in a general-purpose place in the design, or put general-purpose code in a special-purpose place, the result will be very unnatural coupling and design flaws that cascade out along those unnatural lines of coupling.
The same can be said for putting general and abstract code in a specialized, concrete place, and putting specialized code in a generalization.
Happy Quadrant: Afferent/General
This quadrant features inbound calls that limit the receiver’s ability to change, and either general-purpose or abstract, generalized code that should not need to change.
A highly-afferent piece of software is difficult to change. The pieces of your software that are highly-afferent should not be expected to change. These pieces are stable. Afference has less impact when it is on pieces of software that are stable.
Happy Quadrant: Efferent/Special
Efferent pieces of software are typically coordinators of other pieces of software. They make more outbound calls to other things that they coordinate than they receive calls from other things.
Because they make more calls than they receive, they’re easier to change. They have low afference.
Unhappy Quadrant: Afferent/Special
This quadrant features inbound calls that limit the ability to make a change, but also features scenario-specific logic which will likely cause the code in this quadrant to need to change.
Code in this quadrant is usually scenario-specific code. Scenario code is closer to particular user, usage, and business requirements. These things tend to be more capricious. They tend to need to change more often.
Capricious things are inherently unstable. Afference shouldn’t be combined with instability.
Unhappy Quadrant: Efferent/General
A piece of software that is both general and efferent, would make calls out to other pieces of software, but like a controller, would not receive calls from other pieces of software.
However, it would also be general-purpose or abstract. It would be coordinating a number of other pieces of software, and it would be a stable, unchanging implementation of such coordination of other things.
The absence of afference on a general-purpose, multi-use piece of software is a bit of a contradiction. It’s certainly possible to have parts of your design that fit this quadrant, but they’re more uncommon and somewhat odd.
SAGE can help you immediately spot the real trouble spots and fix them immediately. And the more you use it and apply it, the more automatic and instinctual it will be.
SAGE is a guideline. You can make hard measures of coupling and generality and abstractness, but you still need to be able to make a judgment call about what it means to your design. Sometimes your decisions will be straightforward, and sometimes it’s not going to be so clear, and you’ll need to invest a little more thought into it.
Understanding the interplay of coupling and generalization is absolutely critical to writing code that doesn’t drag you into its own hell. It’s also a good entry point into the ecosystem of design principles and design qualities that will only make your job easier and your work more rewarding.
SAGE is effectively a restatement of Bob Martin's Distance from the Main Sequence metric.