Pull to refresh

Comments 49

Единственным и самым «сильным» аргументов спорщиков против goto чаще всего в моей практике попадалось то, что это как раз «плохой тон» и goto портит программу, делая её запутанной. Без явного goto можно обойтись, но какой ценой?
приведу пример, в котором goto неоходим:
// lines
do
{
 // many lines
   if ( some_cond )
       break;
 // many lines
   if ( other_cond )
       break;
// lines
}
while (0);


либо же
// lines
 if ( some_cond )
       goto label_skip_some;
// lines
 if ( other_cond )
       goto label_skip_some;
// lines
label_skip_some:
А что мешает many lines выделить в отдельные методы, возвращающие some_cond и other_cond, и все эти проверки делать в одном месте?
if ( !some_cond )
    func1();
// lines
if ( !other_cond )
    func2();

Такой вариант? Если да, то делать остальные проверки после some_cond — лишняя трата времени, когда some_cond должен пропустить часть кода до label_skip_some. Вариант вычисления other_cond до проверки some_cond — не в этом примере, иначе был бы другой код.

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

Можно писать
if ( some_cond )
{
// lines
     if ( other_cond )
     {
          // lines
     }
}

Но при увеличении вложенности большей 2, код становится плохо читаемым.
Вот бывает да сложный выбор, либо репутация среди быдлокодеров либо красивый код :)
Кино ни о чём. Особенно если рассуждать в отрыве от какого-то конкретного языка. На практике в случае необходимости часто применяется досрочный return. Так же смотрите в Java пример реализации циклов с меткой (точнее, блоков с меткой), break и continue. Конечные автоматы редко пишутся вручную, но чаще есть переменная state и код вида state = new_state; break;

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

По третьему пункту, в том же PHP есть break N.
В PHP, как и в других языках много вещей, необходимости в которых нету.
Впрочем есть мнение, что он там чтобы злобно тролить самоуверенных неофитов :)
Впрочем, я искренне надеюсь, что однажды его оттуда выпилят без предварительного уведомления.
За предварительно вы то точно очень зря надеятесь, большая куча людей учавствуют и следят за развитием языка, а также это может делать каждый. Ну и в целом вой то поднимется точно ибо любители goto полюбому обнаружаться :)
учавствуют

обнаружаться

участвуют
обнаружатся

Извините, не могу равнодушно смотреть на учавкающих людей :)
«И мы еще боремся за почетное звание дома высокой культуры быта» (с)

вой то поднимется

Жду, злорадно потирая ладошки.
>участвуют
>обнаружатся

какая такая культура) особенно в комментариях к такой статье)
Rosenthal
«И мы еще боремся за почетное звание дома высокой культуры быта» (с)
«Имена собственные пишутся так, как слышатся»

«И мы еще боремся за почетное звание дома высокой культуры быта» (с)
Выход из множества циклов одновременно решается введением в язык именованных блоков кода и операторов break (и continue заодно) с аргументом — именем блока. Имена блоков также выполняют роль «комментариев» и повышают читаемость программы. Поскольку я до сих пор не встречал практической реализации именованныз блоков, честно считаю это своим собственным изобретением:)
В целом не имею ничего против goto, хотя и не использую в коде (просто не сталкивался с необходимостью). Не имею ничего против потому, что несмотря на все доводы «против», есть один большой довод «за»: языки возможностей лучше языков ограничений. Мало-ли для каких хакерских целей может понадобиться профессиональному программисту этот оператор? Это не нам решать. Хуже когда что-то нужно, а нету. А значит, пусть будет.
Это очень близко к меткам.
Мой вариант на примере rust (кстати впервые придумал такой синтаксис около 10 лет назад, тогда точно аналогов не было):
for x in range(0u, 5) outerLoop {
    for y in range(0u, 5) innerLoop {
        println!("{},{}", x, y);
        if y == 3 {
            break outerLoop;
        }
    }
}

Perl 5 вышел в свет 20 лет назад :)
Если говорить про JavaScript, то там это работает только для циклов.
Так ктож спорит :)
Никак не могу ожидать от человека считающегося авторитетным ни участия в споре о goto, ни попытки его разрешить. Спора ведь нет. То что выглядит как спор — это процесс самообучения новых разработчиков, которые выросли из «goto нельзя» начальной школы, начали думать своей головой, задавать вопросы и анализировать информацию. А установка «goto нельзя» очень нужна, потому что в неумелых руках он опасен. В остальном — обычный инструмент. Теперь по содержанию:

