Бросок при успешном выполнении, do-while-false, массив функций и операторы goto. - PullRequest
3 голосов
/ 09 ноября 2011

Рассмотрим класс, который должен выдвигать параметры, учитывая некоторые подсказки и конкретный приемочный тест.

Пример для конкретизации: Скажем, вы угадываете кубические размеры необработанногофайл данных, основанный на имени файла.Приемочный тест: общее количество элементов == размер файла (в расчете на 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 имеет преимущество в удобстве обслуживания, поскольку логика управления находится исключительно на нижнем уровне: приемочный тест.

Ответы [ 3 ]

5 голосов
/ 09 ноября 2011

Метод 5: логика короткого замыкания

if (someTest1() ||
    someTest2() ||
    // ...
    someTestN()) 
{
    // success    
}

Это эквивалентно (и, на мой взгляд, легче следовать, чем) вариантам 2 и 4, которые имитируют короткое замыканиеповедение с другими операциями управления потоком.

Вариант 3 очень похож, но немного более гибок;Это может быть хорошей идеей, если вам нужно применить один и тот же шаблон к различным наборам тестов, но это излишне, если у вас есть только один набор тестов.

Вариант 1 будет довольно удивительным для многих людей, поскольку исключения обычно используются только для непредвиденных событий;хотя, если тесты построены так, что обнаружение успеха происходит где-то в глубокой цепочке вызовов, тогда это может быть более удобным, чем возвращать возвращаемое значение обратно.Это, безусловно, потребует документирования, и вы должны бросить тип со значимым именем (например, success), и быть осторожным, чтобы его не поймал какой-либо механизм обработки ошибок.Исключения, как правило, намного медленнее, чем обычные функции, поэтому имейте это в виду, если производительность является проблемой.Сказав все это, если бы я соблазнился использовать исключения здесь, я бы, конечно, искал способы упростить структуру тестов, чтобы сделать возвращаемое значение более удобным.

3 голосов
/ 09 ноября 2011

Ну, я бы действительно выбрал самое простое и простое решение:

bool succeeded;

if (!succeeded)
    succeeded = someTest1();
if (!succeeded)
    succeeded = someTest2();
if (!succeeded)
    succeeded = someTest3();
if (!succeeded)
    succeeded = someTestN();

Я могу поспорить с другими решениями, но подводя итог: просто хорошо, сложно плохо.

0 голосов
/ 09 ноября 2011

Я думаю, что первый метод более читабелен, основан на c ++ и проще в обслуживании, чем другие.Gotos и do-while-false добавляют немного путаницы.Есть варианты всех этих методов, но я предпочитаю первый.

...