Метки цикла Perl считаются GOTO? - PullRequest
17 голосов
/ 21 июня 2010

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

Рассмотрим следующий код:

Line:
    while( <> ) {
        next Line if (insert logic);
    }

Считается ли использование метки цикла в качестве перехода?

Вот что perlsyn в perldoc должен сказать:

Вот как программист C может кодировать определенный алгоритм в Perl:

for (my $i = 0; $i < @ary1; $i++) {
    for (my $j = 0; $j < @ary2; $j++) {
        if ($ary1[$i] > $ary2[$j]) {
            last; # can't go to outer :-(
        }
        $ary1[$i] += $ary2[$j];
    }
    # this is where that last takes me
}

В то время как вот как программист Perl более комфортно использует идиому:

OUTER: for my $wid (@ary1) {
    INNER:   for my $jet (@ary2) {
                 next OUTER if $wid > $jet;
                 $wid += $jet;
             }
       }

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

Ответы [ 11 ]

28 голосов
/ 21 июня 2010

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

http://userweb.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF

16 голосов
/ 21 июня 2010

Опасность ярлыков GOTO заключается в том, что они создают код спагетти и делают логику нечитаемой.Ничего из этого не произойдет в этом случае.Использование утверждений GOTO весьма обоснованно, большая часть защиты исходит от Дональда Кнута [ article ].

Изучение различий между вашим примером C и Perl ... Если вырассмотрите, что происходит на уровне ассемблера с вашими C-программами, все равно все компилируется в GOTO.И если вы выполняли какое-либо MIPS или другое программирование на ассемблере, то вы видели, что большинство этих языков не имеют циклических конструкций, только условные и безусловные ветви.

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

5 голосов
/ 21 июня 2010

Кого волнует, считается ли он goto, если он облегчает понимание кода? Использование goto часто может быть более читабельным, чем набор дополнительных тестов в условиях if () и циклах.

3 голосов
/ 21 июня 2010

IMO, сравнение вашего кода несправедливо. Цель - читаемый код.

Чтобы быть справедливым, вы должны сравнить идиоматический вложенный цикл Perl с метками против одного без них. Стиль C для и заблокированный оператор if добавляет шум, который делает невозможным сравнение подходов.

Ярлыки:

OUTER: for my $wid (@ary1) {
    INNER:   for my $jet (@ary2) {
                 next OUTER if $wid > $jet;
                 $wid += $jet;
             }
       }

без ярлыков:

for my $wid (@ary1) {
   for my $jet (@ary2) {
       last if $wid > $jet;
       $wid += $jet;
   }
}

Я предпочитаю помеченную версию, потому что она явно о влиянии условия $wid > $jet. Без меток вы должны помнить, что last работает во внутреннем цикле и что, когда внутренний цикл завершен, мы переходим к следующему элементу внешнего цикла. Это не совсем ракетостроение, но это реальные, очевидные, познавательные накладные расходы. При правильном использовании метки делают код более читабельным.

Обновление:

stocherilac спросил, что произойдет, если у вас есть код после вложенного цикла. Это зависит от того, хотите ли вы пропустить это на основе внутреннего условного выражения или всегда выполнять его.

Если вы хотите пропустить код во внешнем цикле, помеченный код работает как нужно.

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

OUTER: for my $wid (@ary1) {
    INNER:   for my $jet (@ary2) {
                 next OUTER if $wid > $jet;
                 $wid += $jet;
             }
       }
       continue {
           # This code will execute when next OUTER is called.
       }
2 голосов
/ 21 июня 2010

Я бы ответил так, и я не уверен, достаточно ли это отличается от того, что говорили другие:

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

if (1) {
  goto BAR;
  die 'bar'
}
BAR:

Это должно сработать, очевидно, но это не сработает (не может двигаться вэто направление).

if (0) {
  BAR:
  die 'bar'
}
goto BAR;

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

LOOP: while (1) {
  next LOOP if /foo;
}

чем-то хуже, чем

