Unary call sites and intention-revealing interfaces
One of the features I love most about my IDE is the button "Find Usages". It is invaluable when improving a legacy code base. When used on a class it will show you where this class is used (as a parameter type, in an import statement, etc.). When used on a method, it will show you where this method gets called. Users of a method are often called "clients", but when we use "Find Usages", we might as well use the more generic term "call sites".
I first saw that term used by Michael Feathers in his blog post "Unary Call Sites and Architecture". He proposes it as a way of analyzing code quality and architectural style. When a method is used in only one place, it is said to have a "unary call site". Michael says about these unary call sites:
There’s a code base developed by a friend that is 45% methods with single call sites. He and his team are very zealous about their design so I’m not surprised and I am going to investigate further.
I'm curious where his investigations are leading to, since I often come to the same conclusion: when methods are made with a single purpose, they tend to be better methods. Some reasons for this are:
- The method name can be very specific; it will clearly describe its use case. The reader of the code will have no trouble understanding how its used, and what the implications of calling it are.
- The method won't need to tailor a diverse range of clients, so it won't have a lot of code and branches dealing with exotic scenarios, edge cases, etc.
Reusing methods for different use cases leads to everything that's forbidden by clean code literature. For example, adding flags to a method to allow different usages:
Have a function that gets used in about 100 places that you need to behave slightly differently for some new code? Add an optional flag. pic.twitter.com/YzWVNErQlU— Legacy Coder (@legacy_coder) June 28, 2017
Adventures in legacy code
It's only appropriate to quote Twitter's @legacy_coder here, since legacy projects tend to have big issues with call sites. I must be honest with you: this includes the legacy projects created today. Older projects may be worse though. Maintainers of legacy projects tend to reuse and adapt existing methods. This allows them to do "minimal" work in order to fix a bug or add a small feature. But new projects have the same issue. Developers will create all these generic and reusable sub-frameworks for their convenience. This code will have multiple call sites, which will make it harder to adopt the code to new requirements. (Last week a former co-worker showed me a nice example of such overly generic code I wrote a couple of years ago...)
What's so bad about multiple call sites? Well, as always, the Stable Dependencies Principle kicks in. Something that's used (i.e. depended upon) in many places, should be stable (i.e. it should very rarely change). The principle works in two ways:
- We don't want it to change (since that would break the code in all the call sites).
- It can't easily change (since it would cost us a lot of time to update the client code in all the call sites).
If we start reusing methods, they will over time become ever more difficult to change. When the point has come that we are terribly annoyed by the bad quality of these methods, it will be almost impossible to do anything about it. I think you will recognize this pain, when you want to get rid of that ugly table gateway or utility class and you find out that it has 100+ call sites...
Reducing the number of call sites
What can you do to reduce the number of call sites for a method? There are several techniques available.
Interfaces tend to be more stable than classes, mainly because they are more abstract than classes. So if a method has many clients, you might start by introducing an interface. You can either let the original class implement the new interface, or you can create a new class which wraps the original class. In other words, you either adapt the class, or adopt the class. An example related to previous discussions on this blog about
Truncated by Planet PHP, read more at the original (another 5316 bytes)