Считаете ли вы эту технику "ПЛОХОЙ"?

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

Итак, я делаю трюк:

do
{
   bool isGood = true;

   .... some code

   if(!isGood)
       break;

   .... some more code

   if(!isGood)
       break;

   .... some more code

 } while(false);

 ..... some other code, which has to be executed.

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

Некоторые из моих коллег не понравились, и они назвали это "плохой практикой". Я лично нахожу этот подход довольно гладким. Но как вы думаете?

Ответ 1

Плохая практика, это зависит.

То, что я вижу в этом коде, - очень творческий способ написать "goto" с меньшим количеством серосодержащих ключевых слов.

Существует несколько альтернатив этому коду, который может или не может быть лучше, в зависимости от ситуации.

Решение do/while

Ваше решение интересно, если у вас много кода, но будет оценивать "выход" этой обработки в некоторых ограниченных точках:

do
{
   bool isError = false ;

   /* some code, perhaps setting isError to true */
   if(isError) break ;
   /* some code, perhaps setting isError to true */
   if(isError) break ;
   /* some code, perhaps setting isError to true */
}
while(false) ;

// some other code   

Проблема в том, что вы не можете легко использовать свой разрыв if (isError); " это цикл, потому что он выйдет только из внутреннего цикла, а не из блока do/while.

И, конечно, если сбой находится внутри другой функции, функция должна возвращать какой-то код ошибки, и ваш код не должен забывать правильно интерпретировать код ошибки.

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

Вызов goto a... goto

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

По крайней мере, это покажет, что что-то может быть неправильным с кодом и пригласить рецензентов для проверки или аннулирования вашего решения.

Вы все равно должны открыть блок и вместо нарушения использовать goto.

{
   // etc.
   if(/*some failure condition*/) goto MY_EXIT ;
   // etc.

   while(/* etc.*/)
   {
      // etc.
      for(/* etc.*/)
      {
         // etc.
         if(/*some failure condition*/) goto MY_EXIT ;
         // etc.
      }
      // etc.
      if(/*some failure condition*/) goto MY_EXIT ;
      // etc.
   }

   // etc.
}

MY_EXIT:

// some other code   

Таким образом, когда вы выходите из блока через goto, вы не можете обойти какой-либо объект-конструктор с goto (что запрещено С++).

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

Конечно, теперь вы можете выйти из своего процесса из нескольких точек, от глубины вложенности множества циклов, поэтому, если это проблема...

попытаться/поймать

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

try
{
   // All your code
   // You can throw the moment something fails
   // Note that you can call functions, use reccursion,
   // have multiple loops, etc. it won't change
   // anything: If you want to exit the process,
   // then throw a MyExitProcessException exception.

   if(/* etc. */)
   {
      // etc.
      while(/* etc.*/)
      {
         // etc.
         for(/* etc.*/)
         {
            // etc.
            if(/*some failure condition*/) throw MyExitProcessException() ;
            // etc.
         }
         // etc.

         callSomeFunction() ;
         // the function will throw if the condition is met
         // so no need to test a return code

         // etc.
      }
      // etc.
   }

   // etc.
}
catch(const MyExitProcessException & e)
{
   // To avoid catching other exceptions, you should
   // define a "MyExitProcessException" exception
}

// some other code

Если какое-либо условие в приведенном выше коде или внутри некоторых функций, вызываемых указанным выше кодом, не выполняется, то генерируйте исключение.

Это несколько более весомое, чем ваше решение, но имеет те же преимущества и может даже прервать обработку из внутренних циклов или из внутренних вызываемых функций.

Обсуждение

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

Предположим, что.

Если вы можете закодировать его с помощью try/catch, сделайте это. Чтобы прервать сложную часть кода, выбрасывание исключения - правильное решение (факт, что вы можете добавить информацию об ошибке/успехе внутри ваш объект исключения не следует недооценивать). После этого у вас будет более четкий код.

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

Никто не может отрицать, что ваше решение - это прославленная гото. Не будет кода goto-spaghetti, потому что do/while не позволит вам сделать это, но он все еще является семантическим goto. Это может быть причиной, по которой некоторые могут найти этот код "плохо": они пахнут goto, не набрав его ключевого слова четко.

