Иногда вам нужно пропустить выполнение части метода при определенных некритических условиях ошибки. Вы можете использовать для этого исключения, но исключения обычно не рекомендуются в нормальной логике приложения, только для нештатных ситуаций.
Итак, я делаю такой трюк:
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.
Я использую «фальшивый» цикл, который выполняется один раз, и я могу прервать его с помощью перерыв или Продолжать.
Некоторым из моих коллег это не понравилось, и они назвали это «плохой практикой». Я лично считаю такой подход довольно удачным. Но что вы думаете?
Почему проверка "isGood" несколько раз? Возможно, это будет изменено, например, моей другой веткой?
Потому что он иногда изменяет это в коде.
Лично меня больше отталкивает повторное использование слова «isGood», чем использование слова «break». Если есть 2 точки, в которых что-то может быть нехорошим, скорее всего, для этого есть 2 разные причины. Если принуждение каждого к логическому значению вообще приведет к добавлению какого-либо кода, просто сделайте «break» условным для чего-либо.
Кстати, если коллеги честно заявляют, что не могут прочитать ваш код, тогда у вас есть проблема даже в (редком) случае, когда вы полностью правы, а они ошибаются. Код должен быть читаемым по крайней мере для 95% программистов, а не только для 50%, которые не боятся контролируемых переходов вперед.
Однако остерегайтесь людей, которые говорят: «Это плохая практика», когда на самом деле «я бы поступил иначе».
Я видел и использовал, когда есть сложная операция получения ресурсов, в которой, если какой-либо из шагов должен быть отменен, если что-то не удалось.
Решение try / catch не кажется правильным, но является элегантной альтернативой, у goto есть проблемы, потому что деструктор не будет вызываться. Я бы предпочел выделить код в его собственную процедуру и вместо этого использовать return.
мне кажется, что переменная хорошая не нужна - просто "ломай" напрямую





