Consider a class that is supposed to make parameter suggestion, given some clues, and a specific acceptance test.
Example to concretise: Say you are guessing the cubic dimensions of a raw data file, based on the filename. The acceptance test is: total elements == file-size (assuming 1 byte pr. grid unit).
This requires a prioritized ordering of tests, where each test does one or more attempt to pass the acceptance test. The first suggestion that passes is immediately returned, and no more attempts are made. If none pass, suggest nothing.
The question: Which pattern/approach would you recommend, when readability is the main concern? Also, what are the flaws and drawbacks with the following suggestions?
Method 1: Exceptions for catching a successful acceptance test
I've heard said by wise people to avoid using try/catch when not catching actual exceptions. However, in this case, the result is fairly readable, and looks something like:
try {
someTest1();
someTest2();
// ...
someTestN();
}
catch(int){
// Succesfull return
xOut = x_; yOut = y_; zOut = z_;
return;
}
xOut = -1; yOut = -1; zOut = -1;
With the inner acceptance test:
void acceptanceTest(const int x, const int y, const int z)
{
if (verify(x * y * z)) {
x_ = x; y_ = y; z_ = z;
throw 1;
}
}
Method 2: Do-while-false:
Change: All tests return true as soon it passes the acceptance test. Returns false if all tries in the test fails.
do {
if ( someTest1() ) break;
if ( someTest2() ) break;
// ...
if ( someTestN() ) break;
// All tests failed
xOut = -1; yOut = -1; zOut = -1;
return;
} while (0);
xOut = x_; yOut = y_; zOut = z_;
Acceptance test:
bool acceptanceTest(const int x, const int y, const int z)
{
if (verify(x * y * z)) {
x_ = x; y_ = y; z_ = z;
return true;
}
return false;
}
Method 3: Array of function pointers
typedef bool (TheClassName::*Function)();
Function funcs[] = { &TheClassName::someTest1,
&TheClassName::someTest2,
// ...
&TheClassName::someTestN };
for (unsigned int i = 0; i < sizeof(funcs)/sizeof(funcs[0]); ++i) {
if ( (this->*funcs[i])() ) {
xOut = x_; yOut = y_; zOut = z_;
return;
}
}
xOut = -1; yOut = -1; zOut = -1;
Test functions and acceptance test the same as for do-while-false.
Method 4: Goto
I've seen the do-while-false referred to as a disguised goto, followed by the argument that if that's the intended behavior "why not use goto?". So I'll list it up:
if (someTest1() ) goto success;
if (someTest2() ) goto success;
// ...
if (someTestN() ) goto success;
xOut = -1; yOut = -1; zOut = -1;
return;
success:
xOut = x_; yOut = y_; zOut = z_;
return;
Test functions and acceptance test the same as for do-while-false.
Method 5: Short-circuiting logic (suggested by Mike Seymour)
if (someTest1() ||
someTest2() ||
// ...
someTestN()) {
// success
xOut = x_; yOut = y_; zOut = z_;
return;
}
xOut = -1; yOut = -1; zOut = -1;
Test functions and acceptance test the same as for do-while-false.
Edit: I should point out that Methods 2,3,4,5 differ from 1 by requiring boolean return value on the acceptance test that is passed all the way back to the return function, as well as added overhead in each test function that does multiple attempts at passing the acceptance test.
This makes me think that method 1 has an advantage to maintainability as the control logic is solely at the bottom level: the acceptance test.