
Уже несколько лет я знаком с наблюдением, что качество программистов является убывающей функцией от плотности операторов go to в программах, которые они создают. Не так давно я обнаружил, почему использование оператора go to имеет столь пагубные последствия, и пришел к убеждению, что оператор go to должен быть упразднен во всех языках программирования «высокого уровня» (то есть везде, кроме, пожалуй, прямого машинного кода). В то время я не придавал этому открытию большого значения; теперь же я представляю свои соображения для публикации, поскольку в ходе самых последних дискуссий, где эта тема всплывала, меня к этому настоятельно призывали.
Прежде всего отмечу, что, хотя работа программиста завершается построением правильной программы, истинным предметом его деятельности является процесс, протекающий под управлением его программы, ведь именно этот процесс призван достичь желаемого результата; именно этот процесс своим динамическим поведением должен удовлетворять заданным спецификациям. Однако, как только программа готова, «создание» соответствующего процесса делегируется машине.
Во-вторых, отмечу, что наш ум лучше приспособлен к постижению статических отношений, а способность охватывать мысленным взором процессы, разворачивающиеся во времени, развита сравнительно плохо. Поэтому мы (как благоразумные программисты, осознающие ограниченность своего понимания) обязаны сделать все возможное, чтобы свести к минимуму смысловой разрыв между статической программой и динамическим процессом, добившись максимально прямой и очевидной связи между программой (развернутой в пространстве текста) и процессом (развернутым во времени).
Давайте теперь рассмотрим, как можно охарактеризовать ход процесса. (Эту проблему можно рассматривать вполне конкретно: допустим, процесс, представляющий собой временну́ю последовательность действий, прерывается после некоторого произвольного действия; какие данные нам нужно зафиксировать, чтобы можно было воспроизвести процесс вплоть до той же самой точки?) Если текст программы представляет собой простую последовательность, скажем, операторов присваивания (которые в рамках данного обсуждения считаются описаниями отдельных действий), достаточно указать точку в тексте программы между двумя последовательными описаниями действий. (При отсутствии оператора go to я могу позволить себе синтаксическую неоднозначность в последних трех словах предыдущего предложения: если мы разбираем их как «последовательные (описания действий)», то речь идет о последовательности в пространстве текста; если же как «(описания) последовательных действий» — то о последовательности во времени.) Давайте назовем такие указатели на подходящее место в тексте «текстовым индексом».
Мы можем добавлять условные конструкции (if B then A), альтернативные (if B then A1 else A2), операторы выбора, предложенные Ч.Э.Р. Хоаром (сase[i] of(A1, A2, ... , An)), или условные выражения, введенные Д. Маккарти (B1 → E1, B2 → E2, … , Bn → En) — но ход процесса все так же определяется единственным текстовым индексом.
Как только мы вводим в наш язык процедуры, мы должны признать, что единственного текстового индекса уже недостаточно. Если текстовый индекс указывает внутрь тела процедуры, то динамический ход процесса можно охарактеризовать, только если мы также укажем, на какой именно вызов процедуры мы ссылаемся. С введением процедур мы можем характеризовать ход процесса с помощью последовательности текстовых индексов, длина которой равна динамической глубине вложенности вызовов процедур.
Давайте теперь рассмотрим операторы повторения (например, while B repeat A или repeat A until B). С логической точки зрения такие операторы излишни, поскольку повторение можно выразить с помощью рекурсивных процедур. Однако я не хочу исключать их из практических соображений: с одной стороны, операторы повторения вполне удобно реализуются на современном ограниченном оборудовании; с другой стороны, шаблон рассуждений, известный как «индукция», хорошо позволяет нам сохранять интеллектуальный контроль над процессами, порождаемыми операторами повторения. При введении операторов повторения текстовых индексов оказывается уже недостаточно для описания динамического хода процесса. Однако с каждым входом в оператор повторения можно связать так называемый «динамический индекс», неукоснительно отсчитывающий порядковый номер текущего повторения. Поскольку операторы повторения (как и вызовы процедур) могут быть вложенными, мы видим, что теперь ход процесса всегда можно однозначно охарактеризовать с помощью (смешанной) последовательности текстовых и/или динамических индексов.
Суть в том, что значения этих индексов находятся вне контроля программиста; они создаются (либо текстом его программы, либо динамическим развитием процесса) независимо от его желания. Они задают независимые координаты, в которых можно описать ход процесса.
Зачем же нам нужны такие независимые координаты? Причина — и это, по-видимому, свойство, присущее всем последовательным процессам, — в том, что мы можем интерпретировать значение переменной лишь в контексте хода процесса. Если мы хотим подсчитать количество людей в изначально пустой комнате — назовем его n — этого можно добиться, увеличивая n на единицу всякий раз, когда мы видим, что кто-то входит в комнату. В тот промежуточный момент, когда мы уже увидели, как кто-то вошел в комнату, но еще не выполнили последующее увеличение n, его значение равно количеству людей в комнате минус один!
Бесконтрольное использование оператора go to имеет своим непосредственным следствием то, что становится чрезвычайно трудно найти осмысленный набор координат для описания хода процесса. Обычно в расчет также принимают значения некоторых тщательно выбранных переменных, но это неприемлемо, поскольку смысл этих значений может быть понят лишь относительно хода процесса. С помощью оператора go to, разумеется, все еще можно однозначно описать ход процесса, добавив счетчик, считающий количество действий, выполненных с момента запуска программы (своего рода нормализованные часы). Проблема в том, что такая координата, хоть и уникальна, совершенно бесполезна. В такой системе координат становится в высшей степени сложной задачей определить все те точки выполнения, где, скажем, n равно количеству людей в комнате минус один!
Оператор go to в его нынешнем виде попросту слишком примитивен; он практически провоцирует программиста устроить в своей программе бардак. Рассмотренные операторы можно рассматривать как средство обуздания его использования. Я не утверждаю, что упомянутые операторы исчерпывают все потребности в том смысле, что удовлетворят любые нужды, но какие бы операторы ни предлагались (например, операторы аварийного завершения), они должны отвечать требованию: должна существовать возможность поддерживать не зависящую от программиста систему координат, описывающую процесс полезным и удобным для управления образом.
Трудно завершить эту статью справедливыми благодарностями. Суждено ли мне определить, кто повлиял на ход моих мыслей? Довольно очевидно, что я не избежал влияния Питера Лэндина и Кристофера Стрейчи. И, наконец, я хотел бы засвидетельствовать (поскольку помню это совершенно отчетливо), как Хайнц Земанек на совещании по подготовке Algol в начале 1959 года в Копенгагене вполне определенно высказал свои сомнения в том, следует ли рассматривать оператор go to как синтаксически равноправный оператору присваивания. В некоторой, пусть и небольшой, мере я виню себя за то, что не сделал тогда должных выводов из его замечания.
Мнение о нежелательности использования оператора go to далеко не ново. Я припоминаю, что читал явную рекомендацию ограничивать его применение аварийными выходами, но не смог найти источник; вероятно, это замечание принадлежит Ч.Э.Р. Хоару. В [1, Sec. 3.2.1.] Вирт и Хоар высказывают соображение в том же духе, обосновывая использование конструкции case: «Как и условная конструкция, она отражает динамическую структуру программы яснее, чем операторы go to и switch, а также избавляет от необходимости вводить в программу большое количество меток».
В [2] Джузеппе Якопини, кажется, доказал (логическую) избыточность оператора go to. Однако упражнение по более или менее механическому преобразованию произвольной блок-схемы в схему без переходов рекомендовать не стоит. В этом случае нельзя ожидать, что получившаяся схема окажется прозрачнее исходной.
Источники
1. Wirth N., Hoare C.A.R. A contribution to the development of ALGOL. Comm. Acm 9 (June 1966), 413–432.
2. Böhm C., Jacopini G. Flow diagrams, Turing machines and languages with only two formation rules. Comm. ACM 9 (May 1966), 363–371.
Комментарии (17)

