In that book, Alan brings the point that any object oriented software component should be looked at in three different perspectives,
- Concept - which is the responsibility of that particular component (i.e. what it’s supposed to do)
- Specification - which is the contract with the external entities (i.e. how it would be used)
- Implementation - which is how that component is actually going to get the job done.
At any given level of abstraction - application | component | package | class | method - this stands true and is very important to separate these concerns in designing at each level.
I'd like to give a thought on how this is applicable at the level of a method. Let’s take an example to make things easier to understand. A method that converts a string to a number.
What is the responsibility?
Convert a string to a number of cause, but does that statement describe everything?
What’s the contract?
Defining the contract is the step which is going to make the responsibility of the method more explicit, and define the explicit usage of the method.
What are the parameters the method would take? Just the string to be converted? Should it get control parameters?
What’s the format of the string should be?
What would the method return if the format is incorrect? Would it return a default value? Would it throw an exception (what type of exception) or would it return null?
What type is the number? Integer or float? Could it be both? If so, how would the method return it?
What’s the implementation?
We should only get at this once we have the answers to the above two questions. And our implementation should depend on them, the responsibility or the contract should not change by our implementation - remember the dependency inversion principle!
So why is this post called “The specification of a method”? Because that’s where I’d like to focus for the rest of this post. That’s where I see room for a lot of improvements.
How exactly do we define the contract for a method after we do have the answers to the questions like what we saw above? It should all end up in the method signature. But, most languages only allow us to specify parameters and return types; so how do we communicate exceptions and whether a method would return null or not?
Java is a language that goes one step further and offer checked exceptions that allow us to express in the method signature what type of exceptions are thrown by the method; which I learned to appreciate only very recently. That’s a feature which make sure an application will not ever crash due to an unhandled exception, by making method contracts explicit.
That’s one good example that making the contract explicit contributes directly towards application stability and better coding practice. (But there are debates on whether this is a good thing or a bad thing - I think it is good.)
How do we do this in .NET? We could add the exception details in the XML comment documentation. But that’s not as good as the compiler mandate of java.
The other part of the signature which we miss a lot is conveying the fact whether a method would return null or not. We see quite a few null reference exceptions and quite a few null checks on methods that do not return nulls than we would like to in our day-today life. Some argue that it’s a “good” practice to check null even if a return value cannot be null. But that’s a pretty lame claim isn't it?
There are two ways that I could think of to overcome this. Obviously we can comment if a null value is returned by the method. But there’s no guarantee that someone someday would forget the comment and would not check null. The much thoughtful approach is to implement the Special Case pattern (PoEAA). In which, separate implementations are given to such anomalies as null for the same interface, so that the consumer need not worry about exceptions. But, it’s a design decision that has to be taken much earlier than you start to design method signatures.
Regardless of how we are going to handle these information in the method signatures, if we do define them upfront and communicate to the consumers of the method (and of cause stick to that contract within the implementation) we are bound to improve stability and the quality of code in our applications.