Оператор Switch вместо нескольких вложенных if-else?

Я столкнулся с ситуацией, когда у меня есть куча «систем», которые необходимо инициализировать последовательно, при этом следующая система инициализируется только в том случае, если все последующие системы успешно инициализированы.

Это привело меня к целому ряду вложенных операторов if-else. Вот некоторый псевдокод для визуализации.

bool mainInit () {
    if (!system1Init ()) {
        reportError ();  // some error reporting function
    }
    else {
        if (!system2Init ()) {
            reportError ();
        }
        else {
            if (!system3Init ()) {
            // ... and so on

Я считаю, что это начинает выглядеть как беспорядок, когда вы получаете даже несколько уровней.

Теперь я подумал об использовании вместо этого оператора switch, начиная с первого случая и переходя к другим случаям в случае успеха, прерываясь только в случае ошибки.

bool mainInit () {

    switch (1) {
    case 1:
        if (!system1Init ()) {
            reportError ();
            break;
        }
    case 2:
        if (!system2Init ())
            reportError ();
            break;
        }
    // ....
}

Теперь мне это нравится намного больше. Я нахожу его намного легче читать, особенно с некоторыми приличными комментариями, но я довольно новичок в программировании.

Итак, мой вопрос: видя, что это не то, как традиционно используются операторы switch (по крайней мере, из того, что я видел), приемлемо ли что-то подобное или это будет считаться дурным тоном?

Будучи новичком в программировании, я стараюсь не развивать слишком много вредных привычек, которые могут расстраивать и усложнять жизнь другим программистам в будущем.

Я провел поиск, но большая часть того, что я нашел, была связана с заменой цепочек операторов if-else if, а не с заменой вложенных.


person liquidhand    schedule 27.09.2014    source источник
comment
Что следует за всеми операторами if? Что возвращает mainInit?   -  person Beta    schedule 28.09.2014
comment
Как насчет return false или throw ... в случае неудачи?   -  person Mike Seymour    schedule 28.09.2014
comment
Вам не обязательно делать else { if() { большинство людей делают else if() {.   -  person Galik    schedule 28.09.2014
comment
@Beta, извините, возвращает true, если все системы инициализируются успешно, и false, если есть какие-либо сбои.   -  person liquidhand    schedule 28.09.2014


Ответы (4)


Ссылайтесь на все системы в массиве, например std::vector<mySystem*>, и последовательно перебирайте их, прерывая при первом сбое. Таким образом, весь ваш код сокращается до менее чем 5 строк кода даже для 500+ систем.

Предложенный хак с коммутатором — плохой пример решения проблемы XY: ваша настоящая проблема в том, что у вас нет массив систем и используют именованные переменные, тем самым устраняя все варианты более гибкого использования всех систем, как в цикле.

person Niels Keurentjes    schedule 27.09.2014
comment
Я сделал ошибку в коде в своем вопросе, я не обрабатываю системные объекты напрямую (я даже не уверен, есть ли под всем этим объект) Я инициализирую части библиотеки c для использования. Я извиняюсь. Наверное, я не совсем понял, о чем я должен был спросить. Тогда я бы использовал вектор функции *? - person liquidhand; 28.09.2014
comment
Я не думаю, что этот вопрос был проблемой XY, поскольку пользователь сначала задал правильный вопрос и дал возможный ответ о том, что, по его мнению, он мог сделать. XY — это когда пользователь только спрашивает, должен ли я сделать переключение, не указывая сначала настоящую проблему. - person Alexis Wilke; 28.09.2014

Предполагая, что все ваши system#Init() вызовы известны во время компиляции, вы можете очень легко поместить их в таблицу, а затем выполнить итерацию по этой таблице.

typedef (*system_init)(void);

system_init initialization_functions[] =
{
    system1Init,
    system2Init,
    system3Init,
      ...
    systemNInit
};

bool mainInit()
{
    for(size_t idx(0); idx < sizeof(initialization_functions) / sizeof(initialization_functions[0]); ++idx)
    {
        if(!initialization_functions[idx]())
        {
            ReportError();
            return false;
        }
    }
    return true;
}

Однако ваш существующий код выглядит некорректным, поскольку первый mainInit() вызывает только system1Init(), а затем завершает работу. Вероятно, не то, что вы хотели в первую очередь.

if(!system1Init())
{
    ReportError();
    return false;
}
// if you add an else, the system2Init() does not get called
// even if system1Init() succeeds
if(!system2Init())
{
    ReportError();
    return false;
}
[...]
return true;

Переключатель решит вашу проблему? Не так, как было написано. То есть, если вы хотите вызвать функцию mainInit() со счетчиком, это может быть полезно. Drupal использует этот механизм:

bool mainInit(int idx)
{
    bool r(true);
    switch(idx)
    {
    case 1:
        r = system1Init();
        break;

    case 2:
        r = system2Init();
        break;

    [...]
    }
    if(!r)
    {
        ReportError();
    }
    return r
}

Обратите внимание, что механизм таблицы работает так же, как переключатель. Пока весь код находится в функциях systemNInit() (а так и должно быть), переключатель ничего не добавляет, так что можно было бы сделать и что-то вроде этого:

bool mainInit(int idx)
{
    if(idx < 0 || idx >= sizeof(initialization_functions) / sizeof(initialization_functions[0]))
    {
        throw std::range_error("index out of bounds");
    }
    if(!initialization_functions[idx]())
    {
        ReportError();
        return false;
    }
    return true;
}

Вызов mainInit() с индексом может быть полезен, если вы хотите правильно "деинициализировать":

int main()
{
    for(size_t idx(0); idx < ...; ++idx)
    {
        if(!mainInit(idx))
        {
            while(idx > 0)
            {
                --idx;
                mainDeinit(idx);
            }
            exit(1);
        }
    }
    ...app do something here...
}
person Alexis Wilke    schedule 27.09.2014

Используйте настраиваемые исключения с четкими сообщениями об ошибках и добавьте штамп try-catch-report-die вокруг кода в main(). Исключения существуют специально для того, чтобы ваш случай выглядел хорошо, делая неявным «плохой путь».

void initX() { ...; throw std::invalid_argument_exception("..."); }

int main() {
    try {
        init1(); init2(); ... run();
        return 0;
    } catch (std::exception const& e) {
        log(e.what()); exit 42;
    }
}
person bobah    schedule 27.09.2014

Я бы сделал это так:

bool mainInit () {
  if (!system1Init ()) {
    return(false);
  }

  if (!system2Init ()) {
    return(false);
  }

  if (!system3Init ()) {
    return(false);
  }

  //...                 

  return(true);
}

//...

if(!mainInit()) {
  reportError();
}
person Beta    schedule 27.09.2014