Чи це виправдовує goto заяви?


15

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

Це видається непотрібним складним способом для того, щоб люди повинні доручити програмі вийти з подвійного вкладеного циклу:

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

Я знаю, що підручники люблять говорити, що гото твердження - це чорт, і я сам їх зовсім не люблю, але чи це виправдовує виняток із правила?

Я шукаю відповіді, які стосуються 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 також корисний для виходу з глибоко вкладених циклів."

OMG, я ніколи не знав, що c # мав goto заяву! Те, що ви дізнаєтесь на StackExchange. (Я не можу пригадати востаннє, коли я хотіла можливості для goto statement, але, схоже, ніколи не з'явиться).
Джон Ву

6
IMHO це дійсно залежить від вашої "релігії програмування" та поточної місячної фази. Для мене (і більшості людей навколо мене) добре використовувати гото, щоб вийти з глибоких гнізд, якщо цикл досить малий, а альтернативою буде зайва перевірка змінної bool, запровадженої просто для того, щоб перервати стан циклу. Тим більше, коли ви хочете прорватися на півдорозі через тіло, або коли після внутрішньої петлі є якийсь код, який також повинен перевірити bool.
ПлазмаHH

1
@JohnWu gotoнасправді потрібен у перемикачі C # на перехід на інший випадок після виконання будь-якого коду в першому випадку. Це єдиний випадок, коли я використовував goto, хоча.
Ден Ліон

Відповіді:


33

Очевидна потреба у операторі go-to виникає у тому, що ви обираєте погані умовні вирази для циклів .

Ви заявляєте, що хотіли, щоб зовнішня петля тривала так довго, i < 10а внутрішня - до тих пір, поки вона тривала j > 0.

Але насправді це не те, чого ви хотіли , ви просто не сказали петлі реальний стан, який ви хотіли, щоб вони оцінили, тоді ви хочете вирішити це за допомогою перерви або 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в кінці зовнішньої петлі у питанні ОП.
Tulains Córdova

ОП посилався на код з мого запитання тут , але цю частину не залишив. Я не відповідав тобі, BTW. Відповідь цілком правильна щодо правильного вибору умов циклу.
pswg

3
@pswg Я думаю, якщо ви дійсно дуже хочете виправдати goto, ви можете вирішити проблему, яка потребує такої, з іншого боку, я вже два десятиліття професійно програмую і не маю жодної реальної проблеми, яка потрібна один.
Tulains Córdova

1
Метою коду було не надання дійсного випадку використання goto(я впевнений, що є кращі), навіть якщо він породив питання, яке, здається, використовує його як таке, але ілюструє основну дію break(n)мов, які не були ' t підтримувати його.
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);
}

Вихід:

цикл розривається, коли i == j == 1
2
0

Чому ви навіть використовувати brokenв першому прикладі взагалі ? Просто використовуйте перерву на внутрішній петлі. Крім того, якщо ви абсолютно повинні використовувати broken, навіщо це оголошувати поза петлями? Просто оголосити його всередині в iпетлі. Щодо whileциклу, будь ласка, не використовуйте це. Я, чесно кажучи, думаю, що вдається бути ще менш читабельним, ніж версія goto.
luiscubal

@luiscubal: Змінна з ім'ям brokenвикористовується, оскільки мені не подобається використовувати breakоператор (за винятком випадків переключення, але це стосується цього). Це оголошено поза зовнішньою петлею, якщо ви хочете розірвати ВСІ петлі, коли i==j. Ви маєте рацію, загалом, brokenвам потрібно оголосити лише перед самим зовнішнім циклом, що він зламається.
FrustratedWithFormsDesigner

У вашому оновлення i==2в кінці циклу. Умова успіху також повинна коротко замикати приріст, якщо він повинен бути 100% еквівалентним a break 2.
pswg

