Re: Frank's reply to my last post...
Sun is absolutely correct in their statement on assertions...callers should not be dependent on the fail-fast behavior they provide. Your statement, immediately following, on the other hand, I'm not so sure I can agree with:
I also think there will be less bugs and that debugging will be easier if the contract is checked and a fast explicit failure results from a breach of contract
If you really, truly believe that you can significantly affect the quality of your product in a positive way by fail-fast behavior, then build it into your contract
. Let me give you an example...
I'm sure we're all familiar with the idea of a usage statement on command-line utilities. If I type in "dir /ljkl;akjl;das" at the Windows command prompt, I get a usage statement or an error message. Note that the dir program did not throw an exception, it did not do something unexpected...it behaves in a totally, 100% predictable way and instructs me on how to properly use the program. An instructional message, in other words, is part of the contract of the dir program. Restated a bit differently, the dir program accepts any input at all and has a "return value" associated with any of those possible inputs. Now, from a user standpoint, I might say that by printing out the usage message it's telling me that my input was "invalid". For my purposes as a user, of course it was invalid input becuase it didn't get me the information I was ostensibly after (unless I actually wanted to see the usage message). From an API standpoint, the input I provided was a valid point in the "space of inputs" accepted by dir.
So what's the difference between supporting any input versus restricting inputs in the contract, but supporting them with a particular behavior (say, assertions that fail right away). The difference is huuuuge. The difference lay in understanding the fundamental point of contracts. The point of a contract is to let the caller know what they have a right to expect from you, the implementor. It clearly demarcates where your responsibility ends and your caller's responsibility picks up. The real value of contracts is NOT simply documenting what inputs you expect...the real value of contracts is in setting expectations and drawing boundaries of responsibility
For example...I write a method that declares as a precondition arg1!=null. It so happens the way I've implemented my method, if a caller hands me arg1==null, I throw a NullPointerException. The caller may, after a while, figure out that all my methods behave this way, and become dependent on that functionality, catching NPEs in certain circumstances and interpreting them to mean that arg1==null. One day, however, I'm faced with new requirements from management that require me to change that behavior--my methods will no longer throw NPEs if callers pass in arg1==null, instead they'll give some random, invalid return data.
The callers that have learned to depend upon NPEs might be angry...they may have scattered throughout their code all these catch blocks that they now have to change. They may cry and whine and insist that it is my responsibility to make sure I don't change that behavior, forcing me to opt for a hack solution where an elegant one exists if only I'm allowed to change that functionality.
The question is, did I ever intend to support that functionality? And that's where the contract comes in. Since I wrote that precondition arg1!=null at the outset, I'm well within my rights to change how my method responds to arg1==null at any time and all those callers who were depending on that behavior can go suck a lemon. Of course, properly used, DbC will prevent this situation from even occurring in the first place--that's the point, really. And therein lies its value.
That's why I say that you ought to insist that your method's behavior, if preconditions are not met, is undefined. You should resist giving any other outward appearance or answer. Now, when it comes to coding up your method, you are free to do any old thing you want for those undefined inputs, and you ought to choose the path that will increase quality the most (say, for instance, failing out right away with an AssertionError). But I always keep a careful eye on whether callers are beginning to depend upon certain undocumented behavior, and then I change it right away for a time and force them to do things without depending on what I like to call "incidental behavior", pointing at my contract and insisting all the while I never said I'd support that behavior. Once they get back in line, I go right back to implementing the undefined behavior that will result in the highest quality project. I do this because I believe that the first and most important step to quality is hammering out who's responsible for what and then holding people to their stated responsibilities...only then can language features like asserts or stack traces do the project any good. Once the people creating the codebase are allowed to get out of control, no amount of debugging tools will save the quality of the project.
So: externally, declare what you plan to support from here on out, and periodically shake up the other degrees of freedom to keep people honest. Internally, handle your undefined states in a way that will increase quality the most.
To address a few of the points that were made in the ensuing posts...Tim writes:
...the fact that a contract exists on paper doesn't mean anything unless you enforce it...
I must disagree. Assuming you understand contracts the way I do, it makes a *world* of difference that the contract exists on paper whether you "enforce" it or not (I put "enforce" in quotes because I consider a contract enforced if it meets its declared postconditions when the caller meets the declared preconditions). Because when stuff blows up, you can quickly figure out who's job it is to fix it. If people are depending on undocumented functionality, it's their job. If they met your preconditions and things still aren't working, it's your job. This ensures that changes occur at the source of the problem, not as hacks in surrounding code that really isn't responsible for the functionality misbehaving...this is how modules initially begin to assume responsibility for other modules and functional creep happens. The thing I love about contracts is that they are not just a coding tool, they are a management tool that keeps people honest when used properly...and better yet, they allow you as a lowly coder to manage your fellow coders without having to wait around for Pointy-Haired Boss to do it.
Continuing with Tim's post:
Specifying the contract of a method is not about returning a value for every conceivable input value...
Right. I agree wholeheartedly. Specifying a contract is not about returning a value for every conceivable input. It is about specifying the return value for every valid
(meaning, "meets the preconditions") input, while declining to specify anything at all for inputs falling outside those boundaries.
anyone reading it can expect an AssertionError as soon as they violate the contract of the method: they know immediately when they do so. (Note that expecting it and relying on it are different things. If they catch the AssertionError, all bets are off: in production code you'd get badness).
You've made my point more articulately than I did, I think. I am in vehement agreement with you, sir! "Expecting" in the above quote means that you, as a coder, can expect to see something happen. "Relying" means that not you, but your code, is expecting to see something happen. First case: fine. Second case: nope.
(I might in some cases go so far as to even say that, in the first case, a given response to an invalid input should not even be expected. There might be a particular situation where it is extremely difficult or even impossible for me to fail out in a particular "expected" fashion. If a caller has no way of detecting that particular situation, they might be surprised when "the usual" doesn't happen, to which I say: "Ha ha! Gotcha!" And then I point and laugh...)
(Which usually gets me a
, and then a
, by the way.)
[ June 08, 2004: Message edited by: sever oon ]