Что лучше - для цикла с прерыванием или условного цикла?
Мне просто любопытно, что думают люди по этой теме. Скажем, у меня есть массив объектов, и я хочу просмотреть их в цикле, чтобы увидеть, содержат ли объекты определенные значения, и если да, то я хочу остановить цикл. Что лучше - цикл for с перерывом или условный цикл?
Псевдокод в приведенном мной примере предназначен только для аргументации (он также находится в ActionScript, поскольку в последнее время это мой основной язык). Кроме того, я не ищу лучших практических идей по синтаксису.
для цикла с разрывом:
var i:int;
var isBaxterInMilwaukee:Boolean;
for (i = 0; i < arrayLen; i++)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
break;
}
}
условный цикл:
var i:int;
var isBaxterInMilwaukee:Boolean;
while (!isBaxterInMilwaukee && i < arrayLen)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
i++;
}
Ответов (18)18
Короче говоря, вы должны выбрать ту версию, которую легче всего читать и поддерживать.
Я знаю, что раньше выход из цикла считался запретом (наравне с оператором goto). Циклы должны были прерываться по условию цикла и больше нигде. Таким образом, цикл while был бы подходящим вариантом.
(Вероятно, это пережиток сборки, где циклы в основном представляют собой блок кода с оператором перехода go-to-the-start-if-true в конце. Множественные операторы условного перехода в блоке чрезвычайно затрудняют отладку ; поэтому их следовало избегать и в конце объединить в одно целое.)
Мне кажется, эта идея сегодня немного меняется, особенно с циклами foreach и управляемым миром; теперь это действительно вопрос стиля. Обнаруженные циклы for, возможно, стали приемлемыми для многих, за исключением, конечно, некоторых пуристов. Обратите внимание, что я бы по-прежнему избегал использования break в цикле while, поскольку это может запутать условие цикла и сбить его с толку.
Если вы позволите мне использовать цикл foreach, я считаю, что приведенный ниже код намного легче читать, чем его собрат по циклу while:
bool isBaxterInMilwaukee;
foreach (var item in myArray)
{
if (item.name == "baxter" && item.location == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
break;
}
}
Однако по мере того, как логика становится все более сложной, вы можете захотеть рассмотреть заметный комментарий рядом с break
утверждением, чтобы его не было похоронить и его было трудно найти.
Возможно, все это должно быть реорганизовано в отдельную функцию, которая не break
найдена, но фактически return
является результатом (вместо этого не стесняйтесь использовать версию for-loop):
bool isBaxterInMilwaukee(Array myArray)
{
foreach (var item in myArray)
{
if (item.name == "baxter" && item.location == "milwaukee")
{
barkTwice();
return true;
}
}
return false;
}
Как указал Эско Луонтола, вероятно, было бы лучше переместить вызов за barkTwice()
пределы этой функции, поскольку побочный эффект не очевиден из имени функции и не связан с поиском Бакстера в каждом случае. (Или добавьте логический параметр BarkTwiceIfFound
и измените строку на чтение, if(BarkTwiceIfFound) barkTwice();
чтобы прояснить побочный эффект.)
Для записи вы также можете выполнить проверку флага в цикле for без перерыва, но я чувствую, что это на самом деле ухудшает читаемость, потому что вы не ожидаете дополнительного условия в определении цикла for:
var i:int;
var isBaxterInMilwaukee:Boolean;
for (i = 0; !isBaxterInMilwaukee && i < arrayLen; i++)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
}
Вы также можете моделировать механику автоинкремента с помощью цикла while. Мне это не нравится по нескольким причинам - вы должны инициализировать, i
чтобы оно было на единицу меньше вашего реального начального значения, и в зависимости от того, как ваш компилятор сокращает логику условия цикла, ваше значение при i
выходе из цикла может варьироваться. Тем не менее, это возможно, и для некоторых людей это может улучшить читаемость:
var i:int = -1;
var isBaxterInMilwaukee:Boolean;
while (!isBaxterInMilwaukee && ++i < arrayLen)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
}
Наиболее разумным будет тот, который лучше всего передает идею человеку, читающему код. Помните, прежде всего , о читабельности кода , и вы обычно сделаете правильный выбор. Обычно вы не хотите использовать что-то вроде, break
если вам это действительно не нужно, потому что это может затруднить отслеживание, если вы будете делать это часто или даже просто в глубоко вложенном наборе выражений. continue
иногда может служить той же цели, что и прерывание, и тогда цикл завершится нормально, а не потому, что он был прерван. В этом случае есть несколько разных способов написать это.
Вероятно, лучшее, что вам здесь нужно, - это модификация вашего while
цикла:
while(!isBaxterInMilwaukee || i < arrayLen) {
if(myArray[i]["name"] == "baxter" && myArray[i]["location"] == "milwaukee") {
isBaxterInMilwaukee == true;
barkTwice()
} else {
i++;
}
}
Это ясно и не использует break
или continue
, поэтому вы можете сразу сказать, что вы всегда завершаете работу в результате одного из условий, указанных в while
выражении.
ETA : вероятно, должен быть i < arrayLen
в while
цикле, иначе он не сработает с первого раза, если входное значение не совпадает с целевым значением ...
В ES6 это было сделано очень просто, нет необходимости использовать ключевое слово break, мы можем использовать функцию поиска, когда условие удовлетворяется, мы можем вернуть true.
let barkTwice = () => {
console.log('bark twice');
}
let arr = [{
location: "waukee",
name: "ter"
}, {
location: "milwaukee",
name: "baxter"
},
{
location: "sample",
name: "sampename"
}
];
Здесь мы сопоставляем условие, и как только условие совпадает, мы вызываем функцию в соответствии с вопросом, а затем возвращаем истину. Так что дальше не идет.
arr.find(item => {
if (item.location == "milwaukee" && item.name == "baxter") {
barkTwice();
return true
}
});
Мне всегда не нравилось использование breaks
в коде ... В этом случае это, кажется, не имеет значения, но в более сложных циклах это может ужасно сбить с толку другого кодировщика, читающего его. В общем, это часто приводит к непониманию того, как цикл может завершиться, пока кодер не обнаружит вложенность break
глубоко в цикле. Указав условие флага, которое проверяется на каждой итерации цикла, можно сделать это намного яснее.
Эта проблема была бы похожа на наличие return
операторов, которые находятся глубоко в теле метода, где их нелегко обнаружить (вместо того, чтобы устанавливать retVal
переменную и возвращаться в конце метода). При использовании небольшого метода это кажется прекрасным, но чем больше он становится, тем больше запутывается.
Дело не в эффективности эксплуатации, а в ремонтопригодности.
Спросите своих коллег, что можно прочитать и понять в конкретной ситуации ... Вот что действительно важно.
Это во многом зависит от конкретных обстоятельств. Но в вашем примере вы хотите пройти по массиву ограниченной длины, и использование цикла for упрощает это и защищает от сбега с конца. В вашем примере цикла while вы должны выполнить собственное приращение - что может быть проблематично, если вы хотите использовать continue
оператор для перехода к следующему циклу - и сделать более сложное условное выражение (которое, кстати, имеет ошибку , Я думаю ты имел ввиду && i != arrayLen
). Вам просто нужно написать дополнительный код, чтобы добиться эффекта, обеспечиваемого циклами for.
Конечно, некоторые пуристы будут утверждать, что break
и continue
не следует использовать, и что вы должны использовать if-else и логические переменные, если это необходимо, а не продолжать или выходить из цикла. Но я думаю, что это может сделать цикл намного более уродливым, особенно если он относительно короткий и простой для понимания, как этот пример. Для цикла с гораздо более длинным кодом, где разрыв или продолжение можно легко скрыть от внимания, пуристский подход может быть более ясным, поскольку в этом случае цикл уже сложен для понимания. Но вы всегда можете сделать это как часть цикла for, просто добавьте его как часть условия.
Также лучше проверять связанный массив, i < arrayLen
а не на точное равенство, на случай, если что-то заставит i
пропустить точное значение (я действительно видел, как это произошло в ошибке 2000 года, чего можно было бы избежать с помощью лучшей практики).
В цикле for вы также можете преждевременно выйти, указав критерии раннего выхода в объявлении цикла for. Итак, для вашего примера вы можете сделать это так:
var i:int;
var isBaxterInMilwaukee:Boolean;
isBaxterInMilwaukee = false;
for (i = 0; i < arrayLen && !isBaxterInMilwaukee; i++)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
}
Таким образом, вам не понадобится перерыв, и он будет более читабельным, чем цикл while.
Я вижу разрыв в обеих петлях, это правильно?
В любом случае:
- Я бы выбрал цикл FOR, если известно количество (максимальное количество) итераций перед запуском цикла.
- В противном случае я бы выбрал ПОКА.
- В цикле FOR я свободно использую BREAK.
- В цикле WHILE я предпочитаю использовать сложное условие вместо BREAK (если это возможно).
Моя теория состоит в том, что существует полезная абстракция программирования, подобная «соотношению сигнал / шум», которое является «отношением проблемы к инструменту» - доброту можно измерить в одном измерении тем, сколько времени я трачу на размышления о проблеме и его решение по сравнению со временем, которое я трачу на размышления о том, как использовать инструмент (в данном случае синтаксис языка).
Исходя из этого, я стараюсь чаще использовать меньше конструкций, потому что я (и, надеюсь, те, кто следит за ним) могу быстрее и точнее понять суть структуры моего кода. И поскольку варианты «for loops» довольно хорошо справляются со случаями, когда другие могут быть использованы (без искажений), я использую их в качестве первого предпочтения, когда они взаимозаменяемы.
И приятно иметь все, что вам нужно знать (по-разному) о правилах циклов, в одной строке в верхней части цикла for. Я также предпочитаю ставить переключатель «по умолчанию» первым в тестах по той же причине.
Но последовательность и ясность - вот главные соображения. YMMV, конечно.
У меня есть опыт работы с C++, и поэтому у меня все еще бывают моменты, когда я пытаюсь «думать как компилятор». Циклы while имеют тенденцию приводить к более жесткому коду, поэтому циклы for рассматривались только в том случае, если вы знали, что собираетесь каждый раз перебирать каждый элемент в массиве.
EDIT: теперь я считаю, что это перебор, если вы используете .Net или что-то еще, вы не собираетесь компенсировать накладные расходы виртуальной машины парой жестких циклов. Я действительно считаю, что помнить «почему» определенных практик - это хорошо.
Между ними существует концептуальная разница. for
циклы предназначены для перебора дискретных наборов, а while
циклы - для повторения операторов на основе условия. Другие языки добавляют finally
предложения и конструкции цикла, такие как foreach
или until
. У них, как правило, значительно меньше традиционных for
петель.
В любом случае, я использую правило: for
циклы повторяются, а while
циклы повторяются. Если вы видите что-то вроде:
while (counter <= end) {
// do really cool stuff
++counter;
}
Тогда вам, вероятно, будет лучше с for
циклом, поскольку вы повторяете. Однако такие петли, как:
for (int tryCount=0; tryCount<2; ++tryCount) {
if (someOperation() == SUCCESS) {
break;
}
}
следует записывать в виде while
циклов, поскольку они действительно что-то повторяют, пока условие не станет истинным.
Мысль о том, чтобы не использовать, break
потому что это так жеgoto
плохо, как и довольно бессмысленно. Как тогда вы можете оправдать выброс исключения? Это просто нелокальный и недетерминированный переход! Между прочим, это не тирада против обработки исключений, а просто наблюдение.
Я бы определенно выбрал for + break. «For» - это легко узнаваемая идиома для «итерации по последовательности», и ее легче понять «итерация по последовательности; закончить раньше, если значение найдено », чем комбинированное условие цикла и остановки.
Доказательством этого может быть то, что вы, кажется, сделали две ошибки в коде условного цикла!
условие while (! isBaxterInMilwaukee || i == arrayLen) - вы имели в виду «(! (isBaxterInMilwaukee || i == arrayLen))»?
Оператор break не требуется, если вы используете переменную завершения цикла.
Лично я считаю, что простой «разрыв» намного легче читать, чем пытаться отслеживать переменную завершения цикла.
У проблемы два аспекта:
- Что делать (например: выяснить, есть ли в одном из предметов указанное лицо в локации)
- Как это сделать (например: использовать индекс, повторять и т. Д.)
В обоих примерах смешано то и другое, и трудно понять, что и как . Лучше всего, если бы мы могли выразить в коде только то, что часть. Вот пример (c # 3.5), который делает это с использованием шаблона спецификации
// what we are looking for?
IsPersonInLocation condition = new IsPersonInLocation("baxter", "milwaukee");
// does the array contain what we are looking for?
bool found = myArray.Find(item => condition.IsSatifiedBy(item));
// do something if the condition is satisfied
if (found) {
barkTwice();
}
Для полноты, вот определение класса для условия:
class IsPersonInLocation {
public string Person { get; set; }
public string Location { get; set; }
public IsPersonInLocation(string person, string location) {
this.Person = person;
this.Location = location;
}
bool IsSatifiedBy(item) {
return item["name"] == this.Person
&& item["location"] == this.Location;
}
}
Думаю, ни то, ни другое на самом деле не очень интересно. Вам следует искать конструкции более высокого уровня, если вам нужна удобочитаемость.
В JS:
if(myArray.some(function(o) { o.name == "baxter" && o.location == "milwaukee" }))
barkTwice();
или с некоторыми собственными утилитами
if(myArray.containsMatch({name:"baxter",location:"milwaukee"})
barkTwice();
Инкапсулируйте цикл в его собственном методе и используйте возврат для завершения обработки, когда ваше условие соответствия выполнено.
Пример кода C#:
class Program
{
static bool IsBaxterInMilwaukee(IList<WhoAndWhere> peopleAndPlaces)
{
foreach (WhoAndWhere personAndPlace in peopleAndPlaces)
{
if (personAndPlace.Name == "Baxter"
&& personAndPlace.Location == "Milwaukee")
{
return true;
}
}
return false;
}
static void Main(string[] args)
{
List<WhoAndWhere> somePeopleAndPlaces = new List<WhoAndWhere>();
somePeopleAndPlaces.Add(new WhoAndWhere("Fred", "Vancouver"));
somePeopleAndPlaces.Add(new WhoAndWhere("Baxter", "Milwaukee"));
somePeopleAndPlaces.Add(new WhoAndWhere("George", "London"));
if (IsBaxterInMilwaukee(somePeopleAndPlaces))
{
// BarkTwice()
Console.WriteLine("Bark twice");
}
}
public class WhoAndWhere
{
public WhoAndWhere(string name, string location)
{
this.Name = name;
this.Location = location;
}
public string Name { get; private set; }
public string Location { get; private set; }
}
}