Когда поток кода выглядит так:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
Я обычно видел эту работу, чтобы избежать беспорядочного потока кода:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
Какие есть лучшие способы избежать этого обходного пути, чтобы он стал кодом более высокого уровня (отраслевого уровня)?
Любые предложения, которые из коробки приветствуются!
goto
- но я уверен, что кто-то отметит меня за то, что я предложил это, поэтому я не пишу ответ на этот счет. Иметь длинноеdo ... while(0);
кажется неправильным.goto
, будьте честны с этим и делайте это открыто, не скрывайте это с помощьюbreak
иdo ... while
goto
усилия по реабилитации. :)Ответы:
Приемлемой практикой считается выделение этих решений в функции и использование
return
s вместоbreak
s. Хотя все эти проверки соответствуют тому же уровню абстракции, что и функции, это вполне логичный подход.Например:
источник
return
чище, потому что любой читатель сразу же осознает, что он работает правильно и что он делает. Сgoto
вами нужно осмотреться, чтобы увидеть, для чего он нужен, и быть уверенным, что ошибок не было. Маскировка - это выгода.forceinline
.Есть моменты, когда использование
goto
на самом деле ПРАВИЛЬНЫЙ ответ - по крайней мере, для тех, кто не воспитан в религиозной вере, что "goto
никогда не может быть ответом, независимо от того, какой это вопрос», - и это один из таких случаев.Этот код использует хак
do { ... } while(0);
с единственной целью одетьсяgoto
какbreak
. Если вы собираетесь использоватьgoto
, то откровенно об этом. Нет смысла делать код сложнее для чтения.Особая ситуация возникает, когда у вас много кода с довольно сложными условиями:
Это на самом деле может сделать ЯСНО, что код верен, не имея такой сложной логики.
Изменить: чтобы уточнить, я ни в коем случае не предлагаю использовать
goto
в качестве общего решения. Но есть случаи, когдаgoto
это лучшее решение, чем другие решения.Например, представьте, что мы собираем некоторые данные, и различные условия, для которых проводится тестирование, являются своего рода «это конец собираемых данных» - что зависит от неких маркеров «продолжить / закончить», которые различаются в зависимости от того, где Вы находитесь в потоке данных.
Теперь, когда мы закончим, нам нужно сохранить данные в файл.
И да, часто есть другие решения, которые могут обеспечить разумное решение, но не всегда.
источник
goto
может иметь место, ноgoto cleanup
не имеет. Очистка делается с RAII.goto
- это никогда не правильный ответ. Я был бы признателен, если бы был комментарий ...goto
потому что вы должны думать , чтобы использовать его / понять программу , которая использует это ... Микропроцессоры, с другой стороны, построены наjumps
иconditional jumps
... так что проблема с некоторыми людьми, а не с логикой или что - то еще.goto
. RAII не является правильным решением, если ожидаются ошибки (не исключительные), поскольку это будет злоупотребление исключениями.Вы можете использовать простой шаблон продолжения с
bool
переменной:Эта цепочка выполнения прекратится, как только
checkN
вернет afalse
. Дальнейшиеcheck...()
вызовы не будут выполняться из-за короткого замыкания&&
оператора. Более того, оптимизирующие компиляторы достаточно умны, чтобы признать, что установкаgoOn
наfalse
улицу с односторонним движением, и вставить недостающуюgoto end
для вас. В результате производительность кода выше будет такой же, как уdo
/while(0)
, только без болезненного удара по его читабельности.источник
if
условий выглядят очень подозрительно.if
независимо от того, чтоgoOn
было, даже если ранний отказал (в отличие от прыжка / вырыва) ... но я только что провел тест, и VS2012, по крайней мере, был достаточно умен, чтобы в любом случае замкнуть все после первой ошибки. Я буду использовать это чаще. примечание: если вы используете,goOn &= checkN()
тоcheckN()
всегда будет работать, даже еслиgoOn
былfalse
в началеif
(то есть, не делайте этого).if
s.Попробуйте извлечь код в отдельную функцию (или, возможно, более чем одну). Затем вернитесь из функции, если проверка не пройдена.
Если это слишком тесно связано с окружающим кодом, чтобы сделать это, и вы не можете найти способ уменьшить связь, посмотрите код после этого блока. Предположительно, он очищает некоторые ресурсы, используемые функцией. Попробуйте управлять этими ресурсами, используя объект RAII ; затем замените каждую хитрость
break
наreturn
(илиthrow
, если это более уместно) и дайте деструктору объекта убрать за вас.Если поток программы (обязательно) настолько неровный, что вам действительно нужно
goto
, то используйте это, а не придавайте ему странную маскировку.Если у вас есть правила кодирования, которые запрещают вслепую
goto
, и вы действительно не можете упростить поток программы, то вам, вероятно, придется замаскировать его своимdo
хаком.источник
goto
если это действительно лучший вариант.TLDR : RAII , транзакционный код (устанавливает результаты или возвращает только тогда, когда он уже вычислен) и исключения.
Длинный ответ:
В C лучшая практика для этого вида кода заключается в добавлении в код метки EXIT / CLEANUP / other , где происходит очистка локальных ресурсов и возвращается код ошибки (если есть). Это лучшая практика, потому что он естественным образом разделяет код на инициализацию, вычисление, фиксацию и возврат:
В C, в большинстве базы коды, то
if(error_ok != ...
иgoto
код, как правило , скрываются за некоторые удобство макросов (RET(computation_result)
,ENSURE_SUCCESS(computation_result, return_code)
и т.д.).C ++ предлагает дополнительные инструменты над C :
Функциональность блока очистки может быть реализована как RAII, что означает, что вам больше не нужен весь
cleanup
блок, и позволяет клиентскому коду добавлять операторы раннего возврата.Вы бросаете каждый раз, когда не можете продолжить, превращая все
if(error_ok != ...
в прямые вызовы.Эквивалентный код C ++:
Это лучшая практика, потому что:
Это явно (то есть, хотя обработка ошибок не является явной, основной поток алгоритма таков)
Написать код клиента просто
Это минимально
Это просто
У него нет повторяющихся конструкций кода
Не использует макросов
Это не использует странные
do { ... } while(0)
конструкцииЕго можно использовать с минимальными усилиями (то есть, если я хочу скопировать вызов в
computation2();
другую функцию, мне не нужно обязательно добавлятьdo { ... } while(0)
в новый код ни#define
макрос , ни макрос обертки goto, ни метку очистки, ни что-нибудь еще).источник
shared_ptr
с помощью пользовательского средства удаления можно многое сделать. Еще проще с лямбдами в C ++ 11.namespace xyz { typedef shared_ptr<some_handle> shared_handle; shared_handle make_shared_handle(a, b, c); };
в этом случае (сmake_handle
установкой правильного типа средства удаления при построении) имя типа больше не предполагает, что это указатель ,Я добавляю ответ для полноты картины. В ряде других ответов указывалось, что большой блок условий можно разделить на отдельную функцию. Но, как уже было указано, несколько раз этот подход отделяет условный код от исходного контекста. Это одна из причин, по которой лямбды были добавлены в язык в C ++ 11. Использование лямбды было предложено другими, но явного образца предоставлено не было. Я положил один в этом ответе. Что меня поражает, так это то, что он
do { } while(0)
во многих отношениях очень похож на подход - и, возможно, это означает, что он все ещеgoto
скрыт ...источник
Конечно , не ответ, но ответ (для полноты картины )
Вместо того :
Вы могли бы написать:
Это все еще замаскированный goto , но, по крайней мере, это уже не цикл. А это значит, что вам не нужно будет очень тщательно проверять, нет ли продолжения скрытого где-то в блоке.
Конструкция также достаточно проста, и вы можете надеяться, что компилятор ее оптимизирует.
Как подсказывает @jamesdlin, вы можете даже скрыть это за макросом вроде
И используйте это как
Это возможно, потому что синтаксис языка C ожидает оператор после переключателя, а не блока в квадратных скобках, и вы можете поставить метку регистра перед этим оператором. До сих пор я не видел смысла позволять это, но в данном конкретном случае удобно скрывать переключатель за хорошим макросом.
источник
define BLOCK switch (0) case 0:
и использовать его какBLOCK { ... break; }
.Я бы рекомендовал подход, аналогичный ответу Матса, за вычетом ненужного.
goto
. Поместите только условную логику в функцию. Любой код, который всегда выполняется, должен идти до или после вызова функции в вызывающей программе:источник
func
, вам нужно выделить другую функцию (согласно вашему шаблону). Если все эти изолированные функции нуждаются в одних и тех же данных, вы просто в конечном итоге будете копировать одни и те же аргументы стека снова и снова или решите разместить свои аргументы в куче и передать указатель, не используя при этом самые основные функции языка ( аргументы функции). Короче говоря, я не верю, что это решение подходит для худшего случая, когда каждое условие проверяется после получения новых ресурсов, которые необходимо очистить. Обратите внимание на комментарий Наваз о лямбдах.func()
и разрешением его деструктору освобождать ресурсы? Если что-либо за пределамиfunc()
нуждается в доступе к тому же ресурсу, это должно быть объявлено в куче до вызоваfunc()
соответствующим менеджером ресурсов.Сам поток кода уже является запахом кода, который во многом происходит в функции. Если нет прямого решения для этого (функция является общей функцией проверки), то лучше использовать RAII, чтобы вы могли вернуться вместо перехода к конечному разделу функции.
источник
Если вам не нужно вводить локальные переменные во время выполнения, вы часто можете сгладить это:
источник
Аналогичен ответу dasblinkenlight, но избегает назначения внутри,
if
которое может быть «исправлено» рецензентом кода:...
Я использую этот шаблон, когда необходимо проверить результаты шага перед следующим шагом, что отличается от ситуации, когда все проверки могут быть выполнены заранее с помощью
if( check1() && check2()...
шаблона большого типа.источник
Используйте исключения. Ваш код будет выглядеть намного чище (и исключения были созданы именно для обработки ошибок в потоке выполнения программы). Для очистки ресурсов (файловых дескрипторов, соединений с базой данных и т. Д.) Прочитайте статью. Почему C ++ не предоставляет конструкцию «finally»? ,
источник
check()
сбоя условия, и это, безусловно, ВАШЕ предположение, что в примере нет контекста. Предполагая, что программа не может продолжить работу, используйте исключения.Для меня
do{...}while(0)
это хорошо. Если вы не хотите видетьdo{...}while(0)
, вы можете определить альтернативные ключевые слова для них.Пример:
SomeUtilities.hpp:
SomeSourceFile.cpp:
Я думаю, что компилятор удалит ненужное
while(0)
условие вdo{...}while(0)
в двоичной версии и преобразует разрывы в безусловный переход. Вы можете проверить его версию на ассемблере, чтобы быть уверенным.Использование
goto
также производит более чистый код, и это просто с логикой условие-затем-переход. Вы можете сделать следующее:Обратите внимание, этикетка размещается после закрытия
}
. Это одна из возможных проблем,goto
которая заключается в том, чтобы случайно поместить код между ними, потому что вы не видели метку. Теперь какdo{...}while(0)
без кода условия.Чтобы сделать этот код чище и понятнее, вы можете сделать это:
SomeUtilities.hpp:
SomeSourceFile.cpp:
При этом вы можете делать вложенные блоки и указывать, куда вы хотите выйти / выпрыгнуть.
Код спагетти не вина
goto
, это ошибка программиста. Вы все еще можете производить код спагетти без использованияgoto
.источник
goto
расширить синтаксис языка, используя препроцессор, миллион раз.do{...}while(0)
предпочтительнее. Этот ответ является только предложением и игрой на макро / goto / label для разбивки на определенные блоки.Это хорошо известная и хорошо решаемая проблема с точки зрения функционального программирования - возможно, монада.
В ответ на комментарий, который я получил ниже, я отредактировал свое введение здесь: вы можете найти полную информацию о реализации монад C ++ в различных местах, что позволит вам достичь того, что предлагает Rotsor. Чтобы получить монады, требуется некоторое время, поэтому вместо этого я собираюсь предложить здесь быстрый, похожий на монаду механизм «бедняков», для которого вам нужно знать не что иное, как boost :: option.
Настройте свои шаги вычисления следующим образом:
Каждый вычислительный шаг, очевидно, может выполнять что-то вроде возврата,
boost::none
если заданный необязательный параметр пуст. Так, например:Затем соедините их вместе:
Приятно то, что вы можете написать четко определенные модульные тесты для каждого вычислительного шага. Также вызов читается как обычный английский (как это обычно бывает с функциональным стилем).
Если вас не волнует неизменность, и удобнее возвращать один и тот же объект каждый раз, когда вы можете придумать какой-то вариант, используя shared_ptr или тому подобное.
источник
optional<EnabledContext> enabled(Context); optional<EnergisedContext> energised(EnabledContext);
вместо этого использовать монадическую композиционную операцию ('bind'), а не приложение-функцию.Как насчет перемещения операторов if в дополнительную функцию, которая дает числовой результат или результат перечисления?
источник
Что-то вроде этого возможно
или используйте исключения
Используя исключения, вы также можете передавать данные.
источник
ever
будет очень несчастным ...Я не особенно в способе использования
break
илиreturn
в таком случае. Учитывая, что обычно, когда мы сталкиваемся с такой ситуацией, это, как правило, сравнительно длинный метод.Если у нас есть несколько точек выхода, это может вызвать трудности, когда мы хотим знать, что вызовет выполнение определенной логики: обычно мы просто продолжаем идти вверх по блокам, охватывающим этот фрагмент логики, и критерии этого вмещающего блока говорят нам ситуация:
Например,
Глядя на вмещающие блоки, легко обнаружить, что
myLogic()
происходит только тогда, когдаconditionA and conditionB and conditionC
это правда.Это становится намного менее видимым, когда есть ранние возвращения:
Мы больше не можем перемещаться вверх от
myLogic()
, глядя на вмещающий блок, чтобы выяснить условие.Есть разные обходные пути, которые я использовал. Вот один из них:
(Конечно, можно использовать одну и ту же переменную для замены всех
isA isB isC
.)Такой подход как минимум даст читателю код, который
myLogic()
выполняется когдаisB && conditionC
. Читателю дают подсказку, что ему нужно продолжить поиск, что приведет к тому, что B будет правдой.источник
Как насчет этого?
источник
return condition;
, в противном случае я думаю, что это хорошо обслуживаемо.Еще один шаблон полезен, если вам нужны разные этапы очистки в зависимости от места сбоя:
источник
Я не программист на C ++ , поэтому я не буду писать здесь никакого кода, но пока никто не упомянул объектно-ориентированное решение. Итак, вот мое предположение об этом:
Иметь общий интерфейс, который предоставляет метод для оценки одного условия. Теперь вы можете использовать список реализаций этих условий в вашем объекте, содержащий рассматриваемый метод. Вы перебираете список и оцениваете каждое условие, возможно, оно выходит из строя раньше, если оно не выполняется.
Хорошо, что такой дизайн очень хорошо придерживается принципа открытия / закрытия , потому что вы можете легко добавлять новые условия при инициализации объекта, содержащего рассматриваемый метод. Вы даже можете добавить второй метод в интерфейс с методом оценки условия, возвращая описание условия. Это может быть использовано для систем самодокументирования.
Недостатком, однако, является то, что из-за использования большего количества объектов и итерации по списку происходит немного больше накладных расходов.
источник
Это способ, которым я делаю это.
источник
Во-первых, короткий пример, чтобы показать, почему
goto
не является хорошим решением для C ++:Попробуйте скомпилировать это в объектный файл и посмотреть, что произойдет. Тогда попробуйте эквивалентный
do
+break
+while(0)
.Это было в стороне. Суть в следующем.
Эти маленькие кусочки кода часто требуют некоторой очистки, если вся функция не работает. Эти очистки обычно должны происходить в обратном порядке от самих кусков, так как вы «раскручиваете» частично законченные вычисления.
Одним из вариантов получения этой семантики является RAII ; см. ответ @ utnapistim. C ++ гарантирует, что автоматические деструкторы запускаются в порядке, обратном конструкторам, что, естественно, обеспечивает «раскручивание».
Но это требует много классов RAII. Иногда более простой вариант - просто использовать стек:
...и так далее. Это легко проверить, так как он помещает код отмены рядом с кодом do. Легкий одитинг это хорошо. Это также делает поток управления очень четким. Это полезный шаблон для C тоже.
Может потребоваться, чтобы
calc
функции принимали множество аргументов, но обычно это не проблема, если у ваших классов / структур хорошая сплоченность. (То есть вещи, которые принадлежат друг другу, живут в одном объекте, поэтому эти функции могут брать указатели или ссылки на небольшое количество объектов и при этом выполнять много полезной работы.)источник
Если в вашем коде есть длинный блок операторов if..else if..else, вы можете попробовать переписать весь блок с помощью
Functors
илиfunction pointers
. Возможно, это не всегда правильное решение, но довольно часто.http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html
источник
Я поражен количеством различных ответов, представленных здесь. Но, наконец, в коде, который я должен изменить (т.е. удалить этот
do-while(0)
хак или что-то еще), я сделал что-то отличное от любого из ответов, упомянутых здесь, и меня смущает, почему никто не подумал об этом. Вот что я сделал:Исходный код:
Сейчас:
Итак, что здесь сделано, так это то, что завершающий материал был изолирован в функции, и все вдруг стало таким простым и чистым!
Я думал, что это решение стоит упомянуть, поэтому предоставил его здесь.
источник
Объедините это в одно
if
утверждение:Это шаблон, используемый в таких языках, как Java, где ключевое слово goto было удалено.
источник
true
. Оценка короткого замыкания гарантирует, что вы никогда не запустите промежуточный материал, для которого не прошли все предварительные тесты.(/*do other stuff*/, /*next condition*/)
вы можете даже красиво отформатировать их. Просто не ожидайте, что людям это понравится. Но, честно говоря, это говорит о том, что для Java было ошибкойgoto
Если для всех ошибок используется один и тот же обработчик ошибок, и каждый шаг возвращает логическое значение, указывающее успех:
(Аналогично ответу tyzoid, но условия - это действия, а && предотвращает дополнительные действия после первого сбоя.)
источник
Почему не помечен метод ответа, он используется с давних времен.
источник