2
Я особисто віддаю перевагу, broken = (j == i)а не якщо і, якщо мова дозволяє, поклавши розбиту декларацію всередині ініціалізації зовнішньої петлі. Хоча важко сказати ці речі на цьому конкретному прикладі, оскільки весь цикл є неоперативним (він нічого не повертає і не має побічних ефектів), і якщо він щось робить, можливо, це робити всередині if (хоча мені все одно подобається broken = (j ==i); if broken { something(); }краще особисто)
Martijn

@Martijn У моєму оригінальному коді він друкується iпісля закінчення зовнішньої петлі. Іншими словами, єдине, що дійсно важливе, - це коли саме він виривається із зовнішньої петлі.
pswg

3

Коротка відповідь - "це залежить". Я рекомендую вибрати щодо розбірливості та зрозумілості вашого коду. Шлях до переходу може бути більш розбірливим, ніж налаштування умови, або навпаки. Ви також можете взяти до уваги принцип найменшого сюрпризу, настанову, що використовується в магазині / проекті та узгодженість бази коду. Наприклад, перехід до вимикача або виправлення помилок є звичайною практикою в базі коду Linux ядра. Також зауважте, що у деяких програмістів можуть виникнути проблеми з читанням вашого коду (або підняті мантрами "goto is evil", або просто не вивчені, оскільки так думає їхній вчитель), і деякі з них можуть навіть "судити вас".

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


2

У вашому випадку, goto є достатньою для поліпшення, щоб воно виглядало заманливо, але це не стільки вдосконалення, скільки варто порушити правило проти використання їх у кодовій базі.

Подумайте про свого майбутнього рецензента: він / йому доведеться подумати: "Це людина поганий кодер чи це насправді випадок, коли гото того варте? Дозвольте мені взяти 5 хвилин, щоб вирішити це для себе або дослідити це на Інтернет ". Чому б на землі ви зробили це своєму майбутньому кодеру, коли не можете використовувати goto цілком?

Взагалі цей менталітет стосується всіх "поганих" кодів і навіть визначає його: якщо він працює зараз, і в цьому випадку добре, але створює зусилля, щоб перевірити його правильність, що в цю епоху гото завжди буде робити, тоді не робіть Зроби це.

Коли це було сказано, так, ви створили один із дещо ширших випадків. Ви можете:

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

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

До речі, найкраще обговорення цього, що я бачив, - це Код Стіва МакКоннелла . Якщо вам подобається критично мислити над цими проблемами, я настійно рекомендую прочитати, навіть прикрившись, незважаючи на велику кількість.


1
Це наші програмісти по роботі з читанням і розумінням коду. Не просто спрощений код, якщо тільки це не є вашою базою кодів. Існують і завжди будуть винятки із загальності (а не правила) проти використання gotos через зрозумілу вартість. Всякий раз, коли вигода переважає вартість, коли гото слід використовувати; як це стосується всіх рішень кодування в інженерії програмного забезпечення.
дієтабудда

1
@dietbuddha є витрата на порушення прийнятих конвенцій програмного кодування, які слід адекватно враховувати. Збільшення різноманітності структур управління, що використовуються в програмі, є вартістю, навіть якщо ця структура управління може добре відповідати ситуації. Існує вартість розбиття статичного аналізу коду. Якщо це все-таки варто, то хто я, щоб сперечатися.
djechlin

1

TLD; DR: Ні конкретно, так загалом

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

Взагалі пророк проти goto- це спільність, заснована на розумінні природи gotoта вартості її використання. Частина програмної інженерії - це прийняття рішень щодо впровадження. Обґрунтування цих рішень відбувається шляхом зважування витрат у відношенні вигоди та кожного разу, коли переваги переважають за витрати, реалізація виправдана. Суперечка виникає через різні оцінки як вартості, так і вигоди.

Справа в тому, що завжди будуть винятки, коли а gotoє виправданим, але лише для деяких через різні оцінки. Шкода, що багато інженерів просто виключають методи з цілеспрямованим незнанням, тому що вони читають її в Інтернеті, а не витрачають час, щоб зрозуміти, чому.


Чи можете ви порекомендувати будь-які статті із заяви goto, що додатково пояснюють вартість?
Тис

-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.