Richard Bos said:
Any reasons for that statement?
Sure. The basic idea of structured programming is that the program is
divided into a hierarchy of functions, becoming more general-purpose as you
move down the hierachy, more specific as you move up.
The other principle is that each function performs one closely-defined task,
and is a black box to the function that calls it.
This means that if we have a function bar() that calls foo(), and barb()
calls bar(), barb() shouldn't know that bar() is calling foo().
This means that barb() shouldn't know the intricacies of foo(). Any change
to foo() shouldn't imply a change in barb(), but only in bar() (where it is
unavoidable).
This does not absolutely preclude returning a result directly from foo() in
barb(), but it does mean that occasions for doing so will be rare, and can
be used as a marker for code that is likely to be poorly-designed, because
the function hierarchy isn't abstracting foo() from bar().
Let's give a concrete example.
void barb()
{
char *str;
str = bar();
if(!str)
out_of_memory_error();
}
char *bar()
{
return foo("duplicate this string\n");
}
char *foo(char *str)
{
char *answer = malloc(strlen(str)+1);
strcpy(answer, str);
return answer;
}
Now let's modify foo(). Let's say that we only handle strings of ten
characters or less. If passed more than ten, return 0.
Now you will notice that barb() is now broken, because it is reporting an
out-of-memory condition when in fact the error is "string too long". However
barb() doesn't call foo(), so a search for all instances of the identifier
foo() won't pick this up.
Of course in a short program we can easily correct the error, and in a long
program bar() should be correctly-documented (but how likely is the
programmer who wrote bar() to say "returns 0 on error" when he really meant
"returns 0 on out-of-memory"?).
By not handling the return value at the correct level you are spoiling the
structure of your program.
Nonsense. Look at the example I gave: it could easily be an indication
that foo() can work on any data, and the calling function uses it to
work on specific data.
It means that foo() is too general for the real caller to use. The function
cinterp(char *cfunction) which runs a c interpreter on arbitrary source is
completely general, but also not much use.
Now there can be good reasons for providing a very general function. For
instance you could impement sqrt() as return pow(x, 0.5). However see the
problem - sqrt() is a special case of exponentiation, and it can be
implemented a lot more efficiently. If you already have a working pow() nad
you don't care about run-time efficiency then go ahead. This is not an
untypical case. Take quad( ... four points ... ) axis_aligned_rectangle( ...
two points ...) could be implemented as a call to quad(), but again this
only makes sense in the special circumstances of having a working quad() and
no access to its display buffer.
Microsoft have provided a very customisable OpenFile() dialog. For programs
intended for distribution to end users it probably makes sense. However in a
testing and quick tools environment you generally want to write fname =
getopenfile(), rather than go to the help pages to see how to set up a
complex input structure. So arguably the function is badly-designed.