Caller and Callee whos responsiblity is it really

Started by
4 comments, last by NotAYakk 17 years, 6 months ago
I was reading the book "The Pragmatic Programmer" and there it suggested that it was the responsibility of the caller to make sure that valid data was passed to the routine being called. It sounds good but seems to have the possible implication of severly violating the "DRY" (don't repeate yourself) principle discribed earlier in the book. If the calling routine has to check if the data is valid every time the subroutine is called there is alot of duplicate error checking code where as if the error checking is done within the subroutine itself the bounds error checking is easily modifiable and never duplicated. Where should error checking really be placed?
____________________________"This just in, 9 out of 10 americans agree that 1 out of 10 americans will disagree with the other 9"- Colin Mochrie
Advertisement
It depends on the function contract.

If the contract is "pass me a valid argument, or I'll throw an exception", then you pass it any arguments without checking, do the checks only once inside the function, and then catch at the call point any errors that happen.

If the contract is "you must pass me a valid argument", then you perform the checks before doing the call (if necessary) and once inside the call, you assert the validity of the data once again (resulting in a runtime error if it doesn't happen).

Of course, it is reasonable to use the interfaces to represent the contract (so you get compiler errors instead of runtime ones). For example, the contract is "the pointer argument must not be null", use a reference instead and avoid a check. This can be extended to other situations easily.
You should also consider the lifetime of the arguments being passed.
For a setup like
FILE *myfile = fopen ( .... )
you want to check that myfile is valid before passing it onto the other file functions, since the FILE* stays around a long time.
That said, this check only happens once. For something more like
sqrt ( x )
it might make sense to check inside sqrt if ( x < 1 ) and throw and exception, since x is likely only used in this one place before being modified again. you'd have a TON of extra
code if you wrote the check before each function call.
Quote:Original post by raptorstrike
I was reading the book "The Pragmatic Programmer" and there it suggested that it was the responsibility of the caller to make sure that valid data was passed to the routine being called. It sounds good but seems to have the possible implication of severly violating the "DRY" (don't repeate yourself) principle discribed earlier in the book. If the calling routine has to check if the data is valid every time the subroutine is called there is alot of duplicate error checking code where as if the error checking is done within the subroutine itself the bounds error checking is easily modifiable and never duplicated.
Where should error checking really be placed?

"making sure that the preconditions are met" is not the same thing as "verifying that the preconditions are met". The situation where you will be explicitly checking whether a precondition is true is very much the exception rather than the rule. Much more often, the precondition will be "provably true" given the preconditions to the calling function and what has happened in the function to date. If you multiply a number by 2 and pass it into a function that only operates on even numbers, for instance, there's no need to check to make sure the number is even. If you find yourself writing a lot of handling code for the case where preconditions are not met, you should consider expanding the preconditions of the calling function itself.
Quote:Original post by Sneftel
"making sure that the preconditions are met" is not the same thing as "verifying that the preconditions are met".


Quoted for emphasis.

The usual idea is:

- If there is sane handling for corner cases and they aren't really "errors" (e.g. some particular pointer is allowed to be NULL, but if it is, then nothing should actually be done), then handle that with guard clauses at the beginning of the function (callee responsibility).

- If the situation really isn't supposed to happen, i.e. you've identified a real "precondition" for the function, it becomes the responsibility of the *person writing* the calling code to make sure it doesn't come up. This is ideally done by writing simple code that can easily be shown not to violate the precondition. It's not done by explicitly checking the precondition.

- Code defensively, by *asserting* the precondition. (This will cause the program to cough and die loudly in debug mode if a violation occurs, and get compiled out in release mode.) Following DRY, the callee becomes responsible for the asserts.

- If you want to code *really* defensively, you can assert *postconditions* as well. This still happens in the callee. The caller then gets to *assume* truth of the postconditions; i.e. the person writing the calling code uses that information when reasoning to prove that preconditions will be met for the *next* function call.

- Asserts serve as *documentation* of pre- and postconditions *by their very existence*. (Unfortunately, documentation-generation tools like Doxygen may tempt you to violate DRY by writing those conditions out explicitly in header comments.)
"Be strict in what you supply, be lenient in what you accept."

... and complain loudly when the caller breaks the contract.

The wonderful thing about the real world is that it contains contradictions.

Don't repeat yourself.
and
Code defensively.

often disagree with each other.

The thing you are missing is that you aren't always writing to code you wrote yourself at the present time. You are dealing with code your wrote 2 years ago, or will write in 2 years, or someone else wrote next door, or someone else wrote 5 years ago, or someone else will write in 1 year.

Just because your code currently is hooked up in one configuration, it doesn't mean that is the only way it will be hooked up. The code in the caller or the callee may change. And by coding defensively, your code will scream when the assumptions you made when writing it fail -- allowing you to catch the mistake before it snowballs, or catch it and prevent it from hiding away in a corner.

This topic is closed to new replies.

Advertisement