How do you design an interface that returns the result of some calculation when 99% of the time the result is simple (for example a single point) and 1% of the time the result is complex (an arbitrary set of points)? How do you signal to the customer that he needs to be careful of the 1% cases? How do you protect the customer from accidentally ignoring these cases, while not forcing him to write cumbersome code? That is the topic of my next few posts.
One of the standard functions in any solid modeling kernel, such as ACIS or CGM, is FindClosestPoint, which, given a test point and a surface finds the closest point on the surface (as pictured below) and returns it – this closest point is sometimes called the projection of the point onto the surface.
A customer who wants to do something with the projection of a particular point might write code like the following:
- where the declarations labeled “modeler” come from the modeler’s header files.
Looks pretty straightforward, eh? I would ask if you’ve noticed the subtle mistake in the modeler interface, but you’ve probably already glanced at Figure 2 and seen the failure mode:
The failure mode is that, for certain geometric configurations of the input point and the surface, the closest point is not unique. Typically, the failure will be that there are several isolated closest points (rather than just one) on the surface, but I’ve chosen a moderately nasty case for Figure 2 – the test point is at the center of a spherical surface, in which case all the points on the sphere are “closest”.
The root cause of this issue is that the interface designer of FindClosestPoint fell into “The Happy Path Trap” (a name that I just made up). The designer was imagining Figure 1 (the happy path case) when the function was created, and didn’t think of the “what if there’s more than one closest point” case in Figure 2. The typical behavior of FindClosestPoint in the non-happy-path case is to return an arbitrarily selected point from the set of all closest points, as illustrated in Figure 2.
The reason this interface error is so insidious is that it leads to a rare and subtle contract violation: in almost all the cases, the function returns all (one) of the closest points. Only in the rare Figure 2-type cases will this not be true. If it’s important that DoSomething processes all of the closest points, then the customer will have a rare and subtle bug in his application – these are the worst sorts of bugs to protect against and the hardest to track down.
So now we’ve recognized the problem: most of the time the answer is simple (an isolated point), but occasionally the answer will be complex (a general point set). How do we design a better interface that signals to the customer that there’s a subtlety here, so that the customer doesn’t fall into the same trap that we’ve just found?
The wrong answer is to simply document the subtlety. The reason is that this puts the burden of finding (in the docs) and understanding the subtlety on the customer. In isolation, this might not seem like a large burden, but there will be hundreds or thousands of such subtleties in a solid modeler’s interface, and expecting the customer to remember them all is a recipe for bugs. That’s not to say the subtlety shouldn’t be documented; of course it should. We just shouldn’t count on the customer reading and remembering the documentation.
A better thing to do (in addition to documenting the subtlety) is to simply change the name of the function from FindClosestPoint to FindAClosestPoint. In this case, we haven’t changed the behavior at all, but we’ve signaled to the customer, in his source code, that there’s a subtlety. The hope here is that the customer will notice while coding (or, more importantly, cutting and pasting) that the name is a bit funny, which will lead him to read the documentation for the function. In essence, we’ve changed the name of the function to reflect the true contract that it obeys. The problems with this solution are that the customer doesn’t have a simple mechanism for testing whether he’s on the happy path or in the complex case, he’s still in a situation where he can accidentally ignore the subtlety (if he doesn’t notice the funny name), and he has no way of getting the “full” answer. I’ll talk about techniques for solving these problems in my next post.
Anyone care to guess what I'll propose?