Главная страница «Первого сентября»Главная страница журнала «Информатика»Содержание №1/2008


Мнения

О вреде оператора goto

От редакции. Споры о том, большее или меньшее зло оператор goto (или break/continue), периодически возникают среди наших коллег. При этом аргументы тех, кто считает, что goto — это очень плохо, нередко сводятся к тому, что “даже Дийкстра об этом говорил”. К сожалению, при ближайшем рассмотрении выясняется, что мало кто знает, что же именно в точности говорил Дийкстра. Поэтому мы решили опубликовать перевод оригинальной статьи, на которую так часто ссылаются. Конечно, некоторые фрагменты этой статьи слишком сильно привязаны к контексту (частично устаревшему), однако это не мешает понять основную мысль выдающегося специалиста.

Эдсгер В. Дийкстра

За многие годы я утвердился во мнении о том, что квалификация программистов — функция, обратно зависящая от частоты появления операторов goto в их программах. Позже я открыл, почему оператор goto производит такой пагубный эффект, и я убежден в том, что оператор goto должен быть отменен в языках программирования “высокого уровня” (т.е. отовсюду, кроме, возможно, простого машинного кода). В то время я не счел это открытие слишком важным. Теперь же я отправляю свои соображения для публикации, потому что меня подтолкнула к этому развернувшаяся сейчас дискуссия на эту тему.

Мое первое замечание состоит в том, что, хотя деятельность программиста заканчивается с конструированием корректной программы, процесс, который выполняется под управлением этой программы, является истинной сущностью этой деятельности; этот процесс должен завершиться с заданным эффектом; поведение этого процесса должно удовлетворять заданным спецификациям. Хотя, когда программа уже изготовлена, “изготовление” соответствующего процесса поручается машине.

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

Давайте теперь рассмотрим, как мы можем охарактеризовать состояние процесса. (Вы можете представить себе этот вопрос очень конкретно: предположите, что процесс, рассматриваемый как последовательность действий во времени, остановлен после произвольного действия, какие данные мы должны иметь, чтобы точно определить порядок, в котором следует повторить процесс, чтобы дойти до той же точки?) Если текст программы представляет собой просто сцепление, скажем, операторов присваивания (в рамках данной дискуссии будем рассматривать их как описания одиночных действий), то достаточно в тексте программы указать точку между двумя последовательными описаниями действий. В отсутствие оператора goto я могу позволить себе синтаксическую неоднозначность в последних трех словах предыдущего предложения: если мы разбираем их как “последовательные (описания действий)”, то мы имеем в виду последовательность в тексте; если же мы разбираем их как “описания (последовательных действий)”, то мы имеем в виду последовательность во времени. Давайте назовем указатель на соответствующее место в тексте “текстуальным индексом”.

Когда мы вводим условное предложение (if B then A), предложение альтернатив (if B then A1 else A2), предложения выбора, введенные Хоаром [C. A. R. Hoare] (case [i] of (A1, A2, ..., An)), или условные выражения Маккарти [J. McCarthy] (B1 -> E1, B2 -> E2, ..., Bn -> En), сохраняется тот факт, что состояние процесса продолжает характеризоваться единственным текстуальным индексом.

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

Теперь давайте рассмотрим предложения повторения (как while B repeat A или repeat A until B ). С позиций логики эти предложения избыточны, потому что мы можем выразить повторения при помощи рекурсивных процедур. Из соображений реалистичности я не хочу их исключать: с одной стороны, предложения повторений могут быть весьма удобно реализованы на современном ограниченном оборудовании, с другой стороны, модель мышления, известная как “индукция”, делает нас хорошо подготовленными для интеллектуального восприятия процессов, порождаемых предложениями повторения. С введением предложений повторения текстуальных индексов уже недостаточно для описания динамического состояния процесса. С каждым входом в предложения повторения, однако, мы можем связать так называемый “динамический” индекс, исправно подсчитывающий порядковый номер текущего повторения. Если предложения повторения (так же, как вызовы процедур) могут применяться с вложением, мы находим, что теперь развитие процесса может быть однозначно охарактеризовано последовательностью (смешанной) текстуальных и/или динамических индексов.

Суть в том, что значения этих индексов не управляются программистом; они генерируются (то ли при написании программы, то ли при динамическом выполнении процесса) независимо от того, желает он этого или нет. Они обеспечивают независимые координаты, в которых описывается состояние процесса.

Почему нам нужны эти независимые координаты? Причина в том — и это представляется неотъемлемым для последовательных процессов, — что мы можем интерпретировать значение переменной только с учетом состояния процесса. Если мы хотим подсчитать число N, скажем, людей в комнате, изначально пустой, мы можем достичь этого путем увеличения N на единицу всякий раз, когда мы видим, что кто-то вошел в комнату. В промежуточный момент, когда мы увидели кого-то входящего в комнату, но еще не выполнили последующего увеличения N. Его значение равно числу людей в комнате минус один!

Разнузданное применение оператора goto имеет прямым следствием то, что становится ужасно трудно найти осмысленный набор координат, в которых описывается состояние процесса. Обычно люди принимают во внимание также и значения некоторых избранных переменных, но это не вопрос, потому что от состояния процесса зависит, как понимать эти значения! С оператором goto можно, конечно, все еще описывать состояние процесса однозначно при помощи счетчика числа действий, выполненных от старта программы (т.е. некоторой разновидности нормализованных часов). Трудность в том, что такие координаты, хотя они и уникальные, просто бесполезные. С такими координатами система становится чрезвычайно сложной, сталкиваясь с необходимостью определения всех тех точек состояния, в которых, скажем, n равно числу людей в комнате минус один!

Оператор goto сам по себе просто слишком примитивен; он создает слишком сильное побуждение внести путаницу в программу. Рассмотренные предложения могут оцениваться и приветствоваться как сдерживающие его применение. Я не заявляю, что упомянутые предложения являются исчерпывающими в том смысле, что они удовлетворяют любым требованиям, но какие бы предложения ни приводились (например, предложение прекращения), они должны удовлетворять тому требованию, чтобы при них могла бы поддерживаться независимая от программиста система координат для описания процесса полезным и осуществимым способом.

Трудно закончить эти заметки без благодарностей. Могу ли я судить о том, кто оказал влияние на мои рассуждения? Совершенно очевидно, что не обошлось без влияния Питера Ландина и Кристофера Страчи. Наконец, я хочу отметить (я помню это совершенно точно), как Хайнц Земанек на встрече “pre-ALGOL” в начале 1959 г. в Копенгагене предельно ясно выразил свои сомнения в том, должен ли оператор goto трактоваться как такой же синтаксический базис, как оператор присваивания. До некоторой степени я виню себя за то, что не имел тогда представления о значимости его замечания.

Замечания о нежелательности оператора goto далеко не новы. Я помню, что читал четкие рекомендации ограничивать использование оператора goto аварийным выходом, но не помню точно, где, вероятно, это было сделано Хоаром. Вирт и Хоар вместе делают замечание в том же направлении, мотивируя конструкцию выбора: “Как и условия, она отражает динамическую структуру программы более ясно, чем оператор goto и переключатели, и она устраняет необходимость введения в программу большого числа меток”.

Эд. В.. Дийкстра

TopList