Предполагая, что у меня есть этот псевдокод:
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
Функции executeStepX
должны быть выполнены тогда и только тогда, когда предыдущий будет успешным. В любом случае, executeThisFunctionInAnyCase
функция должна быть вызвана в конце. Я новичок в программировании, поэтому прошу прощения за очень простой вопрос: есть ли способ (например, в C / C ++) избежать такой длинной if
цепочки, производящей такого рода «пирамиду кода», за счет разборчивости кода ?
Я знаю, что если бы мы могли пропустить executeThisFunctionInAnyCase
вызов функции, код мог бы быть упрощен как:
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
Но ограничение - это executeThisFunctionInAnyCase
вызов функции. Можно ли как- break
то использовать это утверждение?
c++
c
if-statement
control-flow
ABCplus
источник
источник
false
рассматриваться как исключительная ситуация?false
Возвращение может быть вполне нормальным.Ответы:
Вы можете использовать
&&
(логическое И):это удовлетворит оба ваших требования:
executeStep<X>()
следует оценивать, только если предыдущий преуспел (это называется оценкой короткого замыкания )executeThisFunctionInAnyCase()
будет выполнен в любом случаеисточник
&&
и||
), поэтому вы не захотите объединить их в одноif
утверждение без потери читабельности. И не всегда легко перенести эти условия во внешние функции, потому что они могут зависеть от множества предварительно вычисленных локальных переменных, что может создать ужасный беспорядок, если вы попытаетесь передать каждое из них в качестве отдельного аргумента.Просто используйте дополнительную функцию, чтобы ваша вторая версия работала:
Использование глубоко вложенных ifs (ваш первый вариант) или желание выйти из «части функции» обычно означает, что вам нужна дополнительная функция.
источник
foo
работает через последовательность связанных условий и действий. Функцияbar
четко отделена от решений. Если бы мы увидели детали условий и действий, может оказаться, чтоfoo
все еще делается слишком много, но пока это хорошее решение.bar
вас, вам придется вручную передавать их вfoo
качестве параметров. Если это так и еслиfoo
вызывается только один раз, я бы ошибся в использовании версии goto, чтобы избежать определения двух тесно связанных функций, которые в конечном итоге не будут использоваться повторно.if (!executeStepA() || !executeStepB() || !executeStepC()) return
Программисты старой школы C используют
goto
в этом случае. Это единственное использование,goto
которое фактически поощряется в руководстве по стилю Linux, оно называется централизованной функцией выхода:Некоторые люди обходят использование
goto
, заключая тело в петлю и разрывая ее, но эффективно оба подхода делают одно и то же.goto
Подход лучше , если вам нужны другие очистки , только еслиexecuteStepA()
был преуспевающим:При использовании циклического подхода в этом случае вы получите два уровня циклов.
источник
goto
и сразу думают: «Это ужасный код», но он действительно имеет свое применение.goto
и мне даже не нужно думать, чтобы увидеть, что это ужасный код. Мне когда-то приходилось поддерживать такое, это очень противно. ОП предлагает разумную альтернативу языка Си в конце вопроса, и я включил это в свой ответ.throw
во многих отношениях хуже, чемgoto
потому,throw
что даже из местного контекста неясно, где вы собираетесь оказаться! Используйте те же принципы проектирования для потока управления в стиле Goto, что и для исключений.Это обычная ситуация, и есть много способов справиться с ней. Вот моя попытка канонического ответа. Пожалуйста, прокомментируйте, если я что-то пропустил, и я буду обновлять этот пост.
Это стрелка
То, что вы обсуждаете, называется анти-паттерном стрелки . Это называется стрелкой, потому что цепочка вложенных if формирует блоки кода, которые расширяются все дальше и дальше вправо, а затем назад влево, образуя визуальную стрелку, которая «указывает» на правую сторону панели редактора кода.
Расправь стрелу с гвардией
Некоторые общие способы избежать Стрелы обсуждаются здесь . Наиболее распространенным методом является использование защитного шаблона, в котором код сначала обрабатывает потоки исключений, а затем обрабатывает основной поток, например, вместо
... вы бы использовали ....
Когда есть длинная серия охранников, это значительно сглаживает код, поскольку все охранники появляются полностью налево, и ваши if не являются вложенными. Кроме того, вы визуально связываете логическое условие с соответствующей ошибкой, что значительно упрощает понимание происходящего:
Стрелка:
Guard:
Это объективно и количественно легче читать, потому что
Как добавить общий код в конце
Проблема с паттерном охраны заключается в том, что он опирается на то, что называется «оппортунистическим возвращением» или «оппортунистическим выходом». Другими словами, это нарушает схему, согласно которой каждая функция должна иметь ровно одну точку выхода. Это проблема по двум причинам:
Ниже я предоставил некоторые варианты для обхода этого ограничения, либо используя языковые функции, либо избегая проблемы в целом.
Вариант 1. Вы не можете сделать это: использовать
finally
К сожалению, как разработчик C ++, вы не можете сделать это. Но это ответ номер один для языков, которые содержат ключевое слово finally, поскольку именно для этого оно и предназначено.
Вариант 2. Избегайте проблемы: реструктурируйте свои функции
Вы можете избежать этой проблемы, разбив код на две функции. Преимущество этого решения заключается в работе на любом языке, и, кроме того, оно может снизить цикломатическую сложность , которая является проверенным способом снижения уровня дефектов, и повышает специфичность любых автоматических модульных тестов.
Вот пример:
Вариант 3. Языковой трюк: использовать фальшивый цикл
Другой распространенный трюк, который я вижу, - это использование while (true) и break, как показано в других ответах.
Хотя это менее "честно", чем использование
goto
, оно менее подвержено путанице при рефакторинге, так как оно четко отмечает границы логической области видимости. Наивный программист, который вырезает и вставляет ваши ярлыки или вашиgoto
заявления, может вызвать серьезные проблемы! (И, честно говоря, картина настолько распространена, что теперь я думаю, что она ясно сообщает о намерениях и, следовательно, вовсе не является «нечестной»).Есть и другие варианты этой опции. Например, можно использовать
switch
вместоwhile
. Любая языковая конструкция сbreak
ключевым словом, вероятно, будет работать.Вариант 4. Использование жизненного цикла объекта
Еще один подход использует жизненный цикл объекта. Используйте объект контекста для переноса ваших параметров (то, чего подозрительно не хватает нашему наивному примеру) и избавьтесь от него, когда закончите.
Примечание. Убедитесь, что вы понимаете жизненный цикл объекта по своему выбору. Для этого вам нужен какой-то детерминированный сборщик мусора, т.е. вы должны знать, когда будет вызван деструктор. В некоторых языках вам нужно будет использовать
Dispose
вместо деструктора.Вариант 4.1. Использовать жизненный цикл объекта (шаблон оболочки)
Если вы собираетесь использовать объектно-ориентированный подход, можете сделать это правильно. Эта опция использует класс, чтобы «обернуть» ресурсы, которые требуют очистки, а также другие его операции.
Опять же, убедитесь, что вы понимаете жизненный цикл вашего объекта.
Вариант 5. Языковой трюк: использовать оценку короткого замыкания
Другой метод заключается в использовании оценки короткого замыкания .
Это решение использует преимущества работы оператора &&. Когда левая часть && имеет значение false, правая часть никогда не оценивается.
Этот трюк наиболее полезен, когда требуется компактный код, и когда код вряд ли нуждается в большом обслуживании, например, вы реализуете хорошо известный алгоритм. Для более общего кодирования структура этого кода слишком хрупкая; даже незначительное изменение в логике может вызвать полное переписывание.
источник
Просто сделать
Это так просто.
Из-за трех изменений, каждое из которых принципиально изменило вопрос (четыре, если пересчитать версию обратно до версии # 1), я включил пример кода, на который я отвечаю:
источник
&&
список мне кажется намного понятнее. Я обычно разбиваю условия на отдельные строки и добавляю трейлинг// explanation
к каждой .... В конце концов, это значительно меньше кода, и когда вы понимаете, как&&
работает, нет никаких постоянных умственных усилий. У меня сложилось впечатление, что большинство профессиональных программистов на C ++ знакомы с этим, но, как вы говорите, в разных отраслях / проектах фокус и опыт отличаются.На самом деле в C ++ есть способ отложить действия: использовать деструктор объекта.
Предполагая, что у вас есть доступ к C ++ 11:
А затем с помощью этой утилиты:
источник
executeThisFunctionInAnyCase();
будет выполняться, даже еслиfoo();
выдает исключение. При написании кода, исключающего исключение, рекомендуется помещать все такие функции очистки в деструктор.foo()
. И если вы это сделаете, поймайте это. Задача решена. Исправлять ошибки, исправляя их, а не писать обходные пути.Defer
класс представляет собой небольшой фрагмент кода, который можно многократно использовать, который позволяет выполнять любую очистку конца блока безопасным способом. это более известно как охрана прицела . да, любое использование защиты области видимости может быть выражено другими, более ручными способами, точно так же, как любойfor
цикл может быть выражен как блок иwhile
цикл, который, в свою очередь, может быть выражен с помощьюif
иgoto
, который может быть выражен на языке ассемблера, если вы хотите, или для тех, кто является настоящими мастерами, изменяя биты в памяти с помощью космических лучей, управляемых эффектом бабочки специальных коротких ворчаний и пений но зачем это делатьЕсть хороший метод, который не нуждается в дополнительной функции-обертке с операторами return (метод, предписанный Itjax). Он использует
while(0)
псевдо-цикл do . Вwhile (0)
гарантирует , что он на самом деле не цикл , а выполняется только один раз. Однако синтаксис цикла позволяет использовать оператор break.источник
inline
. В любом случае, это хорошая техника, которую нужно знать, потому что она помогает не только в решении этой проблемы.Вы также можете сделать это:
Таким образом, у вас будет минимальный линейный размер роста, +1 линия на вызов, и его легко обслуживать.
РЕДАКТИРОВАТЬ : (Спасибо @Unda) Не большой поклонник, потому что вы теряете видимость ИМО:
источник
Будет ли это работать? Я думаю, что это эквивалентно вашему коду.
источник
ok
когда использую такую же переменную, как эта.Предполагая, что нужный код такой, какой я его сейчас вижу:
Я бы сказал, что правильный подход, поскольку его проще всего читать и легче поддерживать, будет иметь меньше уровней отступов, что (в настоящее время) является заявленной целью этого вопроса.
Это исключает любую необходимость в
goto
s, исключениях, фиктивныхwhile
циклах или других сложных конструкциях и просто выполняет простую работу под рукой.источник
return
иbreak
выпрыгивать из цикла без необходимости вводить дополнительные «флаговые» переменные. В этом случае использование goto будет также безобидным - помните, что вы торгуете дополнительной сложностью goto для дополнительной изменчивой переменной сложности.newbie
, это обеспечивает более чистое решение без недостатков. Я отмечаю, что это также не зависит отsteps
наличия одной и той же сигнатуры или от того, что они являются функциями, а не блоками. Я вижу, что это используется в качестве первого рефакторинга даже в тех случаях, когда более сложный подход оправдан.Возможно, не лучшее решение, но вы можете поместить свои операторы в
do .. while (0)
цикл и использоватьbreak
вместо нихreturn
.источник
do .. while (0)
для определений макросов также неправильно использует циклы, но считается, что все в порядке.Вы можете поместить все
if
условия, отформатированные так, как вы хотите, в их собственную функцию, при возврате выполнитьexecuteThisFunctionInAnyCase()
функцию.Из базового примера в OP тестирование и выполнение условий можно разделить как таковые;
И затем называется таковым;
Если доступны лямбды C ++ 11 (в OP не было тега C ++ 11, но они все еще могут быть опцией), тогда мы можем отказаться от отдельной функции и обернуть ее в лямбду.
источник
Если вам не нравятся
goto
и не нравятсяdo { } while (0);
циклы и вам нравится использовать C ++, вы также можете использовать временную лямбду, чтобы получить тот же эффект.источник
if
вам не нравится goto&&
вы не любите do {}, в то время как (0)&&
вам нравится C ++ ... Извините, не смог устоять, но последнее условие не выполняется, потому что вопрос помечен как c, так и c ++Цепочки IF / ELSE в вашем коде - это не проблема языка, а дизайн вашей программы. Если вы можете переформулировать или переписать свою программу, я бы посоветовал вам посмотреть в Design Patterns ( http://sourcemaking.com/design_patterns ), чтобы найти лучшее решение.
Обычно, когда вы видите много IF и других в вашем коде, это возможность реализовать шаблон проектирования стратегии ( http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net ) или, возможно, комбинацию других моделей.
Я уверен, что есть альтернативы, чтобы написать длинный список if / else, но я сомневаюсь, что они изменят что-либо, кроме того, что цепочка будет выглядеть симпатично для вас (однако, красота в глазах смотрящего по-прежнему относится к коду слишком:-) ) . Вас должны беспокоить такие вещи, как (через 6 месяцев, когда у меня появилось новое условие, и я ничего не помню об этом коде, смогу ли я добавить его легко? Или что если цепь изменится, как быстро и безошибочно буду ли я это реализовывать)
источник
Вы просто делаете это ..
99 раз из 100, это единственный способ сделать это.
Никогда, никогда не пытайтесь сделать что-то «хитрое» в компьютерном коде.
Кстати, я почти уверен, что именно то, что вы задумали ...
Оператор continue важен в алгоритмическом программировании. (Так же, как и оператор goto имеет решающее значение в алгоритмическом программировании.)
На многих языках программирования вы можете сделать это:
(Прежде всего обратите внимание: голые блоки, подобные этому примеру, являются критической и важной частью написания красивого кода, особенно если вы имеете дело с «алгоритмическим» программированием.)
Опять же, это именно то, что у вас было в голове, верно? И это прекрасный способ написать это, чтобы у вас были хорошие инстинкты.
Однако, к сожалению, в текущей версии target-c (кроме - я не знаю о Swift, извините) есть уязвимая функция, которая проверяет, является ли заключающий блок циклом.
Вот как вы можете обойти это ...
Так что не забывайте это ..
do {} while (false);
просто означает «сделать этот блок один раз».
то есть, нет абсолютно никакой разницы между письмом
do{}while(false);
и простым письмом{}
.Теперь это работает отлично, как вы хотели ... вот вывод ...
Так что, возможно, именно так вы видите алгоритм в своей голове. Вы всегда должны пытаться написать, что у вас в голове. (Особенно, если вы не трезвые, потому что именно тогда красавица выходит! :))
В «алгоритмических» проектах, где это часто случается, в target-c у нас всегда есть макрос вроде ...
... так что вы можете сделать это ...
Есть два момента:
а, даже если глупо, что target-c проверяет тип блока, в котором находится оператор continue, трудно "бороться с этим". Так что это сложное решение.
б, есть вопрос, стоит ли в этом примере делать отступ? Я теряю сон из-за таких вопросов, поэтому не могу советовать.
Надеюсь, поможет.
источник
if
, вы также можете использовать более описательные имена функций и помещать комментарии в функции.Сделайте так, чтобы ваши исполняющие функции выдавали исключение, если они терпят неудачу, вместо возврата false. Тогда ваш код вызова может выглядеть так:
Конечно, я предполагаю, что в вашем исходном примере шаг выполнения вернул бы false только в случае ошибки внутри шага?
источник
Много хороших ответов уже есть, но большинство из них, похоже, компромисс с некоторой (по общему признанию, очень мало) гибкостью. Обычный подход, который не требует этого компромисса, это добавление переменной status / keep-идя . Цена, конечно, одно дополнительное значение для отслеживания:
источник
ok &= conditionX;
и не простоok = conditionX;
?В C ++ (вопрос помечен как C, так и C ++), если вы не можете изменить функции для использования исключений, вы все равно можете использовать механизм исключений, если вы напишете небольшую вспомогательную функцию, такую как
Тогда ваш код может выглядеть следующим образом:
Если вам нравится необычный синтаксис, вы можете вместо этого заставить его работать через явное приведение:
Тогда вы можете написать свой код как
источник
Если ваш код так же прост, как ваш пример, и ваш язык поддерживает оценку короткого замыкания, вы можете попробовать это:
Если вы передаете аргументы своим функциям и возвращаете другие результаты, так что ваш код не может быть написан предыдущим способом, многие другие ответы лучше подходят для этой проблемы.
источник
Для C ++ 11 и выше, хорошим подходом может быть реализация системы выхода из области действия, аналогичной механизму D (выхода) из области видимости .
Один из возможных способов его реализации - использование лямбда-кода C ++ 11 и некоторых вспомогательных макросов:
Это позволит вам досрочно вернуться из функции и убедиться, что любой код очистки, который вы определяете, всегда выполняется при выходе из области:
Макросы действительно просто украшение.
MakeScopeExit()
можно использовать напрямую.источник
[=]
это обычно не так для лямбды с ограниченным диапазоном.[&]
: она безопасна и минимально удивительна. Захват по значению только тогда, когда лямбда (или копии) может выжить дольше, чем прицел на момент объявления ...Почему никто не дает самое простое решение? : D
Если все ваши функции имеют одинаковую подпись, то вы можете сделать это следующим образом (для языка Си):
Для чистого решения C ++ вы должны создать интерфейсный класс, который содержит метод execute и упаковывает ваши шаги в объекты.
Тогда приведенное выше решение будет выглядеть так:
источник
Предполагая, что вам не нужны отдельные переменные условия, инвертирование тестов и использование else-falthrough в качестве пути «ok» позволит вам получить более вертикальный набор операторов if / else:
Пропуск неудачной переменной делает код немного неясным IMO.
Объявление переменных внутри - это хорошо, не беспокойтесь о = vs ==.
Это неясно, но компактно:
источник
Это похоже на конечный автомат, что удобно, потому что вы можете легко реализовать его с помощью шаблона состояний .
В Java это будет выглядеть примерно так:
Реализация будет работать следующим образом:
И так далее. Тогда вы можете заменить большое условие if на:
источник
Как упоминал Роммик, вы можете применить шаблон проектирования для этого, но я бы использовал шаблон Декоратор, а не Стратегию, так как вы хотите связывать вызовы. Если код прост, то я бы пошел с одним из хорошо структурированных ответов, чтобы предотвратить вложение. Однако, если он сложный или требует динамического связывания, то шаблон Decorator является хорошим выбором. Вот диаграмма класса yUML :
Вот пример программы LinqPad C #:
ИМХО, лучшая книга для чтения по шаблонам проектирования - « Head First Design Patterns» .
источник
Несколько ответов намекали на шаблон, который я видел и использовал много раз, особенно в сетевом программировании. В сетевых стеках часто существует длинная последовательность запросов, любой из которых может дать сбой и остановит процесс.
Общий шаблон должен был использовать
do { } while (false);
Я использовал макрос для того,
while(false)
чтобы сделать этоdo { } once;
. Общий шаблон был:Этот шаблон был относительно прост для чтения и позволял использовать объекты, которые могли бы должным образом разрушаться, а также избегал многократных возвратов, что облегчало пошаговое выполнение и отладку.
источник
Чтобы улучшить ответ Матье на C ++ 11 и избежать затрат времени выполнения, понесенных при использовании
std::function
, я бы предложил использовать следующееЭтот простой шаблонный класс будет принимать любой функтор, который может быть вызван без каких-либо параметров, и делает это без каких-либо динамических выделений памяти и, следовательно, лучше соответствует цели абстракции C ++ без ненужных накладных расходов. Дополнительный шаблон функции предназначен для упрощения использования путем вывода параметров шаблона (который недоступен для параметров шаблона класса).
Пример использования:
Так же, как и ответ Матье, это решение полностью безопасно и
executeThisFunctionInAnyCase
будет вызываться во всех случаях. ЕслиexecuteThisFunctionInAnyCase
сам по себе выбрасывать, деструкторы помечаются неявно,noexcept
и поэтому будет вызван вызовstd::terminate
вместо того, чтобы вызывать исключение во время разматывания стека.источник
functor
вdeferred
конструкторе d, не нужно форсировать amove
.Кажется, что вы хотите сделать все ваши звонки из одного блока. Как и предлагали другие, вы должны использовать либо
while
цикл, и уходить с помощью,break
или новую функцию, с которой вы можете выйтиreturn
(может быть, чище).Я лично прогоняю
goto
даже за функцией выхода. Их сложнее обнаружить при отладке.Элегантная альтернатива, которая должна работать для вашего рабочего процесса, - это создать массив функций и повторить этот.
источник
Поскольку у вас также есть [... блок кода ...] между выполнениями, я думаю, у вас есть выделение памяти или инициализация объектов. Таким образом, вы должны заботиться об очистке всего, что вы уже инициализировали при выходе, а также очистить его, если вы столкнетесь с проблемой, и любая из функций вернет false.
В этом случае лучшее, что у меня было в моем опыте (когда я работал с CryptoAPI), было создание небольших классов, в конструкторе вы инициализировали свои данные, в деструкторе вы неинициализировали их. Каждый следующий класс функции должен быть дочерним по отношению к предыдущему классу функции. Если что-то пошло не так - киньте исключение.
И тогда в вашем коде вам просто нужно позвонить:
Я думаю, это лучшее решение, если каждый вызов ConditionX что-то инициализирует, выделяет память и т. Д. Лучше быть уверенным, что все будет очищено.
источник
интересный способ - работать с исключениями.
Если вы пишете такой код, вы идете как-то не в том направлении. Я не считаю «проблемой» иметь такой код, но иметь такую грязную «архитектуру».
Совет: обсудите эти случаи с опытным разработчиком, которому вы доверяете ;-)
источник
Другой подход -
do - while
цикл, хотя он был упомянут ранее, и не было ни одного примера, который бы показывал, как он выглядит:(Ну, уже есть ответ с
while
циклом, ноdo - while
цикл не избыточно проверяет истину (в начале), а вместо этого в конце xD (хотя это можно пропустить)).источник