Современные программисты хорошо знают, что операторы перехода — это плохо. Вводные материалы по программированию активно обескураживают и даже опускают это утверждение. Это ярлык для спагетти-кода. Это почему?

В марте 1968 года известный ученый-компьютерщик Эдсгер В. Дейкстра написал письмо редактору Communications of the ACM. Оно было метко названо Перейти к заявлению, которое считается вредным. В нем Дейкстра впервые различает текстовый файл программы и процесс выполнения программы. Он отмечает, что мы особенно плохо схватываем последнее.

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

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

Если программа представляет собой чистую конкатенацию, скажем, операторов присваивания, достаточно указать в тексте программы точку между двумя последовательными описаниями действий... Если мы включим условные предложения... факт остается фактом: ход выполнения процесс по-прежнему характеризуется одним текстовым индексом.

Если мы начнем вызывать подпрограммы внутри подпрограмм в нашей программе, нам понадобится ряд индексов.

При включении процедур мы можем характеризовать ход процесса через последовательность текстовых индексов, длина этой последовательности равна динамической длине вызова процедуры.

Если у нас есть циклы, то мы можем использовать число для подсчета количества итераций, которые мы выполнили до сих пор. Любой, кто писал цикл for, знаком с этой концепцией: это целое число i в for (int i = 0; i < max; i++).

Эти временные координаты генерируются алгоритмически «вне контроля программиста». Таким образом, механически вычисляя временной индекс, мы всегда знаем, «где» находится программа в данный момент.

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

Необузданное использование оператора go to приводит к тому, что становится ужасно трудно найти осмысленный набор координат для описания хода процесса.

Допустим, у нас есть функция main(), которая выполняет 10 операторов, затем вызывает функцию func(), которая выполняет цикл в качестве первого оператора. Кроме того, предположим, что в данный момент программа выполняет 5-ю итерацию цикла. Мы можем легко описать, «где» находится наша программа, с помощью координаты: main 10, func 1 iteration 5. Если бы тот же самый код был полностью реализован с операторами перехода, координатой было бы какое-то произвольное число. Первая координата более семантически значима, и первый код легче отлаживать.

Операторы перехода теперь почти полностью заменены методами структурного программирования, такими как условные операторы и циклы, первыми из которых стал сам Джикстра. Однако на днях я столкнулся с операторами перехода в учебнике, написанном в 1992 году. Следующий код реализует конечный автомат, который удаляет 0, застрявшие между 1, и наоборот. Например, строка 0000100001111011 становится 0000000001111111.

void bounce() {
  char x;
  /* state a */
  a: putchar('0');
  x = getchar();
  if (x == '0') goto a;
  if (x == '1') goto b;
  goto finis;

  /* state b */
  b: putchar('0');
  x = getchar();
  if (x == '0') goto a;
  if (x == '1') goto c;
  goto finis;

  /* state c */
  c: putchar('1');
  x = getchar();
  if (x == '0') goto d;
  if (x == '1') goto c;
  goto finis;

  /* state d */
  putchar('1');
  x = getchar();
  if (x == '0') goto a;
  if (x == '1') goto c;
  goto finis;

  finis: ;
}

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

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