> оптимизация производится на уровне машинных кодов

В общем случае неверно, оптимизация производится на разных уровнях, начать можно непосредственно с AST. Для примера, даже простой транслятор может перевести for в инструкцию loop вместо dec/inc + cmp + jxx. Для оптимизации произвольных переходов уже нужна сложная логика.

> Отдельную функцию можно оформить в виде inline-функции… Но тогда программа будет занимать больше памяти

Не будет, с чего бы ей занимать больше памяти?

> Конечные автоматы (пример кода) // Достойных реализаций без GOTO не обнаружено

Плохо искали. Посмотрите, например, ragel — он умеет несколько способов представления сгенерированных автоматов, в том числе с goto. И с ним, к слову, не всё однозначно: «In general, the goto FSM produces faster code but results in a larger binary and a more expensive host language compile.»

> в которых без него порой никак (C, C++

Мне правда интересно было бы увидеть пример где без goto действительно «никак» в C++. В C, например, на нём реализуются деструкторы, и лучшего решения действительно нет, но в C++ для этого есть встроенные средства.
Согласен свами, кроме одного мифа: на самом деле, для современных процессоров инструкция loop медленнее чем inc + jmp. (SO)
Хотел написать оговорку, но забыл. Да, loop неактуален, но это был просто пример.
В C, например, на нём реализуются деструкторы, и лучшего решения действительно нет

два чаю! Причём вариант очень хорош со всех сторон, со стороны наглядности и понятности — тоже.
Конечные автоматы реализуются циклом:

var state = 'START';
for (;;) switch (state) {
    case 'START':
         state = 'NEXT';
         break;
    case 'NEXT':
        state = 'STOP';
        break;
    case 'STOP':
        return 'RESULT';
}


Да, с GOTO конечно было бы прикольней, но и без него неплохо.
Вместо того чтобы сразу перейти на нужное состояние автомата будет гонять цикл, делать многочисленные проверки чтобы перейти к следующему состоянию. Причём чем дальше от начала состояние тем дольше будет вести путь к нему — неоправданный расход процессорного времени, хотя такая форма и выглядит красиво(сомнительно).
В вашем случае, чтобы перейти в состояние STOP нужно сделать как минимум 3 проверки и два перехода внутри цикла, не считая перехода по команде break из предыдущего состояния на начало цикла. А если отдельных состояний больше сотни и автомат должен работать Realtime?
О чём и говорится в статье… многие в этом споре берут только один аспект применения GOTO.
Циклом можно принебречь в данном случае, ибо в большей части современных компиляторов / виртуальных машин есть Loop Unrolling, не думаю что цикл будет медленнее чем GOTO (в случае оптимизации!). Не очень понял чем моя конструкция будет отличаться от того же самого но сделанного при помощи GOTO… если вы про лишние проверки на каждой итерации цикла, то switch он тем и отличается от if'ов, что в основе его работы лежит т. н. таблица переходов, которая также строится на этапе оптимизации в ходе компиляции. Поэтому реально сомневаюсь в том что производительность будет как то отличаться.
switch совершенно не обязан работать за O(N) (https://en.wikipedia.org/wiki/Switch_statement#Compilation). Скорее всего он скомпилируется в таблицу и оверхед составит всего лишь пару инструкций на каждую смену состояния. Но да, с goto в данном случае будет быстрее и лучше читается.
конечные автоматы лучше реализуются с помощью подобного кода:
Код
typedef enum {STATE1, STATE2..} tStateNum;
typedef void (*tProcessEvent)(void);
typedef struct
{
   tStateNum current_state, next_state;
   tProcessEvent process;
}tStateDescriptor;
tStateDescriptor States[]={...}
...
tStateDescriptor currState = ...
while (1)
{
   currState->process();
   currState = GetStateByNum(States, curr_state->next_state);
}

Для полного ускорения данного решения можно убрать current_state и вместо GetStateByNum делать
currState = &(States[curr_state->next_state]);


Использование goto и switch по сравнению с этим даёт только простыни кода.
Да я и не говорил что loop & switch — это самая лучшая реализация. Таблицы перехода никто не отменял. Было бы интересно сравнить накладные расходы loop & switch против lookup и вызовы функций. Я помню как то пытался сделать такое, но почему то это оказалось медленнее. Правда это был JavaScript…
Не достаточно ограничений «переход только вперёд, нельзя заходить в блок».
if (cond)
  goto x;

T a = GetA();

x:
  Display(a);


Чему равно a при срабатывании cond? И к каким проблемам в runtime-е это приведет?
Умный компилятор говорит, что так нельзя, ибо goto прыгает через инициализацию переменной. Ежели умудрится скомпилировать, то, по идее, что на стеке будет, то в «a» и попадет.
Сногсшибательно выбрана тема для первой публикации…
А может кто-нибудь объяснить, почему так категорически нельзя переходить назад? Почему опасно переходить вперёд вокруг описания новой переменной, понятно. Но «обнаружить мелкую проблему, что-нибудь исправить и повторить попытку» — чем плохо?
«Категорически нельзя» даже про деление на ноль не говорят :-)
Безусловные переходы назад чреваты мертвыми циклами, довольно таки трудноотлавливаемыми. Это по опыту ковыряния в чужом ассемблерном коде.
А обнаруживать мелкую проблему и исправлять надо на этапе написания, а не в процессе выполнения :-) Ежели она не вполне исправится, и снова пойдем назад (а гарантии, что исправится — никакой, это же проблема все-таки) — получим красивую картину «кот, ловящий свой хвост».
Что значит — на этапе написания? «Проблема» возникает на этапе выполнения. Например, обнаружили, что мотор не включился — что-то подёргали и снова пошли в точку программы, где надо выполнить все действия, связанные с его включением. Цикл «по включению мотора» ставить нехорошо, поскольку это может быть не единственной причиной возврата, кроме того, сама проверка может быть где-то внутри if{} (в случае, если мотор нужен не всегда).
А безусловные переходы назад есть в любом цикле for с пропущенной второй частью.
Я там как бы смайлик поставил… Как бы потому, что понял, что имелась в виду проблема на этапе выполнения. Но шутки-шутками, а решать такие проблемы надо действительно на этапе разработки — чтобы для их решения не требовались безусловные переходы назад. В крайнем случае — условные, с защитой от зависания.
По поводу последнего предложения… Ну, таки да, а еще есть while(true) и еще масса способов организовать мертвый цикл. Для человека, у которого веревка слишком длинная, это очередной способ прострелить ногу, такой же простой и удобный, как goto. Всего лишь забыть условие для break или ошибиться в нем. Но на ЯВУ можно без этого обойтись, более того, в вариантах с for и while компилятор чаще всего укажет ошибку, а в ассемблере без безусловных переходов никуда не денешься.
Отладка чужого плохо откомментированного ассемблерного кода с безусловными переходами назад — офигенный дзен-тренажер.
А что такое безусловный переход и чем он отличается от условного? Скорее всего, этот goto будет в конце какого-то if-блока. Защита от зависания —
if(++nattempts<5) goto _retry;
throw new Exception("Failed to start motors in 5 attempts");

— скорее всего, тоже будет присутствовать. Но от наличия «goto назад» это вряд ли спасёт.
А в ассемблере? Все ли помнят, что цикл while надо записывать как «безусловный переход вперёд — там проверка условия и условный переход назад, в начало тела цикла»? Если нет, то в конце цикла наверняка будет безусловный переход на самое начало.
Если стоит цель экономии программной памяти, то goto применим. В остальном, большинство языков имеют более читабельные средства.
Отлаживать класс в несколько тысяч строк, в котором кто-то плохо выбрал метку для goto, ещё то удовольствие.
Шел 2015 год, а люди все продолжали обсуждать goto. Серьезно?
Хотелось бы видеть в списке литературы вот это исследование фактической статистики использования goto в программах на C с открытым кодом: peerj.com/preprints/826v1.pdf
Интересное исследование. Согласно их данным, в C, goto используют для хендлинга ошибок (более 80%), это вполне понятно. А вот то, что в популярных проектах (например, OpenLDAP), goto убирали в процессе дебага всего паре процентов случаев — интригующе.
Sign up to leave a comment.

Articles