Я думаю, что в принципе нет ничего плохого в технике. Я думаю, что я бы позаботился о том, чтобы имя bool было более описательным, чем isGood. Хотя теперь, когда я смотрю на это, почему бы не поместить весь код, который находится в цикле, в один блок if (! IsGood)? Цикл выполняется только один раз.
Потому что "isGood" может быть установлено в нескольких местах фальшивого цикла.
У меня нет проблем с этим, пока код читабелен.
Вы в значительной степени просто маскируете «goto» под фальшивую петлю. Нравится вам goto или нет, вы бы так же далеко впереди, используя настоящий неприкрытый goto.
Лично я бы просто написал это как
bool isGood = true;
.... some code
if (isGood)
{
.... some more code
}
if (isGood)
{
.... some more code
}
Вложение операторов if на самом деле было бы лучшей идеей, поэтому нет необходимости снова проверять isGood, если он оценивает false в первый раз.
Вложение увеличивает уровень отступа, что затрудняет чтение кода. Избыточные проверки isGood не повредят, так что я думаю, что все в порядке.
Вложение if приводит к коду уклона, из-за которого товарищи по команде засовывают карандаши вам в глаза.
Это намного лучше, чем GOTO. Вы можете ясно видеть область, из которой вы «выпрыгиваете». <br> Мой код не вводит другого уровня вложенности. Обычно эти вещи уже находятся в других условиях. Не люблю много уровней отступов.
@ Ma99uS - если вы не собираетесь принимать ответ, который с вами не согласен, зачем вообще задавать этот вопрос?
@ Ma99uS - Ответ Пола Томблина правильный. Ваш код - это то, что я бы назвал «мерзостью». Проголосовать за вопрос, если не оценил хороший совет.
@Paul Tomblin, Simucal - Почему ты подумал, что я не приму этот ответ? Прочтите мой комментарий позже в ветке. Я согласился с критикой. В следующий раз не делайте преждевременного заключения о людях, которых вы не знаете.
У меня сложилось впечатление, что мы ведем здесь диалог, в котором людей нужно убеждать, а не заставлять принимать лучший ответ. Или у меня есть какое-то время, в течение которого я должен принять ответ?
В вас сбил велоцираптор: xkcd.com/292. Лучше всего сохранять спокойствие и надеяться, что полиция goto не поймет, что цикл Любые является «замаскированным goto», потому что тогда у нас действительно будут проблемы.
@ Ma99uS - Я не имел в виду «принять» в смысле нажатия галочки. Я имел в виду тот факт, что я привел несколько причин, почему то, что вы написали, плохо, и ваш ответ был «Это намного лучше, чем GOTO» и другие причины, по которым вы не планируете прислушиваться к моему совету.
@ Ma99uS - вы обосновали свой GOTO-подобный код комментариями, которые вам не нравятся глубокие отступы и вы не хотите вводить функции. Короче говоря, вы, кажется, предпочитаете императивные методы структурированного программирования. Это может значительно нарушить синхронизацию вас с коллегами.
Я полностью с этим согласен, @ Ma99uS. Goto иногда необходим, и трюк с циклом на самом деле изящный ... Но в большинстве случаев это просто плохой код, и есть лучший способ написать его, который будет выглядеть лучше.
У вас сложный нелинейный поток управления внутри трудно распознаваемой идиомы. Так что да, я считаю эту технику плохой.
Возможно, стоит потратить время на то, чтобы выяснить, можно ли это написать немного лучше.
Я работал с языками, где это обычная идиома, например, с «блокировать ... ломать ... конечный блок». Это несложно, если к этому привыкнуть: прыжки строго вперед и все до конца блока. На самом деле это проще, чем if / else, не говоря уже о if, else if, else.
Я понимаю, что вложенные if могут довольно быстро стать уродливыми. Я бы предпочел не использовать эти идиомы. Я сильно подозреваю, что можно было бы очистить этот код до чего-то, что читается намного проще, но трудно сказать, как это сделать, не зная специфики.
Согласовано - обычно, когда я использовал блокировку / прерывание / конечную блокировку на этом языке, это было для тех случаев, когда у меня был подробный список условий, которые нужно проверить, прежде чем продолжить. В случаях, когда вы серьезно работаете на каждом этапе, я бы сказал, что этот подход вряд ли будет лучшим.
Зачем использовать фальшивую петлю? Вы можете сделать то же самое с методом, и это, вероятно, не будет считаться «плохой практикой», поскольку это более ожидаемо.
someMethod()
{
.... some code
if (!isGood)
return;
.... some more code
if (!isGood)
return;
.... some more code
}
Распространение нескольких возвратов на все ваши функции также обычно является плохой практикой. Если вы используете язык, не использующий сборщик мусора (например, C, C++), вы подвергаетесь утечкам памяти.
Я бы, наверное, согласился в этом случае, учитывая намек на «еще немного кода» между ними. Хотя многократный возврат может быть хорошей стратегией для предотвращения глубокого вложения. Обычно быть наверху, а не «разбрызгиваться» на протяжении всей функции.
код внизу метода должен выполняться всегда. Поэтому я не могу вернуться из функции раньше времени. Я и я не хотим создавать больше функций.
Множественные возвраты - это плохо, если вы не используете RAII / общие указатели. Однако с этими инструментами раннее программирование определенно полезно и предпочтительнее глубоко вложенной логики, как сказано в g.
Я думаю, что неправильно приравнивать путаницу множественных возвратов к путанице исходного кода, который сочетает в себе сложную логику потока управления (будь то его `` фальшивый цикл '' или переписанный в соответствии с ответом Томблина) с логикой предметной области (`` ... больше кода здесь...'). +1 о введении функции.
@ Ma99uS, почему ты не хочешь создать больше функций? Есть ли у вас какие-то религиозные или моральные возражения против них?
Ничего не имею против функций. Но в некоторых случаях, если вы «разорвите» часть кода на отдельную функцию, вам придется передавать в них ТОННЫ параметров и обратно. Это создало бы совершенно другие проблемы с читабельностью.
@ Ma99uS, если у вас есть сегменты кода, зависящие от множества локальных переменных, вы должны ОБЯЗАТЕЛЬНО реорганизовать его в любом случае.
@ Клин +1. @ Ma99uS: если у вас есть «тонны локальных переменных», относящиеся к блоку «больше кода» и дополнительно, у вас есть переменные, относящиеся к потоку управления (isGood и т. д.), То вы наверняка смешиваете проблемы внутри одной функции.
@Larry +1, определенно смешиваю проблемы. Ваш код должен быть структурирован таким образом, чтобы его разбиение на разные функции не требовало множества локальных переменных.
@ Ma99uS, если ваш код настолько большой и беспорядочный, что извлечение отдельных функций было бы болезненным, то он слишком большой и беспорядочный. Реорганизуйте свой путь в более счастливое место. Начните с низко висящих фруктов. Если вы поддадитесь такому мышлению («это слишком сложно»), ваш код будет становиться все хуже и хуже.
Хотя мне нравятся функции и методы, мне очень не нравится множественный возврат. Как сказал @Larry, это так же сбивает с толку, как несколько разрывов в оригинале или использование gotos. Так что делать это не лучше.
Это зависит от того, какие есть альтернативы. Вы должны признать, что опубликованный вами код несколько уродлив. Я бы не сказал, что это понятно. Это своего рода взлом. Так что, если использование другого решения для кодирования будет хуже, тогда хорошо. Но если у вас есть альтернатива получше, не позволяйте извинению "это достаточно хорошо" утешать вас.
Знаете, мне вроде нравится этот код, я не думаю, что он УЖАСНЫЙ. Я даже думаю, что это вроде как "умно". Но более приемлемо, если (условие) {делать вещи}, конечно, легче понять.
public bool Method1(){ ... }
public bool Method2(){ ... }
public void DoStuff(){
bool everythingWorked = Method1() && Method2();
... stuff you want executed always ...
}
Причина, по которой это работает, связана с так называемой логикой короткого замыкания. Method2 не будет вызываться, если Method1 вернет false.
Это также имеет дополнительное преимущество, заключающееся в том, что вы можете разбить свой метод на более мелкие фрагменты, которые будет легче выполнять модульное тестирование.
Отлично работает, если исходный код не включает много временного состояния в локальных переменных.
Вы всегда можете продвинуть эту операцию, чтобы она жила в своем собственном классе, и тогда у вас может быть любое временное состояние, которое вы хотите.
Будьте осторожны с порядком вызова Method1 и Method2. Я бы предпочел, чтобы вы работали & = Method1 (); работал & = Method2 ();
Почему? Не похоже, что порядок оценки внезапно изменится.
@Matt - порядок выполнения жестко определяется C / C++, это не одна из тех вещей, которые зависят от реализации. Не нужно прыгать через обручи.
Думаю, мне придется согласиться с вашими коллегами только из-за удобочитаемости, на первый взгляд непонятно, чего вы пытаетесь достичь с помощью этого кода.
Почему бы просто не использовать
if (isGood)
{
...Execute more code
}
?
То, что вы пытаетесь сделать, - это нелокальное восстановление после сбоя. Для этого и нужен goto. Используй это. (на самом деле, это то, для чего нужна обработка исключений - но если вы не можете это использовать, лучше всего подойдут 'goto' или 'setjmp / longjmp').
Этот шаблон, шаблон if (succeeded(..)) и «очистка перехода» - все три семантически и структурно эквивалентны. Используйте тот, который наиболее часто встречается в вашем проекте кода. Последовательность очень ценится.
Я бы предостерегал против 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(..)) нет такого сюрприза, поэтому я бы посоветовал вместо этого использовать один из этих двух.
Очень странная идиома. Он использует цикл для чего-то не предназначенного и может вызвать путаницу. Я предполагаю, что это будет охватывать более одной страницы, и для большинства людей будет сюрпризом, что это никогда не запускается более одного раза.
Как насчет использования более привычных языковых функций, таких как функции?
bool success = someSensibleFunctionName();
if (success)
{
...
}
someCommonCodeInAnotherFunction();
По сути, вы только что описали 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 и finally имеют противоположный порядок, я не понимаю, почему этот код должен быть ПЛОХОЙ только потому, что он использует goto, если настоящий код try / catch / finally работает точно как это и просто не использует goto. Это бессмысленно. И поэтому я не понимаю, почему ваш код должен быть помечен как ПЛОХОЙ.
с goto вы не видите, где будет прыжок. В моем коде область видимости очень ясна. У вас фигурные скобки, и вы выпрыгиваете из них.
@ Ma99uS: при переходе к пункту назначения указывается пункт назначения. С вашим кодом читатель должен обвести фигурные скобки. Я не вижу улучшения.
@ Shog9 Проще отследить фигурные скобки, чем точно определить goto и его метки. Но оба все равно плохие. Текущее принятое решение имеет отличный заключение по этому поводу.
@Cawas: Согласно принятому ответу, goto предпочтительнее всего остального (если у вас нет try / catch, которого у вас нет в простом C). Вы дочитали принятый ответ до конца, не так ли? ;-)
Да, и он действительно говорит, что они все плохие (потому что он уклоняется от того, чтобы сказать, что что-то действительно так хорошо). Это просто список от наименее худшего к худшему. : D
Я считаю это плохой практикой. Я думаю, было бы более идиоматично и в целом более понятным, если бы вы сделали это функцией и изменили разрывы на возврат.
Если ваш код делает что-то иное, чем простой смысл имеющихся конструкций, это хороший знак, что вы отважились на «милую» территорию.
В этом случае у вас есть «цикл», который запускается только один раз. Любому читателю кода нужно будет дважды подумать, чтобы понять, что происходит.
Если случай, когда это не «хорошо», действительно является исключительным, то лучшим выбором будет выбрасывание исключений.
Разделите свой код на более мелкие фрагменты функциональных элементов, чтобы вы могли разделить приведенное выше на функцию, которая возвращает вместо прерывания.
Я не знаю, является ли приведенное выше плохой практикой, но его читабельность немного нечеткая и может сбивать с толку других, которым, возможно, придется поддерживать исходный код.
Если разделить код между если (! isGood) перерыв; на отдельные функции, можно получить десятки функций, содержащих всего пару строк, так что исполнители ничего не упрощают. Я не мог использовать возвращаться, потому что я не готов покинуть функцию, я все еще хочу там заняться.
Я согласен с тем, что, вероятно, мне следует просто согласиться на отдельное условие if (isGood) {...} для каждой части кода, которую я хочу выполнить, но иногда это приводило к МНОГО фигурных скобок. Но я думаю, я согласен с тем, что людям не очень нравится такая конструкция, поэтому условия для всего ветры!
Спасибо за ваши ответы.
Вы неправильно поняли предложение. Вы создаете подпрограмму новый, которая содержит только код, который вы вставляете в свой фиктивный цикл. Тот же эффект, что и ваш фиктивный цикл, но он удаляет эту запутанную логику упорядочивания в свою собственную подпрограмму. Это упрощает понимание обоих фрагментов кода.
иметь более мелкие методы обычно лучше, чем иметь один массивный метод.
Это заставило бы меня создать отдельную функцию не потому, что это диктуется сегментами логики кода (как и должно быть), а просто потому, что в этом случае отдельная функция РАБОТАЕТ. Я не хочу, чтобы меня заставляли создавать функции в соответствии с этим требованием.
функция с конструкцией «фальшивый цикл» не обязательно велика, так что нет причин разбивать ее здесь.
You can use exceptions for that, but exceptions generally are not recommended in normal application logic, only for abnormal situations.
Нет ничего плохого в использовании исключений. Исключения являются частью логики приложения. Рекомендация относительно исключений заключается в том, что они должны быть относительно редкими во время выполнения.
Для меня то, что вы делаете, во многих отношениях плохо. Цикл можно заменить, поместив этот код в метод.
Я лично считаю, что если надо ставить! перед вашими условиями, значит, вы ищете не то. Для удобочитаемости сделайте логическое соответствие тому, что вы проверяете. Вы действительно проверяете, есть ли ошибка или плохое состояние, поэтому я бы предпочел:
над
If (isError)
{
//Do whatever you need to do for the error and
return;
}
If (!isGood)
{
//Do something
}
Так что проверьте, что вы действительно хотите проверить, и сведите количество проверок к минимуму. Ваша цель должна заключаться в удобочитаемости, а не в хитрости. Подумайте о бедной душе, которая придет с вами и будет поддерживать ваш код.
Одна из первых вещей, над которыми я работал 28 лет назад, была программа на Фортране, которая всегда должна была проверять, доступно ли графическое устройство. Кто-то принял важное решение вызвать логическое значение для этого LNOGRAF, поэтому, если бы было доступно графическое устройство, это было бы ложью. Я считаю, что это было настроено так из-за ложной эффективности, проверка устройства вернула ноль, если было графическое устройство. На протяжении всего кода все проверки заключались в том, чтобы увидеть, доступно ли графическое устройство. Он был полон:
Если (.NOT. LNOGRAF)
Не думаю, что был сингл:
Если (LNOGRAF)
в программе. Это использовалось в программном обеспечении планирования миссий для B-52 и крылатых ракет. Это определенно научило меня называть свои переменные по тому, что я действительно проверяю.
Хорошая точка зрения! «Проверить, что мне действительно нужно». Я с радостью это принимаю. Спасибо
Это запутанно и запутанно, я бы сразу отказался от этого.
Рассмотрим эту альтернативу:
private void DoSomething()
{
// some code
if (some condition)
{
return;
}
// some more code
if (some other condition)
{
return;
}
// yet more code
}
Также рассмотрите возможность разбиения приведенного выше кода на несколько методов.
удивил, что никто об этом не упомянул, просто кажется настолько очевидным использовать функцию и просто возвращать ее вместо кода OP. И он избегает goto, если вы не занимаетесь такими вещами.
Что ты имеешь в виду, @Greg? .G ответил примерно то же самое за 30 минут до этого: stackoverflow.com/questions/243967/…
Я использую эту идиому много лет. Я считаю, что это предпочтительнее аналогичных вложенных или последовательных if, «goto onError» или многократных возвратов. Приведенный выше пример с вложенным циклом - это то, чего стоит остерегаться. Я всегда добавляю комментарий к слову «делать», чтобы было понятно всем, кто не знаком с этой идиомой.
А как насчет функционального подхода?
void method1()
{
... some code
if ( condition )
method2();
}
void method2()
{
... some more code
if ( condition )
method3();
}
void method3()
{
... yet more code
if ( condition )
method4();
}
создание отдельных функций может быть проблемой в случае, если нужно будет отправлять и получать МНОГО параметров от каждой функции.
Только если параметров для начала много. Ясно, что он подходит не для всех случаев, но кое-что стоит учесть. Очевидно, что одни языки подойдут для этого лучше, чем другие.
Плохая практика, зависит от обстоятельств.
В этом коде я вижу очень творческий способ написать goto с менее пахнущими серой ключевыми словами.
У этого кода есть несколько альтернатив, которые могут быть или не могут быть лучше, в зависимости от ситуации.
Ваше решение интересно, если у вас много кода, но вы оцените "выход" из этой обработки в некоторых ограниченных точках:
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) break;» это цикл, потому что он выйдет только из внутреннего цикла, а не из вашего блока do / while.
И, конечно же, если сбой происходит внутри другой функции, функция должна возвращать какой-то код ошибки, и ваш код не должен забывать правильно интерпретировать код ошибки.
Я не буду обсуждать альтернативы, использующие «если» или даже вложенные «если», потому что, немного подумав, я нахожу их решения вашей проблемы хуже, чем ваши собственные.
Возможно, вам следует четко изложить тот факт, что вы используете 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 (что запрещено C++).
Эта проблема решает проблему выхода процесса из вложенных циклов (и использование 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
Если какое-либо условие в приведенном выше коде или внутри некоторых функций, вызываемых указанным выше кодом, не выполняется, генерируется исключение.
Это несколько тяжелее, чем ваше решение do / while, но имеет те же преимущества и может даже прервать обработку из внутренних циклов или из внутренних вызываемых функций.
Кажется, ваша потребность исходит из того факта, что у вас может быть сложный процесс для выполнения (код, вызовы функций, циклы и т. д.), Но вы хотите прервать его по какому-то условию (возможно, либо сбой, либо потому, что он преуспел раньше, чем было исключено) . Если вы можете переписать его по-другому, вы должны это сделать. Но, пожалуй, другого выхода нет.
Предположим, что.
Если вы можете закодировать это с помощью try / catch, сделайте это: Чтобы прервать сложный фрагмент кода, выбрасывание исключения - правильное решение (не следует недооценивать тот факт, что вы можете добавить информацию об ошибке / успехе в свой объект исключения). После этого у вас будет более четкий код.
Теперь, если вы находитесь в узком месте в скорости, решение проблемы с выброшенными исключениями в качестве выхода - не самый быстрый способ сделать это.
Никто не может отрицать, что ваше решение - это прославленный goto. Не будет кода goto-spaghetti, потому что do / while не позволит вам это сделать, но это все еще семантический goto. Это может быть причиной того, что некоторые могут посчитать этот код "плохим": они нюхают goto, не находя четко его ключевое слово.
Только в этом случае (и в этом случае с профилированной проверкой производительности) ваше решение кажется нормальным и лучше, чем альтернатива с использованием if), но с меньшим качеством (IMHO), чем решение goto, которое, по крайней мере, не скрывает себя за ложной петлей.
Насколько я понимаю, я считаю ваше решение креативным, но я бы придерживался решения с созданным исключением.
Итак, в порядке предпочтения:
Сама попытка дать такой подробный ответ заслуживает того, чтобы быть принятым. Я согласен с вашими соображениями о невозможности вырваться из внутренних петель. Код возврата неудачных функций все еще может быть сохранен, если вы используете целое число вместо bool в качестве условия прерывания (например, все значения> 0 указывают на ошибки)
Я бы поставил невложенные if как номер 2, но в остальном согласен.
В java я бы без колебаний использовал исключения в этом случае, но в C++ я как-то осторожен с использованием исключений повсюду. Но в этом случае я сейчас склоняюсь к их использованию. Спасибо,
Хорошо аргументировано, но я не согласен с тем, что «Чтобы прервать сложный фрагмент кода, вызовите исключение - правильное решение». Вы сделали свой аргумент в пользу «GOTO», но я бы сказал, что исключения связаны с семантически, ну, исключительными случаями. Не до сложностей потока управления.
Простой и понятный способ решить эту проблему - просто поместить код «ложного цикла» в вызываемую функцию. Вы выходите из функции (через return), когда закончите с логикой.
Я оспариваю "прославленный гото". Либо нелинейный поток управления Любые является прославленным goto (включая любой цикл или условное выражение), либо вещи следует обвинять в переходе только в том случае, если они приводят к спагетти (что не так, потому что все переходы идут вперед и все к то же самое место).
@torial: хорошо, пока нет пары вещей в стиле RAII, обернутых снаружи, которые должны быть переданы по ссылкеc в качестве дополнительных параметров и, как правило, приводят к сигнатуре функции Doom.
+1 за отличный вывод. Но я на самом деле думаю, что если - это путь первый, и только вложенные ifs принадлежит 4-му месту.
Хорошо это или плохо, но я использовал эту конструкцию в нескольких местах. Однако начало этого четко задокументировано:
/* This is a one-cycle loop that simplifies error handling */
do
{
...modestly complex code, including a nested loop...
} while (0);
Это на C, а не на C++, поэтому исключения не возможны. Если бы я использовал C++, я бы серьезно подумал об использовании исключений для обработки исключений. Идиома повторного тестирования, предложенная Джереми, также разумна; Я использовал это чаще. RAII мне тоже очень поможет; к сожалению, C не поддерживает это легко. И использование большего количества функций могло бы помочь. Обработка разрывов внутри вложенного цикла выполняется повторным тестом.
Я бы не назвал это отличным стилем; Я бы не стал автоматически классифицировать его как "ПЛОХОЙ".
Я думаю, что здесь люди нечестны.
Если бы я, как руководитель вашей группы, увидел бы такой код, вы были бы готовы пообщаться один на один и пометили бы его как потенциальную проблему для команды, поскольку этот фрагмент кода особенно ужасен.
Я говорю, что вы должны послушать своих коллег и переписать его, следуя любому из размещенных здесь предложений.
Иногда лучше иметь мнение постороннего (например, Stack Overflow). Авторитетные игры могут быть непростыми в некоторых рабочих средах.
Дело не в авторитетных играх. Дело в том, чтобы люди в вашей команде писали поддерживаемый код. Если бы я брал у кого-то интервью, и они показывали мне такие вещи, это было бы одно короткое интервью.
Я бы сказал, что ваше решение может быть правильным, но это зависит от обстоятельств. Пол Томблин опубликовал ответ, который лучше (серия трубок if) ... если его можно использовать.
Решение Пола нельзя использовать, когда в вашем цикле есть дорогостоящие инициализации объекта. Если созданные объекты используются на более поздних этапах, решение do while (0) лучше.
Тем не менее, следует улучшить именование переменных. Кроме того, зачем так часто повторно использовать переменную "escape"? Вместо этого явно перехватывайте каждое отдельное условие ошибки и прерывайте цикл, чтобы было очевидно, что вызывает сбой в каждой точке.
Кто-то еще предложил использовать вызовы функций. Опять же, это может быть не идеальная декомпозиция, если она добавляет ненужную сложность к вызовам функций из-за количества аргументов, которые могут быть переданы на любом данном шаге.
Другие предположили, что это трудная для понимания идиома. Что ж, сначала вы можете поместить комментарий, как предлагается, в верхней части цикла. Во-вторых, do-while (0) - это нормальная и распространенная идиома в макросах, которую все программисты на C должны сразу распознать, поэтому я просто не верю на это.
Я не согласен с некоторыми из того, что вы говорите, но я все равно голосую за это, потому что это хорошо сказано. do-while (0) широко используется в макросах из-за ограничений макросов - я не согласен с тем, что они могут использоваться вне макросов.
Вот для чего нужны исключения. Вы не можете продолжать логику, потому что что-то пошло не так. Даже если восстановление тривиально, это все равно исключение. Если у вас есть действительно веская причина не использовать исключения, например, когда вы программируете на языке, который их не поддерживает, используйте условные блоки, а не циклы.
Мета-комментарий: Когда вы пишете код, ваша цель в первую очередь должна быть ясным, поддерживаемым кодом. Вы не должны отказываться от четкости на алтаре эффективности, если вы не профилируете и не докажете, что это необходимо и что это действительно улучшает ситуацию. Следует избегать уловок с разумом джедаев. Всегда думайте, что следующий парень, который будет поддерживать ваш код, будет большим подлым психопатом с вашим домашним адресом. Я, например.
Во-первых, если вам просто нужен ответ на вопрос, является ли эта структура кода или идиома «плохой», я бы подумал, что это так. Однако я думаю, что это скорее симптом плохой декомпозиции, чем то, является ли код «хорошим» или «плохим».
Я думаю, что для дальнейшего устранения источника проблемы необходимо будет провести гораздо лучший анализ и рефакторинг, а не просто смотреть на код. Если вы можете сделать что-то вроде:
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;
};
Вы получаете преимущество лучшей декомпозиции и помощи от компилятора для создания необходимого короткого замыкания, которое принесет &&. Компилятор может даже встроить код в функции, чтобы получить лучший код, чем если бы вы выполняли цикл, закодированный вручную.
Рефакторинг. Чистым решением в большинстве случаев будет разделение этого кода на меньшую вспомогательную функцию, из которой вы можете вернуться, а не выходить из своего не-фактического цикла.
Затем вы можете заменить свой break на return, и теперь люди могут сразу понять смысл вашего кода при его чтении, вместо того, чтобы останавливаться и удивляться, почему вы сделали этот цикл, который на самом деле не цикл.
Да, я бы сказал, что просто потому, что он ведет себя не так, как ожидал бы читатель, это плохая практика. Принцип наименьшего удивления и так далее. Когда я вижу цикл, я ожидаю, что он зациклится, а если нет, я должен остановиться и задуматься о Почему.
Вы хотите отредактировать заголовок, чтобы он хотя бы намекал на тему? Я не хочу менять его, чтобы вложить слова в ваш рот, но он должен показать что-то полезное в списках вопросов.