Оправдывает ли это заявления goto?


15

Я столкнулся с этим вопросом секунду назад, и я извлекаю часть материала оттуда: есть ли название для конструкции 'break n'?

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

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

Я знаю, что в учебниках нравится говорить, что заявления goto - это дьявол, и я их совсем не люблю, но оправдывает ли это исключение из правила?

Я ищу ответы, которые касаются n-вложенных циклов.

ПРИМЕЧАНИЕ. Независимо от того, отвечаете ли вы «да», «нет» или где-то посередине, ответы совершенно не принимаются . Особенно, если ответ «нет», то предоставьте хорошую, законную причину (почему, в любом случае, это не слишком далеко от правил Stack Exchange).


2
Если вы можете записать условие внутреннего цикла, for (j = 10; j > 0 && !broken; j--)то вам не понадобится break;оператор. Если вы переместите объявление в brokenдо того, как начнется внешний цикл, тогда, если это можно записать так, как for (i = 0; i < 10 && !broken; i++)я думаю, нет break; необходимости.
FrustratedWithFormsDesigner

8
Данный пример взят из C # - ссылка на C # goto: goto (C # Reference) - «Оператор goto также полезен для выхода из глубоко вложенных циклов».

О боже, я никогда не знал, что у c # было заявление goto! Вещи, которые вы узнаете на StackExchange. (Я не могу вспомнить, когда я в последний раз хотел использовать функцию goto, она, кажется, никогда не появлялась).
Джон Ву,

6
ИМХО, это действительно зависит от вашей "религии программирования" и текущей лунной фазы. Для меня (и для большинства людей вокруг меня) хорошо использовать goto, чтобы выбраться из глубоких гнезд, если цикл довольно мал, и альтернативой будет избыточная проверка переменной bool, введенной просто для прерывания условия цикла. Это тем более, когда вы хотите разорвать половину тела или когда после самого внутреннего цикла есть какой-то код, который тоже должен проверять bool.
PlasmaHH

1
@JohnWu gotoфактически требуется в переключателе C # для перехода к другому случаю после выполнения любого кода внутри первого случая. Это единственный случай, когда я использовал goto.
Дэн Лайонс

Ответы:


33

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

Вы утверждаете, что хотели, чтобы внешний цикл продолжался столько же, сколько i < 10самый внутренний, чтобы продолжался до тех пор j > 0.

Но на самом деле это не то, что вы хотели , вы просто не указали циклам реальное состояние, которое вы хотели, чтобы они оценили, а затем вы хотите решить его, используя break или goto.

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

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}

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

1
@pswg Я не вижу никакой печати кода iв конце внешнего цикла в вопросе OP.
Тулаинс Кордова

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

3
@pswg Полагаю, если вы действительно действительно хотите оправдать переход, вы можете придумать проблему, которая нуждается в ней, с другой стороны, я профессионально программировал в течение двух десятилетий, и у меня нет ни одной реальной проблемы, требующей решения. один.
Тулаинс Кордова

1
Целью кода было не предоставить действительный вариант использования goto(я уверен, что есть лучшие), даже если он породил вопрос, который, кажется, использует его как таковой, но чтобы проиллюстрировать основные действия break(n)в языках, которые не ' не могу это поддержать.
PSWG

34

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

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}

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

2
Просто примечание: в исходном коде он печатается iпосле окончания внешнего цикла. Таким образом, чтобы действительно отразить это iявляется важной ценностью, здесь return true;и так return false;должно быть return i;.
PSWG

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

6

Нет, я не думаю, что это gotoнеобходимо. Если вы напишите это так:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

Наличие &&!brokenна обоих циклах позволяет вам выйти из обоих циклов, когда i==j.

Для меня такой подход предпочтительнее. Условия цикла чрезвычайно ясно: i<10 && !broken.

Работающую версию можно увидеть здесь: http://rextester.com/KFMH48414

Код:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Выход:

