Рассмотрим класс, который должен выдвигать параметры, учитывая некоторые подсказки и конкретный приемочный тест.
Пример для конкретизации: Скажем, вы угадываете кубические размеры необработанногофайл данных, основанный на имени файла.Приемочный тест: общее количество элементов == размер файла (в расчете на 1 байт в расчете на единицу сетки).
Для этого требуется упорядочить тесты по приоритетам, где каждый тест выполняет одну или несколько попыток пройти приемочный тест.Первое пропущенное предложение немедленно возвращается, и больше никаких попыток не предпринимается.Если ничего не пройдет, ничего не предложите.
Вопрос : Какой шаблон / подход вы бы порекомендовали, когда читаемость является главной проблемой?Кроме того, каковы недостатки и недостатки следующих предложений?
Метод 1: Исключения для успешного прохождения приемочного теста
Я слышал, как сказалмудрые люди, чтобы не использовать try / catch, когда не ловят фактические исключения.Однако в этом случае результат довольно читабелен и выглядит примерно так:
try {
someTest1();
someTest2();
// ...
someTestN();
}
catch(int){
// Succesfull return
xOut = x_; yOut = y_; zOut = z_;
return;
}
xOut = -1; yOut = -1; zOut = -1;
С внутренним приемочным тестом:
void acceptanceTest(const int x, const int y, const int z)
{
if (verify(x * y * z)) {
x_ = x; y_ = y; z_ = z;
throw 1;
}
}
Метод 2:Do-while-false:
Изменение: все тесты возвращают значение true, как только он проходит приемочный тест.Возвращает false, если все попытки теста не пройдены.
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_;
Приемочный тест:
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;
}
Метод 3: Массив указателей на функции
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;
Тестовые функции и приемочные испытания такие же, как и для do-while-false.
Метод 4: Перейти
Я виделdo-while-false упоминается как замаскированный goto, за которым следует аргумент, что если это предполагаемое поведение, «почему бы не использовать goto?».Итак, я перечислю это:
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;
Тестовые функции и приемочные испытания такие же, как для do-while-false.
Метод 5: Короткое замыканиелогика (предложено Майком Сеймуром)
if (someTest1() ||
someTest2() ||
// ...
someTestN()) {
// success
xOut = x_; yOut = y_; zOut = z_;
return;
}
xOut = -1; yOut = -1; zOut = -1;
Функции тестирования и приемочные испытания такие же, как для do-while-false.
Редактировать: Я должен отметить, что методы 2,3,4,5 отличаются от 1 тем, что требуется булево возвращаемое значение в приемочном тесте, который передается полностью обратно в функцию возврата, а также дополнительные издержки в каждой тестовой функции, которая выполняет несколькопопытки пройти приемочный тест.
Это заставляет меня думать, что метод 1 имеет преимущество в удобстве обслуживания, поскольку логика управления находится исключительно на нижнем уровне: приемочный тест.