Являются ли методы «ломать» и «продолжать» плохое программирование?

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

Я использую их все время, потому что они имеют смысл; позвольте мне показать вам вдохновение:

  function verify (object) {
    if (object-> значение <0) возвращает false;
    if (object-> value> object-> max_value) возвращает false;
    if (object-> имя == "") возвращает false;
    ...
}
 

Дело здесь в том, что сначала функция проверяет правильность условий, а затем выполняет фактическую функциональность. IMO также применяется с циклами:

  while (primary_condition) {
    if (loop_count> 1000) break;
    if (time_exect> 3600) break;
    if (this-> data == "undefined") continue;
    if (this-> skip == true) continue;
    ...
}
 

Я думаю, это облегчает чтение & amp; отлаживать; но я также не вижу недостатка.

173 голоса | спросил 6 revs, 6 users 63%
Mikhail
1 Jam1000000amThu, 01 Jan 1970 03:00:00 +030070 1970, 03:00:00

20 ответов


221

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

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

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
81

Вы можете прочитать статью Дональда Кнута 1974 года Структурированное программирование с go к заявлениям , в которых он обсуждает различные применения go to , которые являются структурно желательными. Они включают в себя эквивалент break и continue (многие из них используют go to ), которые были разработаны в более ограниченных конструкциях). Является ли ваш босс типом, чтобы назвать Кнута плохим программистом?

(Примеры, которые меня интересуют. Обычно break и continue не нравятся люди, которые любят одну запись и один выход из любой части кода, и такие человек также хмурится несколькими операциями return .)

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
38

Я не верю, что они плохие. Идея о том, что они плохи, исходит из дней структурированного программирования. Это связано с тем, что функция должна иметь одну точку входа и одну точку выхода, т.е. е. только один return для каждой функции.

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

Если ваша функция очень короткая, если у вас есть один цикл или, в худшем случае, два вложенных цикла, и если тело цикла очень короткое, то очень ясно, что break или continue делает. Также ясно, что делают несколько операторов return .

Эти вопросы рассматриваются в «Чистом кодексе» Роберта К. Мартина и в «Рефакторинге» Мартина Фаулера.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
31

Плохие программисты говорят в абсолютах (точно так же, как ситы). Хорошие программисты используют максимально возможное решение ( при прочих равных условиях ).

Использование break и continue часто делает код трудным для подражания. Но если их заменить, код еще сложнее следовать, то это плохое изменение.

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

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
22

Большинство людей думает, что это плохая идея, потому что поведение нелегко предсказуемо. Если вы читаете код и видите while (x <1000) {} , вы предполагаете, что он будет запущен до x> = 1000 ... Но если есть перерывы в середине , то это не выполняется, поэтому вы не можете доверять своему циклу ...

По той же причине люди не любят GOTO: уверен, что can будет хорошо использоваться, но это также может привести к божественному коду спагетти, где код случайно перескакивает из раздела в раздел.

Для себя, если бы я собирался сделать цикл, который разбился на несколько условий, я бы сделал while (x) {} , а затем переключил X на false, когда мне нужно было вырваться , Конечный результат был бы таким же, и любой, кто читает код, знал бы, чтобы более внимательно изучить вещи, которые изменили значение X.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
13

Да, вы можете [повторно] писать программы без инструкций break (или возвращает из середины циклов, которые делают то же самое). Но вам, возможно, придется вводить дополнительные переменные и /или дублирование кода, которые обычно затрудняют понимание программы. По этой причине Паскаль (язык программирования) был очень плохим, особенно для начинающих программистов. Ваш босс в основном хочет, чтобы вы программировали в структурах управления Паскаля. Если бы Линус Торвальдс был в ваших силах, он, вероятно, покажет вашему боссу средний палец!

Результат компьютерной науки называется иерархией контрольных структур Косараджу, которая восходит к 1973 году и упоминается в знаменитой статье Кнут (более) на языках с 1974 года. (Эта статья Кнута уже была рекомендована Дэвидом Торнли, кстати.) Что доказал С. Рао Косараджу в 1973 году, так это то, что невозможно переписать все программы с многоуровневыми разломами глубины n в программы с глубиной разлома менее n без введения дополнительных переменных. Но, скажем так, это чисто теоретический результат. (Просто добавьте несколько дополнительных переменных ?! Конечно, вы можете сделать это, чтобы порадовать своего босса ...)

