Главная страница проекта ИНФОРМАТИКА-21

Наука Школе

История изгнания оператора GOTO

С оператором GOTO к настоящему времени достигнута полная ясность: в языке программирования для серьезной работы оператора GOTO быть не должно. История его изгнания весьма поучительна.

Наличие GOTO в машинном языке понятно: там важна экономия средств, а с помощью одного этого оператора можно смоделировать любые структуры управления (операторы IF, FOR, WHILE и т.д.). По той же причине в 50-е годы GOTO был автоматически включен в первые языки программирования (например, фортран): ведь еще не было ясно, ни что такое хорошие программы, ни как их строить.
К концу 60-х гг. анализ уже накопленного опыта выявил (см. знаменитое письмо выдающегося теоретика систематического программирования Э.Дейкстры
Go To Statement Considered Harmful), что бесконтрольное использование GOTO приводит к «спагетти» — программам, хаотически напичканным операторами GOTO, в которых отследить все возможные состояния вычислительного процесса — а следовательно, и убедиться в корректности программы — становится практически невозможно.

Вслед за работами Дейкстры и др. возникла методология структурного программирования. 
(Здесь часто говорят о «программировании без
GOTO», хотя это слишком грубое упрощение.)
В структурном программировании был найден минимальный набор управляющих структур для систематического построения эффективных программ (
IF, WHILE и т.п.; все они представлены в Обероне/Компонентном Паскале).
В 70-е гг. «проблема
GOTO» еще активно дискутировалась, но к настоящему времени вопрос исчерпан: GOTO не нужен и вреден, и защищать его не возьмется ни один серьезный специалист. Наличие GOTO в старых языках следствие необходимости обеспечить совместимость с «программным наследием»; а его наличие в новых — признак некомпетентности проектировщика.

Но почему оператор GOTO следует вообще исключать из языка программирования, ведь, казалось бы, им можно просто не пользоваться, оставив лишь на всякий пожарный случай? Ответ заключается в следующем.

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

Но такие заплатки как ржавчина: стоит ржавчине появиться в одной точке, и она начинает расти, разъедая конструкцию.

Проблема усугубляется в случае программистов-самоучек — а таких большинство, т.к. цифровая революция развивается слишком быстро, требуя бОльшего числа специалистов, нежели может поставлять система образования, сама в значительной степени построенная из самоучек: нередко «образование» сводится к семестровому курсу, в котором объясняется синтаксис фортрана или C под сопровождением благоглупостей вроде «программирование — просто раздел прикладной математики» или «настоящему программисту все равно, на каком языке писать».
Программисты-самоучки, особенно начинающие, нередко убеждены, что «крутизна» программирования в том, чтобы как можно хитроумней использовать как можно больше средств языка в каждой программе: ведь им пришлось учить про них к экзамену!
Из документации, где просто перечислены все средства — как и из большинства руководств, сляпанных по принципу разжевывания документации — невозможно узнать, что то или иное средство было просто сделанной когда-то ошибкой дизайна.

Поэтому наилучшее решение проблемы GOTO — радикальное: просто исключить его из языка.

Пример. Пусть дан массив из n целочисленных элементов a[0] ... a[n-1].  Описание на Обероне/Компонентном Паскале:

    VAR a: ARRAY n OF INTEGER;

Пусть про этот массив известно, что один из элементов равен x. Требуется найти этот элемент и записать его позицию в целую переменную i.

Типичный начинающий программист (и многие «профессионалы») сразу напишет FOR c вложенной проверкой и начнет искать способ выйти из цикла:

    FOR i := 0 TO n-1 DO
        IF a[i] = x THEN ... END
    END

Но в Обероне/Компонентном Паскале из цикла FOR выйти просто так нельзя. Если не отказываться от цикла FOR (см. ниже решение этой задачи с циклом WHILE), то единственный способ это сделать — заключить цикл в процедуру и использовать оператор RETURN, позволяющий выйти из процедуры в произвольной точке:

    PROCEDURE Найти (VAR pos: INTEGER);
        VAR  i: INTEGER;
    BEGIN
        FOR i := 0 TO n-1 DO
            IF a[i] = x THEN pos := i; RETURN END
        END
    END Найти

Тогда в основной программе вместо цикла нужно вызвать эту процедуру:

    Найти( i )

Еще можно заменить FOR на оператор LOOP, в теле которого допускается выход на конец цикла (оператор EXIT).
Но ни в том, ни в другом случае нельзя «выпрыгнуть» вообще в произвольную точку вне цикла:
RETURN «прыгает» на конец процедуры, а EXIT — на конец цикла. Таким образом программа остается достаточно структурированной.

Кстати, вот классическое «правильное» решение в стиле Дейкстры, выраженное на Обероне/Компонентном Паскале:

    i := 0;
    WHILE  (i < n) & (a[i] # x)  DO
        i := i + 1
    END

Напомним, что вычисление логических выражений в Обероне/Компонентном Паскале производится по правилу «короткого замыкания»: если результат логической операции можно определить по первому операнду, то второй не вычисляется.

В данном случае по достижении i = n первый операнд (i < n) даст FALSE, так что результатом операции & (логическое И) тоже будет FALSE, и выход из цикла произойдет сразу, без вычисления второго операнда. Заметим, что вычисление второго операнда привело бы к ошибке и аварийной остановке программы из-за выхода за верхнюю границу массива.
(О проверках выхода за границы массивов в Обероне/Компонентном Паскале см. здесь.)

Правило весьма удобно при систематической разработке алгоритмов и приводит к лаконичным программам; одно это правило исключает множество ситуаций, где наивный инстинкт требует использовать GOTO. Много примеров на этот счет — в том числе гораздо менее тривиальных, чем приведенный — дано в книгах Гриса и Дейкстры в нашем списке.

Заметим еще, что по приведенной стандартной схеме решается большое число задач, сводящихся к задаче поиска:

    <инициализация цикла>
    WHILE  <условие ограничения поиска> & ~(<условие окончания поиска>)  DO
        i := i + 1
    END

Логическое значение, выражающее успех поиска, равно значению выражения <условие ограничения поиска> после выхода из цикла.

Главная страница проекта ИНФОРМАТИКА-21

Наука Школе