В этом случае (и только в этом представлении с профилированным подтверждением) ваше решение кажется Ok, и лучше, чем альтернатива, используя if), но более низкого качества (IMHO), чем решение goto, которое, по крайней мере, t скрывается за ложным циклом.

Заключение

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

Итак, в порядке предпочтения:

  • Использовать try/catch
  • Используйте goto
  • Используйте цикл do/while
  • Использовать ifs/nested ifs

Ответ 2

Вы почти просто маскируете "goto" как фальшивый цикл. Любите ли вы gotos или нет, вы были бы так же далеко вперед, используя настоящий неприкрытый переход.

Лично я просто напишу его как

bool isGood = true;

   .... some code

   if(isGood)
   {
   .... some more code
   }

   if(isGood)
   {
   .... some more code
   }

Ответ 3

Зачем использовать фальшивый цикл? Вы можете сделать то же самое с помощью метода, и это, вероятно, не будет считаться "плохой практикой", поскольку это более ожидаемо.

someMethod()
{
   .... some code

   if(!isGood)
       return;

   .... some more code

   if(!isGood)
       return;

   .... some more code

 }

Ответ 4

У вас сложный нелинейный поток управления внутри трудно распознаваемой идиомы. Итак, да, я думаю, что эта техника плохая.

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

Ответ 5

Это запутанное и запутанное, я бы сразу его отказал.

Рассмотрим эту альтернативу:

private void DoSomething()
{
   // some code
   if (some condition)
   {
      return;
   }
   // some more code
   if (some other condition)
   {
      return;
   }
   // yet more code
}

Также рассмотрите разбивку кода выше на несколько методов.

Ответ 6

public bool Method1(){ ... }
public bool Method2(){ ... }

public void DoStuff(){
    bool everythingWorked = Method1() && Method2();
    ... stuff you want executed always ...
}

Причина, по которой это происходит, связана с чем-то вроде логики короткого замыкания. Метод2 не будет вызываться, если Method1 возвращает false.

Это также дает дополнительное преимущество, что вы можете разбить свой метод на более мелкие куски, что будет проще unit test.

Ответ 7

То, что вы пытаетесь сделать, это восстановление нелокальных сбоев. Для этого используется goto. Используй это. (на самом деле, для этого предназначена обработка исключений, но если вы не можете использовать это, "goto" или "setjmp/longjmp" - это следующая лучшая вещь).

Этот шаблон, шаблон if(succeeded(..)) и 'goto cleanup', все 3 семантически и структурно эквивалентны. Используйте то, что наиболее часто встречается в вашем проекте кода. Там много значения в последовательности.

В какой-то момент я бы предостерег от if(failed(..)) break; в том, что вы вызываете неожиданный результат, если попытаетесь установить петли:

do{
   bool isGood = true;
   .... some code
   if(!isGood)
       break;
   .... some more code
   for(....){
      if(!isGood)
          break; // <-- OOPS, this will exit the 'for' loop, which 
                 //  probably isn't what the author intended
      .... some more code
   }
} while(false);
..... some other code, which has to be executed.

Ничего, кроме goto cleanup и if(succeeded(..)), поэтому я бы рекомендовал использовать один из этих двух.

Ответ 8

В основном вы только что описали goto. Я использую goto в C все время. Я не считаю это плохим, если вы не используете его для эмуляции цикла (никогда не делайте этого!). Мое типичное использование goto в C - эмулировать исключения (C не имеет исключений):

// Code

if (bad_thing_happened) goto catch;

// More code

if (bad_thing_happened) goto catch;

// Even more code

finally:

// This code is executed in any case
// whether we have an exception or not,
// just like finally statement in other
// languages

return whatever;

catch:

// Code to handle bad error condition

// Make sure code tagged for finally
// is executed in any case
goto finally;

За исключением того, что catch и, наконец, имеет противоположный порядок, я не понимаю, почему этот код должен быть BAD только потому, что он использует goto, если реальный код try/catch/finally работает точно, как это и просто не использует goto. Это бессмысленно. И, таким образом, я не понимаю, почему ваш код должен быть помечен как BAD.

Ответ 9

У меня нет проблем с этим, если код читаем.

Ответ 10

Я использовал эту идиому годами. Я считаю предпочтительным аналогичное вложенное или серийное ifs, "goto onError" или наличие нескольких возвратов. Вышеприведенный пример с вложенным циклом - это то, на что нужно обратить внимание. Я всегда добавляю комментарий к "do", чтобы дать понять кому-либо новому в идиоме.