Что гораздо важнее с точки зрения разработки программного обеспечения, является более поздняя в 1995 году работа Эрика С. Робертса под названием Loop Exits и структурированное программирование: повторное обсуждение (http://cs.stanford.edu/people/eroberts/papers/SIGCSE-1995/LoopExits.pdf ) , Робертс подводит итог нескольким эмпирическим исследованиям, проведенным другими перед ним. Например, когда группе учеников типа CS101 было предложено написать код для функции, реализующей последовательный поиск в массиве, автор исследования сказал следующее о тех учениках, которые использовали break /return /goto для выхода из цикл последовательного поиска, когда элемент был найден:

  

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

Робертс также говорит, что:

  

Студенты, которые пытались решить проблему без использования явного возврата из цикла for, шли гораздо реже: только из семи из 42 студентов, пытавшихся эту стратегию, удалось создать правильные решения. Эта цифра представляет собой показатель успеха менее 20%.

Да, вы можете быть более опытными, чем студенты CS101, но без использования инструкции break (или эквивалентно return /goto из середины циклов), в конце концов вы напишете код, который, хотя номинально красиво структурирован, достаточно волосатый в терминах дополнительных логических переменных и дублирования кода, что кто-то, возможно, сам, поместит в него логические ошибки, пытаясь следовать стилю кодирования вашего босса.

Я также скажу здесь, что статья Робертса гораздо более доступна среднему программисту, поэтому лучше сначала прочитать, чем Кнут. Он также короче и охватывает более узкую тему. Возможно, вы даже можете рекомендовать его своему боссу, даже если он является менеджментом, а не типом CS.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
9

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

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
7

Приведенный вами пример не требует перерывов и не продолжается:

  while (первичное условие AND
       количество циклов <= 1000 И
       time-exec <= 3600) {
   когда (data! = "undefined" AND
           НЕ пропустите)
      сделай что-то полезное-;
   }
 

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

В моем вложенном подходе, чем глубже вы идете, тем более «полезный» код становится.

Но если в глубине вы обнаружите причину остановки цикла (кроме первичного), перерыв или возврат будут использовать его. Я бы предпочел, чтобы с использованием дополнительного флага, который должен быть протестирован в состоянии верхнего уровня. Перерыв /возврат более прямой; он лучше заявляет о намерении, чем устанавливает еще одну переменную.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
6

«Плохое» зависит от того, как вы их используете. Обычно я использую breaks in looping constructs ТОЛЬКО, когда он спасет мне циклы, которые не могут быть сохранены путем рефакторинга алгоритма. Например, циклическое перемещение по коллекции, ищущей элемент со значением в определенном свойстве, установленном в true. Если все, что вам нужно знать, это то, что один из элементов имел это свойство, равное true, как только вы достигнете этого результата, разрыв хорош, чтобы закончить цикл соответствующим образом.

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

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
4

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

Но использование этих инструкций типа break и continue абсолютно необходимо в наши дни и вообще не рассматривается как плохая практика программирования.

А также не так сложно понять поток управления при использовании break и continue . В конструкциях, таких как switch , оператор break абсолютно необходим.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
3

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

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

Честно говоря, ваш второй код не очевиден. Что он делает? Продолжает ли «продолжать», или «следующий» цикл? Понятия не имею. По крайней мере, ваш первый пример ясен.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
2

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

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

  while (primary_condition) {
    if (loop_count> 1000) || (time_exect> 3600) {
        ломать;
    } else if ((this-> data! = "undefined") & amp; & amp; (! this-> skip)) {
       ... //где происходит реальная работа цикла
    }
}
 

С другой стороны примечание

Мне лично не нравится использование (flag == true) в условных выражениях, потому что если переменная уже является логической, то вы вводите дополнительное сравнение, которое должно произойти, когда значение булева есть ответ, который вы хотите - если, конечно, вы не уверены, что ваш компилятор оптимизирует это дополнительное сравнение.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
2

Я бы заменил ваш второй фрагмент кода на

  while (primary_condition & amp; & amp; (loop_count <= 1000 & amp; time_exect <= 3600)) {
    if (this-> data! = "undefined" & amp; & this this> skip! = true) {
        ..
    }
}
 

не по каким-либо причинам терпения - я на самом деле думаю, что это легче читать и кому-то понять, что происходит. Вообще говоря, условия для ваших петель должны содержаться исключительно в тех условиях цикла, которые не засоряются по всему телу. Однако есть ситуации, когда break и continue могут помочь в удобочитаемости. break moreso than continue Я могу добавить: D

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
1

Я согласен с вашим боссом. Они плохи, потому что они производят методы с высокой циклической сложностью. Такие методы трудно читать и их трудно проверить. К счастью, есть простое решение. Извлеките тело цикла в отдельный метод, где «continue» станет «return». «Возвращение» лучше, потому что после «возвращения» оно закончено - нет никаких забот о локальном состоянии.

Для «break» извлеките сам цикл в отдельный метод, заменив «break» на «return».

Если извлеченные методы требуют большого количества аргументов, это указание на извлечение класса - либо собирать их в объект контекста.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
0

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

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
0

Пока они не используются в качестве замаскированного goto, как в следующем примере:

  do
{
      если (foo)
      {
             /*** код *** /
             ломать;
      }

      if (bar)
      {
             /*** код *** /
             ломать;
      }
} while (0);
 

Я в порядке с ними. (Пример показан в производственном коде, meh)

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
0

Мне не нравится любой из этих стилей. Вот что я хотел бы:

  function verify (object)
{
    если нет (object-> значение <0)
       и не (object-> value> object-> max_value)
       и не (object-> имя == "")
       {
         делать что-то важное
       }
    else возвращает false; //вероятно, не нужно, так как эта функция даже не кажется, что она определена, чтобы возвращать что-нибудь ...?
}
 

Мне действительно не нравится использовать return , чтобы прервать функцию. Это похоже на злоупотребление return .

Использование break также не всегда понятно для чтения.

Еще лучше:

  notdone: = primarycondition
while (notDone)
{
    if (loop_count> 1000) или (time_exect> 3600)
    {
       notDone: = false;
    }
    еще
    {
        skipCurrentIteration: = (this-> data == "undefined") или (this-> skip == true)

        если не skipCurrentIteration
        {
           сделай что-нибудь
        }
        ...
    }
}
 

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

(Все вышеописанный код является псевдокодом)

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
0

Нет. Это способ решить проблему, и есть другие способы ее решения.

Многие текущие основные языки (Java, .NET (C # + VB), PHP, пишут ваши собственные) используют «break» и «continue» для пропуска петель. Они оба «структурировали goto (s)» предложения.

Без них:

  String myKey = "mars";

int i = 0; bool found = false;
while ((i <MyList.Count) & amp; & amp; (не найдено)) {
  found = (MyList [i] .key == myKey);
  я ++;
}
если (найдено)
  ShowMessage («Ключ» + i.toString ());
еще
  ShowMessage («Не найдено»);
 

С ними:

  String myKey = "mars";

для (i = 0; i <MyList.Count; i ++) {
  if (MyList [i] .key == myKey)
    ломать;
}
ShowMessage («Ключ» + i.toString ());
 

Обратите внимание, что код «break» и «continue» короче и обычно превращает «while» в предложения «for» или «foreach».

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

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

Некоторые разработчики могут подумать, что они не обязательно, но гипотетические, если мы должны были их удалить, мы должны удалить «while» и «do while» («repeat until», вы также pascal);)

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

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
0

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

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

  for (var i = 0; i <collection.Count; i ++)
{
    if (! Predicate (item)) continue;
    если (i> = 100) ломается; //сначала я использовал> здесь это ошибка. еще одна хорошая вещь о более декларативном стиле!

    DoStuff (пункт);
}
 

Это выглядит ПРИЧИНО чисто. Это не очень сложно понять. Я думаю, что это выиграет от того, чтобы быть более декларативным. Сравните это со следующим:

  foreach (элемент var в коллекции.Where (Predicate) .Take (100))
    DoStuff (пункт);
 

Возможно, вызовы Where и Take не должны быть в этом методе. Возможно, эта фильтрация должна быть выполнена до того, как коллекция будет передана этому методу. В любом случае, перейдя от материала низкого уровня и сосредоточившись больше на фактической бизнес-логике, становится более понятным, к чему мы НАСТОЯТЕЛЬНО заинтересованы. Легче разделить наш код на единые модули, которые больше подходят для хороших методов проектирования, и поэтому на.

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

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18
-1

Code Complete содержит приятный раздел об использовании goto и несколько возвратов из подпрограммы или цикла.

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

Стив Макконнелл (автор Code Complete) использует почти те же примеры, что и вы, чтобы показать преимущества использования различных операторов goto .

Однако чрезмерное использование break или continue может привести к сложному и незаменимому программному обеспечению.

ответил Stig Hemmer 18 +03002016-10-18T13:54:18+03:00312016bEurope/MoscowTue, 18 Oct 2016 13:54:18 +0300 2016, 13:54:18

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132