Is goto Still Considered Harmful?

S

Stephen Sprunk

That depends upon how you define unit tests. For purposes of unit
testing, I treat functions with internal linkage as part of the
function(s) with external linkage in the same translation unit that
(directly or indirectly) call them. If they have any feature which
cannot be properly tested through calls to those externally linked
functions, then that generally means that the feature is either
unnecessary or at least badly designed.

To me, the purpose of unit testing is to prove the code is correct (as
defined by the design docs), and one cannot prove a function is correct
until you've proven that all the functions it _calls_ are correct. I've
chased down too many bugs where function X calls function Y with an
invalid argument and got a result that seemed reasonable--until one day
(or once in a blue moon) it didn't. If you test function Y, that's one
less thing to worry about when testing (or troubleshooting) function X.
No, but it can dramatically increase that limit. Well-designed programs
stress your short-term memory capacity much less than poorly designed
programs, because they're easier to understand.

Fair enough, though I think of "large" in terms of complexity rather
than absolute size. A short, complex function can test my mental limits
more than a long, simple one; the former is also more likely to contain
hidden bugs, and that seems like inherently bad design.

For an example, just look at the IOCCC; most submissions are mercifully
short yet more mentally taxing than a typical real-world program that's
ten or even a hundred times their size. And I still don't understand
that "RSA in 5 lines" Perl T-shirt, which puts even most IOCCC code to
shame.

S
 
J

James Kuyper

To me, the purpose of unit testing is to prove the code is correct (as
defined by the design docs), and one cannot prove a function is correct
until you've proven that all the functions it _calls_ are correct.

True, but if Y can only be called by X, that doesn't prevent you from
testing Y; it just means that you have to test Y by calling X. I've
written more than a few such test drivers. In my experience, if there's
a good reason for giving Y internal linkage, the fact that it's not
directly callable by the test driver is seldom a serious barrier to
testing it.

....
Fair enough, though I think of "large" in terms of complexity rather
than absolute size. ...

I can agree with that; greater size allows greater complexity, but it
does not mandate it. However, as I said earlier, most of the
uncomplicated large functions I've ever seen were either reading into or
writing from badly designed data structures.
... A short, complex function can test my mental limits
more than a long, simple one; the former is also more likely to contain
hidden bugs, and that seems like inherently bad design.

For an example, just look at the IOCCC; most submissions are mercifully
short yet more mentally taxing than a typical real-world program that's
ten or even a hundred times their size. And I still don't understand
that "RSA in 5 lines" Perl T-shirt, which puts even most IOCCC code to
shame.

Does the expanded version with annotations at the bottom of
<http://www.cypherspace.org/rsa/pureperl.html> help? I haven't bothered
to try to understand it, but it doesn't look that bad (unless you don't
know perl, of course).
 
S

Stefan Ram

James Kuyper said:
I can agree with that; greater size allows greater complexity, but it
does not mandate it. However, as I said earlier, most of the
uncomplicated large functions I've ever seen were either reading into or
writing from badly designed data structures.

Once I was assigned to write a C++ interface to a specific
database. I started by writing a class for each table.

Someone who saw my code was horrified by the »unnecessary
complexity« of my code.

It seems to me that he measured complexity by »number of
classes«. Since I had written many classes that was a lot of
complexity to him.

I measured complexity by »number of rules«. I had one rule:
»One class per table with the name of the class being the
name of that table.« So, to me, the complexity was very low.
 
I

Ian Collins

Stefan said:
Once I was assigned to write a C++ interface to a specific
database. I started by writing a class for each table.

Someone who saw my code was horrified by the »unnecessary
complexity« of my code.

It seems to me that he measured complexity by »number of
classes«. Since I had written many classes that was a lot of
complexity to him.

I measured complexity by »number of rules«. I had one rule:
»One class per table with the name of the class being the
name of that table.« So, to me, the complexity was very low.

I hope you won that one!

I've seen too many cases in both C and C++ where the database is the
"object" and not the database contents.
 
I

Ian Collins

ralph said:
I would probably be equally horrified and not just because of the
large number of classes, it would be simply that your "rule" is most
likely doomed. <g>

Need to read up on Object Relational Mapping. There are exceptions
where such a scheme might be useful, but doubtful until the
alternatives have been explored and "ruled-out". (Pun intended.)

