May 3, 2017
20 Dev Metrics - 13. Swappability of DependenciesThe 13th in my series 20 Dev Metrics is Swappability of Dependencies.
Swappability lies at the core of object oriented and component-based design, and so we should take a keen interest on how easy it would be to replace an object's collaborators without it having to change. For example, we might want to swap a data access object with a stub for testing, or swap a payment processing service when the customer is in a specific country.
Swappability as a general concept is pretty much universal, but differs in its implementation depending on the language. To make a dependency swappable in C++, we must do more than we would need to in, say, Ruby and other dynamically-typed languages.
I'll illustrate with a Java example.
Here we're depending directly on a static method of a class ImdbService to get information about a video the customer wants to rent. If we wanted to get that information from a different source (e.g., Amazon), there's no easy way to do it.
In our refactored design, we've made that dependency swappable by 3 steps:
1. We made the static method an instance method, so it can be overridden
2. We passed the instance into the constructor ("dependency injection"), so instantiation happens outside of Pricer. i.e., someone else decides what implementation to use
3. We extracted an interface for ultimate swappability ("dependency inversion"). Pricer can use any service that implements that interface.
In dynamically-typed languages, we may not need an interface - technically speaking - but many programmers get into the habit of creating classes with empty methods to represent an interface, mostly because it makes more sense than extending an implementation (e.g., is an AmazonVideoService really a kind of ImdbService?).
In C++, we would absolutely need an interface, as we can only readily override methods declared as virtual. And other languages like Java are somewhere in between.
Measuring swappability in Java would be a matter of analysing references to other objects and determining where those references are instantiated. If they're instantiated inside the client class, then they're not swappable. If they're passed in as a method parameter, they're swappable - but only if all of the methods used are overrideable. Hence, binding to a pure interface gives ultimate swappability. And, of course, if static methods are used, then that's zero swappability.
How I would I calculate swappability for a Java class?
I'd calculate swappability for each individual reference, and then divide the total for all of them by the maximum possible swappability.
If a reference is static, then it has 0% swappability.
If a reference isn't dependency-injected, it has 0% swappability.
If a reference is dependency-injected, it's swappability will depend on which of its methods are being used:
a. If a method used is abstract, that counts as 100% swappable
b. If a method used has an implementation, but is overrideable, that counts as partially swappable - 50%
c. If a method used cannot be overriden, that has 0% swappability.
For each reference, swappability is the average swappability of methods used. For the class as a whole, swappability is the average swappability of references. And at a package or system level, it's the average swappability across all of the classes
So, when Pricer uses ImdbInfo.fetchVideo(), it has zero swappability because it's a static reference. When Pricer uses a dependency-injected VideoInfoService.fetchVideo(), it has 100% swappability because that method is abstract.
You'll no doubt be delighted to learn that there are no automated tools for calculating this metric at present for any languages. So this is some tooling you would need to rig up yourself. For now, though, I find it a very useful conceptual tool for reasoning about swappability of dependencies.
A cruder approach would be to calculate what proportion of references are to interfaces, and from a tooling perspective this is much simpler, but arguably a bit of a blunt instrument... And very language-specific. For example, a field may be of an interface type, but if it's instantiated inside the constructor of that class, then it's not swappable.
Posted 3 years, 9 months ago on May 3, 2017