EliminateFuzzyModifiers
From APIDesign
Fuzzy access modifiers are source of all evil as described at ClarityOfAccessModifiers page. Good message is that we do not need them. Our APIs can be clear enough with use of following easy steps.
The examples on this page are slightly enhanced to those discussed Chapter 10, Cooperating with Other APIs, where this topic is deeply discussed too.
Contents |
Theory
Public Abstract
Imagine that there is a class with a single public abstract method like in this example:
does not exists: sidemeanings.PublicAbstract.Dirty
This indeed violates the clarity rules and in many cases API designers may wish to eliminate it. The change is simple. Just split the method into two:
does not exists: sidemeanings.PublicAbstract.Clean
Now both methods use single meaning access modifiers, so the so wanted clarity is achieved. Moreover the usage of such API is made complex. Compare the original snippet with classical modifier:
does not exists: sidemeanings.PublicAbstract.Dirty.test
With the same code rewritten to clean version with two methods. Everything remains the same, just instead of overriding the final method, one needs to override the protected abstract one:
does not exists: sidemeanings.PublicAbstract.Clean.test
Eliminating public abstract modifiers is easy, if found desirable.
Protected
protected methods are also said to have double meaning. They can be overriden, plus they can carry some implementation for subclasses to call just like in this example:
does not exists: sidemeanings.Protected.Dirty
Similarly like in previous case, this fuzziness can be eliminated by splitting the method into two:
does not exists: sidemeanings.Protected.Clean
The expressive power of such API remains unchanged. One can still rewrite code that is using the old version:
does not exists: sidemeanings.Protected.Dirty.test
into code written against new version quite easily:
does not exists: sidemeanings.Protected.Clean.test
Just instead of calling super.increment() you need to delegate to the protected final defaultImplementation. The rest of the code stays the same. It is so easy to clarify purpose of protected methods!
Public
The plain public modifier has in fact even more meanings. Just writing:
does not exists: sidemeanings.Public.Dirty
may actually mean three different things. In the process of cleaning the meaning we thus need to split the method in following way:
does not exists: sidemeanings.Public.Clean
Again this does not make any use more complex. The old test:
does not exists: sidemeanings.Public.Dirty.test
needs just two modifications - name of overriden method is different and instead of calling super implementation, we call the default one:
does not exists: sidemeanings.Public.Clean.test
Getting rid of multi-meaning public modifiers is easy too.
"Real World" Example
Let's now look how we can use the above suggestions on well known Arithmetica and Factorial example that started the whole Odyssey with access modifiers. However we are not going to reach end of our journey yet. We will have correct, but slightly ugly API. We'll fix that later.
All methods in Arithmetica class are public and as such we are advised to split each of them into three:
does not exists: design.sidemeanings.arith.sumTwo
Looks complex, doesn't it? Instead of one method three. However now we, as authors of the API, can at least see what the original meaning of our single public sumTwo method was. Is it important to understand this? Yes, it is. As soon as you are about to rewrite the sumRange method, you are immediately faced with an important dilemma:
does not exists: design.sidemeanings.arith.the.question
Do you want to open up to subclasses or do you want to call the default implementation? Both is possible, but each leads to different API! Only because we correctly split the public methods into fuzzyless replacement we could enlarge our horizons and notice this important question. If we decide to open up and call the sumAll method we allow the Factorial implementation:
does not exists: design.sidemeanings.arith.factorial
which correctly computes the factorial. However if we decide to call just the defaultSumAll and defaultSumTwo implementations, the same attempt to implement factorial will fail. Why? Because providing new implementation of overridableSumTwo will have effect only on those who directly call sumTwo, not on behaviour of default sumRange.
Different API
The important thing is that the choice of modifiers can turn the API into something completely else. Boths APIs can be useful in certain situations, but one shall be aware that they are completely different. It is not necessary to EliminateFuzzyModifiers all the time, but remember to perform such mental exercise in your mind at least. Only then you'll see what kind of API you are in fact giving to your users.
In the particular Arithmetica example the intention very likely is (especially in the light of future optimization suggested in AlternativeBehaviour) to support just summing numbers. Computing factorial is a misuse, not a use case. In such case, one wants to call the defaultSumAll and defaultSumTwo.
Moreover the subclassing is not a use case either. As such it makes sense to eliminate all the protected methods and restrict the API to just:
public final int sumTwo(int one, int second); public final int sumAll(int... numbers); public final int sumRange(int from, int to);
Simple, easy to call, good looking API. We achieved this by realizing that the original access modifier was fuzzy, we did the mechanical split into three single meaning methods replacement and finally we have choosen to eliminate the unnecessary meanings. This is the base method for making sure our APIs really do what we think they do.
Support for Factorial like Use Case
If, on the other hand, we decide to support subclassing, we'll be left with up-to triple methods for the same thing in one class. As you can imagine this may not look good in Javadoc. Why do users who just call into the class need to be bothered with information about methods only for subclasses? This is not real cluelessness.
On the other hand, the situation is not necessarily that bad. For example the java.nio.charset.CharsetEncoder is using style with public XYZ methods and pair protected implXYZ ones. But when we seek clarity, we shall not stop. We shall seek for better alternative.
Let's think about ClarityOfTypes - but let's leave this for next time. If there is an interest, I'll reveal more. Meanwhile you may want to find the answer in Chapter 10, Cooperating with Other APIs of TheAPIBook.
Pre/Post Conditions
Careful readers might have realized that the split to public final method and protected abstract in fact allows the API to enforce pre/post conditions inside of the final method. This is very good for clarity and consistency too. But again more about that in Chapter 10 or later, if there is some interest...
TBD: Allows pre/post conditions. E.g. enforcing consistency of an API