while (1) {
  next if /foo/;
}

, что просто нелогично, если исключить стиль.Но, если говорить о стиле, последний вариант намного проще для чтения - и он не дает вам искать нужный ярлык.Читатель знает больше с next (что вы перезапускаете цикл в текущей области видимости), и это лучше.

Давайте рассмотрим другой пример

while (1) {

  while (1) {
    last;
  }

  stuff;

}

-vs-

FOO: while (1) {

  BAR: while (1) {
    next FOO;
  }

  stuff;

}

В последнем примере здесь next FOO, пропускает stuff - вы можете этого пожелать, но это плохая идея.Это означает, что программист до конца прочитал родительскую область видимости, чего, вероятно, лучше избегать.Таким образом, label не так плох, как goto, и иногда они могут упростить код;но в большинстве случаев их следует избегать.Обычно я переписываю циклы без label s, когда сталкиваюсь с ними на CPAN.

2 голосов
/ 21 июня 2010

Маркированные циклические переходы в Perl - это GOTO так же, как C break и continue.

2 голосов
/ 21 июня 2010

Я думаю, что это различие нечеткое, но вот что goto perldoc говорит о (нахмурившемся) выражении goto:

Форма goto-LABEL находит оператор, помеченный как LABEL, и возобновляет выполнение там.

...

Автор Perl никогда не чувствовал необходимости использовать эту форму goto (то есть в Perl, C - другое дело). (Разница в том, что C не предлагает именованные циклы в сочетании с управлением циклами. Perl делает, и это заменяет большинство структурированных применений goto в других языках.)

perlsyn perldoc , однако, говорит это:

Оператор while выполняет блок, пока выражение истинно. Оператор till выполняет блок до тех пор, пока выражение ложно. LABEL является необязательным и, если присутствует, состоит из идентификатора, за которым следует двоеточие. LABEL идентифицирует цикл для операторов управления циклом next, last и redo. Если LABEL опущен, оператор управления циклом ссылается на самый внутренний замкнутый цикл. Это может включать в себя динамический просмотр вашего стека вызовов во время выполнения, чтобы найти LABEL. Такое отчаянное поведение вызывает предупреждение, если вы используете прагму use warnings или флаг -w.

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

Книга Learning Perl (5-е издание, стр. 162) гласит:

Если вам нужно работать с блоком цикла, который не является самым внутренним, используйте метку.

...

Обратите внимание, что метка именует весь блок; это не пометка целевой точки в коде. [В конце концов, это не goto .]

Это помогает прояснить ситуацию? Наверное, нет ...: -)

1 голос
/ 23 декабря 2010

4.4.4. Loop Control

Мы упоминали, что вы можете поместить LABEL в цикл, чтобы дать ему имя. LABEL цикла идентифицирует цикл для операторов управления циклом next, last и redo. LABEL называет цикл в целом, а не только его вершину. Следовательно, оператор управления циклом, ссылающийся на цикл, на самом деле «не идет» к самой метке цикла. Что касается компьютера, то ярлык можно было бы так же легко разместить в конце цикла. Но людям почему-то нравятся вещи, помеченные сверху.

Программирование на Perl

1 голос
/ 21 июня 2010

перерыв / последний и продолжение / следующий ЗДЕСЬ. Я не понимаю, почему кто-то пошел на такую ​​длину, чтобы избежать ключевого слова, но использовал другое ключевое слово, которое делает то же самое ...

1 голос
/ 21 июня 2010

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

IMO, этот переход типа Perl даже лучше, чем C "break" и "continue", потому что он проясняет, какой цикл мы разрываем или продолжаем, и делает его более устойчивым перед лицом изменений кода. (Кроме того, это также позволяет разорвать или продолжить внешний цикл.)

Есть эксперты, которые не любят перерыв / продолжение и тому подобное, но в какой-то момент между эмпирическими правилами и удобочитаемостью существует компромисс, и хорошо выбранный перерыв / продолжение или даже переход может стать более читабельным чем «политкорректный» код.

...