Ответ 11

К лучшему или худшему, я использовал конструкцию в нескольких местах. Начало его явно задокументировано:

    /* This is a one-cycle loop that simplifies error handling */
    do
    {
        ...modestly complex code, including a nested loop...
    } while (0);

Это на C, а не на С++, поэтому исключения не являются опцией. Если бы я использовал С++, я бы серьезно подумал об использовании исключений для обработки исключений. Повторная тестовая идиома, предложенная Джереми, также разумна; Я использовал это чаще. RAII тоже мне очень помог; К сожалению, C не поддерживает это легко. И использование большего количества функций поможет. Обработка разрывов внутри внутри вложенного цикла выполняется повторным тестом.

Я бы не классифицировал его как отличный стиль; Я бы не стал автоматически классифицировать его как "ПЛОХО".

Ответ 12

Рефакторинг. Чистое решение в большинстве случаев будет состоять в том, чтобы разбить этот код на меньшую вспомогательную функцию, из которой вы можете вернуться, а не вырваться из своего не-фактически-цикла.

Затем вы можете заменить свой перерыв на возврат, и теперь люди могут сразу понять ваш код при чтении, вместо того, чтобы останавливаться и удивляться, почему вы сделали этот цикл, который фактически не зацикливается.

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

Ответ 13

Это зависит от того, каковы альтернативы. Вы должны признать, что код, который вы опубликовали, несколько уродливый. Я бы не сказал, что это понятно. Это своего рода хак. Так что если использовать какое-то другое решение для кодирования будет хуже, тогда хорошо. Но если у вас есть лучшая альтернатива, не позволяйте оправданию "это достаточно хорошо", успокаивайте вас.

Ответ 14

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

Как использовать более обычные функции языка, такие как функции?

bool success = someSensibleFunctionName();

if(success)
{
   ...
}

someCommonCodeInAnotherFunction();

Ответ 15

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

Ответ 16

Если ваш код делает что-то другое, кроме простого смысла конструкций, это хороший знак, который вы отважились на "милую" территорию.

В этом случае у вас есть "цикл", который будет запускаться только один раз. Любой читатель кода должен будет сделать двойной прием, чтобы выяснить, что происходит.

Если случай, когда он не является "хорошим", действительно является исключительным, то лучшим выбором будет выбор исключений.

Ответ 17

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

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

Ответ 18

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

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

Ответ 19

Для меня то, что вы делаете, плохо во многих отношениях. Цикл можно заменить, поместив этот код в метод.

Я лично считаю, что если вам нужно положить! перед вашими условиями вы ищете неправильную вещь. Для удобочитаемости сделайте логическое соответствие тому, что вы проверяете. Вы действительно проверяете, есть ли ошибка или какое-то плохое состояние, поэтому я бы предпочел:


If (isError)
{
   //Do whatever you need to do for the error and
   return;
}
над

If (!isGood)
{
  //Do something
}

Итак, проверьте, что вы действительно хотите проверить, и сохраните проверки исключений до минимума. Ваша цель должна быть читабельна из-за сложностей. Подумайте о бедной душе, которая должна прийти и поддерживать ваш код.

Одна из первых вещей, над которыми я работала 28 лет назад, была программа Fortran, которая всегда нуждалась в проверке наличия графического устройства. Кто-то принял большое решение назвать логическое значение для этого LNOGRAF, поэтому, если бы имелось графическое устройство, это было бы ложным. Я считаю, что это было установлено таким образом из-за ложной эффективности, проверка на устройство вернула ноль, если было графическое устройство. На протяжении всего кода все проверки должны были видеть, доступно ли графическое устройство. Он был полон:

Если (.NOT. LNOGRAF)

Я не думаю, что был один:

Если (LNOGRAF)

в программе. Это использовалось в программном обеспечении планирования полета для B-52 и крылатых ракет. Это определенно научило меня называть мои переменные тем, что я действительно проверяю.

Ответ 20

Как насчет функционального подхода?


        void method1()
        {
            ... some code
            if( condition )
                method2();
        }

        void method2()
        {
            ... some more code
            if( condition )
                method3();
        }

        void method3()
        {
            ... yet more code
            if( condition )
                method4();
        }

Ответ 21

