Answer: when it’s a 'HappyPathPoint'.
Happy New Year everyone! I left off my last post with a dilemma: I wanted to design the interface for a function that people will call when they want to find the closest point on a surface to a test point. The problem is that the answer to this question is only usually a single, isolated point. Every now and then the answer is a set of (more than one) points. For example, all the points on a spherical surface are 'closest' to its center. So how does one design the interface so that the person writing the client code naturally accounts for this subtlety, while not forcing him to deal with an awkward interface?
I already stated that the wrong thing to do is to simply document the subtlety and hope the customer will notice it. A better solution is to (in addition to documentation) make the name of the function reflect the subtlety: prefer 'FindAClosestPoint' over 'FindClosestPoint'. The problem with this idea is that it doesn’t actually deal with the problem – how is the customer to tell if he’s on the happy path, or has run into the complex condition? And again, it relies on the customer noticing that something is up (rather than having the compiler tell him).
My preferred solution (which we first thought of while working on an ACIS project to make our internal interfaces more robust) is to introduce a new object called a 'HappyPathPoint'; the idea is that the function returns this object rather than a raw point:
As you can see, HappyPathPoint encapsulates both possible answers: if IsPoint() returns true, then the answer is (the typical case of) an isolated point and it’s safe for the customer to use the Value() method to get the point. If IsPoint() returns false, however, then the answer is (the extremely rare case of) a more general point set, and the customer should use a different branch in his analysis code:
In reality, the customer’s initial implementation for the branch where IsPoint() returns false will simply be to throw an error (and put a project in the backlog to think about what his algorithm should do). This is still a big improvement over the case where the customer’s code ignores the subtlety – what was previously a subtlety is now simply an extra question about the answer, and the code for dealing with the atypical case is isolated at the point where that case is first introduced.
One interesting question in all this is "What should the behavior of Value() be if it is called before IsPoint() has been called?" I see three possibilities:
A) Silently return an arbitrary point in the point set B) In release builds, return an arbitrary point. When contract checks are on, throw. C) Always throw
My preferred answer is B, especially if it is used in conjunction with a code coverage tool that ensures that you test suite will always hit the (invalid) call to Value() (with contract checks turned on, of course). The reason that this works is that the question I asked was NOT "What should the behavior of Value() be if it is called when IsPoint() is false?" That case will only show up in the atypical situations, and so typically won’t be hit in customer testing. The contract is that the customer must validate that the HappyPathPoint truly represents an isolated point before calling Value(), even if the HappyPathPoint happens to be an isolated point. This ensures that the client code is correctly written, even if none of its test cases hit the atypical case.
For those of you who speak C#, you should see similarities with the 'nullable' class that allows one to wrap a value class up into something that can be tested against null. As with nullable, the compiler will warn you if you try to use a HappyPathPoint where a Point is expected. HappyPathPoint is more powerful than nullable, however: nullable doesn’t protect (as a contract check) against an untested call to Value(), and it doesn’t provide an alternative answer.
I hope that I’ve convinced at least someone out there that this is a good idea. I don’t think it’s overly burdensome, and it makes the subtlety obvious in the interface. It is important not to adopt this idea as a one-off, however – if you’re going to use it, it should be implemented uniformly across your interface. The code snippets above are just an example; an interesting thing to try might be to write a HappyPath template class.
The last thing I want to say is that we’ve been thinking about this idea for years, and still don’t have a good name!! Any ideas for a better one? 'PointCandidate' maybe?