C++ specifics are OT here, but whether discussing objects/methods or
functions, it has always been a rather universal rule or thumb that
any time an abnormally large number have been created for "external"
consumption of a single resource "complexity" has risen exponentially
and alarm bells should be going off.

The complexity is inherent in the problem. If your database has a
hundred tables, whether you choose to have mega database instance with a
thousand functions or a hundred content focused instances with ten
functions you still have the same complexity. How you manage the
complexity and express the interface to the content is the art of design.

If my client module only cares about the data in tables 36 and 42, I
know which style of interface I prefer to have to understand.
 
S

Stephen Sprunk

True, but if Y can only be called by X, that doesn't prevent you
from testing Y; it just means that you have to test Y by calling X.
I've written more than a few such test drivers. In my experience, if
there's a good reason for giving Y internal linkage, the fact that
it's not directly callable by the test driver is seldom a serious
barrier to testing it.

You can only thoroughly test Y if you can prod X into calling Y with all
possible (or at least interesting) values. If X is well-written, it may
reject or avoid bug-triggering values before they get passed to Y, so
you don't know Y can actually handle them (i.e. fail) correctly, and you
don't get to test X's handling of Y's failure.
I can agree with that; greater size allows greater complexity, but
it does not mandate it. However, as I said earlier, most of the
uncomplicated large functions I've ever seen were either reading into
or writing from badly designed data structures.

That's one common case, but not the only one I've encountered.
Does the expanded version with annotations at the bottom of
<http://www.cypherspace.org/rsa/pureperl.html> help? I haven't
bothered to try to understand it, but it doesn't look that bad
(unless you don't know perl, of course).

I've seen the annotations and they do help, but it's complex enough that
I can't keep the entire thing in my head at once. That was necessary to
fit it on a T-shirt (or signature), but in another context it'd be bad
design.

S
 
J

James Kuyper

You can only thoroughly test Y if you can prod X into calling Y with all
possible (or at least interesting) values. If X is well-written, it may
reject or avoid bug-triggering values before they get passed to Y, so
you don't know Y can actually handle them (i.e. fail) correctly, and you
don't get to test X's handling of Y's failure.

As I said above, I consider X and Y to be a single routine for purposes
of testing. I seen no practical distinction between:

int X1(int a; int b)
{
if(b)
return a/b;
else
return INT_MAX;
}

and

static int Y(a, b) { return a/b; }

int X2 (int a; int b)
{
if(b)
return Y(a,b);
else
return INT_MAX;
}

Y(1,0) would fail; but I don't consider that a defect, since Y() has
internal linkage, and no pointer to Y() is ever passed out of the same
translation unit (TU) where it is defined. Therefore, it can only be
called from within the same TU. Every call to Y() from within the same
TU is protected against having the second argument be 0, so Y() is
relieved of it's responsibility for validating b.

Therefore, I would consider a complete unit test of X1() to also be an
entirely acceptable unit test for X2()+Y(), even though there's no way
for the test driver to call Y(1,0).

This is obviously a simplified example, merely to explain the
equivalence I'm talking about; it is NOT intended to demonstrate a
plausible reason for creating Y() - there is no such reason in this
example. Almost identical blocks of code occurring in several different
locations within X(), and nowhere else, is one typical situation where I
would split off a separate static function for such purposes.
 
M

Malcolm McLean

As I said above, I consider X and Y to be a single routine for purposes
of testing. I seen no practical distinction between:

int X1(int a; int b)
{
if(b)
return a/b;
else
return INT_MAX;
}

and

static int Y(a, b) { return a/b; }

int X2 (int a; int b)
{
if(b)
return Y(a,b);
else
return INT_MAX;
}
A common real example is

void setpixel(unsigned char *image, int width, int height, int x, int y, colour col)

and

void setpixel_checked(unsigned char *image, int width, int height, int x, int y, colour col)

tests for out of bounds are often quite expensive.
 
P

Phil Carmody

Ian Collins said:
Ah Phil, you can which one of us has been corrupted by the politics of
management!

Still a code monkey, and I like it that way. I've finally completed
the cycle of company sizes (peaking at Samsung last year, yikes),
and am now back at the beginning again, in a company with only 4
devs and a management hierarchy that only a mathematician would be
prepared to actually call a "hierarchy".

Is that email address valid, I have a small favour to ask?

Phil
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top