Я бы сказал, что ваше решение может быть правильным решением, но это зависит. Пол Томблин опубликовал ответ, который лучше (серия, если трубки)... если он может быть использован.

Решение Paul не может использоваться, когда есть дорогостоящие инициализации объектов по пути через ваш цикл. Если созданные объекты используются на последующих этапах, лучше сделать while (0).

Тем не менее, переменное именование должно быть улучшено. Кроме того, зачем многократно использовать переменную "escape"? Вместо этого явным образом поймайте каждое отдельное условие ошибки и разбейте цикл таким образом, чтобы было очевидно, что вызывает разрыв в каждой точке.

Кто-то предложил использовать вызовы функций. Опять же, это может быть не идеальная декомпозиция, если она добавляет ненужную сложность вызовам функций из-за количества аргументов, которые могут быть переданы на каждом шаге.

Другие считают, что это трудная для понимания идиома. Ну, сначала вы могли бы поместить комментарий, как было предложено в верхней части цикла. Во-вторых, do-while (0) является обычной и распространенной идиомой в макросах, которую все программисты C должны сразу распознавать, поэтому я просто не покупаю это.

Ответ 22

Во-первых, если вы просто хотите получить ответ на вопрос, является ли эта структура кода или идиома "плохим", я бы подумал, что это так. Тем не менее, я думаю, что это симптом плохой декомпозиции, а не код, который у вас есть, "хороший" или "плохой".

Я бы подумал, что гораздо лучший анализ и рефакторинг должны быть сделаны, чтобы иметь возможность дальше решать проблему, а не просто смотреть на код. Если вы можете сделать что-то вроде:

if (condition1(...) && condition2(...) && condition3(...) && ... && conditionN(...)) {
    // code that ought to run after all conditions
};
// code that ought to run whether all conditions are met or not

Тогда я думаю, что это было бы более "читаемым" и более идиоматичным. Таким образом, вы можете выполнять такие функции, как:

bool conditionN(...) {
    if (!real_condition) return false;
    // code that ought to run
    return true;
};

Вы получаете выгоду от лучшей декомпозиции и помощи от компилятора для получения необходимой короткой цепи, которая && & принесу. Компилятор может даже вставить код в функции для получения лучшего кода, чем если бы вы выполняли кодировку с ручным кодированием.

Ответ 23

Я думаю, что в технике нет ничего принципиального. Я думаю, что я бы удостоверился, что bool назван чем-то более описательным, чем isGood. Хотя, теперь, когда я смотрю на это, почему бы не сработать, чтобы весь код, находящийся в цикле, в один блок if (! IsGood)? Цикл выполняется только один раз.

Ответ 24

Если разделить код между if (! isGood) break; на отдельные функции, можно получить десятки функций, содержащих только пару строк, чтобы работники ничего не упрощали. Я не мог использовать return, потому что я не готов покинуть эту функцию, но я все еще хочу сделать там. Я согласен с тем, что, возможно, мне просто нужно согласиться на отдельное условие if (isGood) {...} для каждой части кода, которую я хочу выполнить, но иногда это приведет к ЛОЖЕ фигурных скобок. Но я предполагаю, что я согласен с тем, что людям не нравится такой тип строительства, поэтому условия для всех ветров! Спасибо за ваши ответы.

Ответ 25

Мета-комментарий: Когда вы кодируете, ваша цель должна быть четкой, поддерживающей код в первую очередь. Вы не должны отказываться от удобочитаемости на алтаре эффективности, если вы не профилируете и не доказываете, что это необходимо, и что оно действительно улучшает ситуацию. Следует избегать трюков разумного джедая. Всегда думайте, что следующий парень, чтобы поддерживать ваш код, - это большой психопат с вашим домашним адресом. Я, например.

Ответ 26

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

Почему бы просто не использовать

if(isGood)
{
...Execute more code
}

?

Ответ 27

Я думаю, что люди здесь не честны.

Если я, как твоя команда, увижу код вроде этого, ты будешь за один на один, и помечен как потенциальная проблема для команды, поскольку этот фрагмент кода особенно ужасен.

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

Ответ 28

Это исключения. Вы не можете продолжать логику, потому что что-то пошло не так. Даже если восстановление тривиально, это все еще исключение. Если у вас есть веская причина не использовать исключения, например, когда вы программируете на языке, который их не поддерживает, используйте условные блоки, а не циклы.