разрывы цикла, когда я == j == 1
2
0

Почему вы вообще используете brokenв своем первом примере вообще ? Просто используйте разрыв на внутренней петле. Кроме того, если вам абсолютно необходимо использовать broken, зачем объявлять это вне циклов? Просто объявить его внутри в iпетле. Что касается whileцикла, пожалуйста, не используйте это. Честно говоря, я думаю, что он может быть даже менее читабельным, чем версия goto.
августа

@luiscubal: именованная переменная brokenиспользуется, потому что я не люблю использовать breakоператор (кроме случая переключения, но это все). Он объявляется вне внешнего цикла, если вы хотите прервать ВСЕ циклы, когда i==j. В общем, вы правы, brokenнужно только объявить перед самым внешним циклом, который он прервет.
FrustratedWithFormsDesigner

В вашем обновлении, i==2в конце цикла. Условие успеха также должно замкнуть приращение, если оно должно быть на 100% эквивалентно a break 2.
PSWG

2
Лично я предпочитаю broken = (j == i)вместо if и, если язык позволяет, помещать прерванное объявление внутри инициализации внешнего цикла. Хотя трудно сказать эти вещи для этого конкретного примера, поскольку весь цикл не работает (он ничего не возвращает и не имеет побочных эффектов), и если он что-то делает, он, возможно, делает это внутри if (хотя мне все еще нравится broken = (j ==i); if broken { something(); }лучше лично)
Мартейн

@Martijn В моем исходном коде он печатается iпосле окончания внешнего цикла. Другими словами, единственная действительно важная мысль - это когда именно она вырывается из внешнего цикла.
PSWG

3

Краткий ответ «это зависит». Я выступаю за выбор в отношении разборчивости и понятности вашего кода. Путь перехода может быть более разборчивым, чем изменение условия, или наоборот. Вы также можете принять во внимание принцип наименьшего удивления, руководство, используемое в магазине / проекте, и согласованность кодовой базы. Например, goto to switch или для обработки ошибок - обычная практика в кодовой базе ядра Linux. Также обратите внимание, что у некоторых программистов могут возникнуть проблемы с чтением вашего кода (либо возникшие с мантрой «goto is evil», либо просто не изученные, потому что их учитель так считает), и некоторые из них могут даже «судить вас».

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


2

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

Подумайте о вашем будущем рецензенте: ему / ей придется подумать: «Этот человек плохой кодер или это действительно тот случай, когда goto стоило того? Позвольте мне потратить 5 минут, чтобы решить это для себя или исследовать его на интернет «. С какой стати вы так поступаете со своим будущим кодером, если не можете использовать goto полностью?

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

При этом, да, вы произвели один из чуть более острых случаев. Ты можешь:

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

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

Между прочим, лучшая дискуссия, которую я видел, - это « Завершенный код» Стива Макконнелла . Если вам нравится критически относиться к этим проблемам, я настоятельно рекомендую читать, даже прикрытие, несмотря на его необъятность.


1
Это наша работа программистов, чтобы читать и понимать код. Не просто упрощенный код, если только это не характер вашего кода. Есть и всегда будут исключения из общности (а не правила) против использования gotos из-за понятной стоимости. Всякий раз, когда выгода превышает стоимость, когда следует использовать goto; как и в случае всех решений по кодированию в программной инженерии.
dietbuddha

1
@dietbuddha Нарушение принятых соглашений о кодировании программного обеспечения сопряжено с определенными издержками, которые следует должным образом учитывать. Существует необходимость в увеличении разнообразия структур управления, используемых в программе, даже если эта структура управления может хорошо соответствовать ситуации. Взлом статического анализа кода обходится дорого. Если это все еще стоит того, кто я такой, чтобы спорить.
Джечлин

1

TLD; DR: нет конкретно, да вообще

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

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

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


Можете ли вы порекомендовать какие-либо статьи в заявлении goto, в которых более подробно объясняется стоимость?
Thys

-1

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

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.