NeoNN
13.12.2025 06:05Goto прекрасно используется везде, где его использование оправдано. Во внутреннем коде дотнета его использования достаточно много.

liutas4x4
13.12.2025 06:05Дийкстра очень любил такие глобальные заявления. Потом кто-то (Уэзерелл?) ссылался на вот такое, ругая код отвалившегося (слава богу до старта) Апполона, что goto перерыгнул код аварийного останова.
У него же (?) или у Кнута была доп. концепция сопоставимого масштаба, что ошибка цикла должна вызывать его бесконечное исполнение, во избежание прыжка в запрещенные области.
Срач по этим темам был жуткий и длился лет 10.

Apxont
13.12.2025 06:05Готу в 25 году? Реали? Когда только начинал кодить 20 лет назад уже говорили что готу это плохо. Да и вообще его не убирают только из обратной совместимости, по моему, хотя надо бы уже давно.

NeoNN
13.12.2025 06:05Для кода бизнес-логики плохо, а для стейт машин (таких как в async await) и оптимизации циклов (и не только циклов), клинапа во внутреннем коде - хорошо.

rukhi7
13.12.2025 06:05Давайте теперь рассмотрим операторы повторения (например, while B repeat A или repeat A until B). С логической точки зрения такие операторы излишни, поскольку повторение можно выразить с помощью рекурсивных процедур.
ужасно интересно посмотреть как эти очень сложные конструкции переписать через простую рекурсию.
Mephi1984
Забавный факт, но в c# и в c++ до сих пор в 2025 году есть оператор goto
saipr
Я часто ссылаюсь на книгу Эндрю Таненбаума «Operating Systems Design and Implementation» на ее первое издание и на то, как в ней представлен язык Си.
В этом, по моему уникальном описании Си, отсутствует оператор goto.
longtolik
Главное, чтобы он поддерживался компилятором.
saipr
Да, поддерживается.
longtolik
Спасибо, те, с которыми я имел дело, все поддерживали, но, возможно, есть и такие, которые не поддерживают. Оператор перехода goto встречался мне в довольно профессиональных проектах.
maisvendoo
В исходном коде Qt 5 я насчитал 2007 случаев использования goto
Kelbon
проблемы?
kenomimi
Без него простая логика превращается в нечитаемую лапшу. Например, возврат из многократно вложеного цикла, при обходе N-мерного массива. Или очистка тучи полей после malloc(), если в какой-то момент что-то пошло не так - иначе у нас будет копипаст блока кода для очистки ресурсов N раз. В ядре linux очень хорошо видны правильные примеры применения goto...
Боль - это когда его используют в высокоуровневом коде как jmp в ассемблере, заменяя нормальные управляющие конструкции языка.