Rambler's Top100 Service калинин.ru / программирование / c и c++ /  << 10.08.00 >>

Оператор безусловного перехода goto

Так уж сложилось, что именно присутствие или отсутствие этого оператора в яызке программирования всегда вызывает жаркие дебаты среди сторонников "хорошего стиля" программирования. При этом, и те, кто "за", и те, кто "против" всегда считают признаком "хорошего тона" именно использование goto или, наоборот, его неиспользование. Я не собираюсь изображать из себя сторонника одной из этих "школ", я просто хочу показать, что действительно есть места, где использование goto выглядит вполне логично.

Но сначала о грустном. Обычно в вину goto ставится то, что его присутствие в языке программирования позволяет делать примерно такие вещи:

int i, j;
for(i = 0; i < 10; i++)
{
  // ...

  if(condition1)
    {
      j = 4;
      goto label1;
    }

  // ...

  for(j = 0; j < 10; j++)
  {
    // ... 
  label1: 
    // ... 
    if(condition2)
      {
        i = 6;
        goto label2;
      }
  }

  // ...
label2:
  // ...
}

Прямо скажем, что такое использование goto несолько раздражает, потому что понять при этом как работает программа при ее чтении будет очень сложно. А для человека, который не является ее автором, так и вообще невозможно. Понятно, что вполне вероятны случаи, когда такого подхода требует какая-нибудь очень серьезная оптимизация работы программы, но делать что-то подобное программист в здравом уме не должен. На самом деле, раз уж я привел подобный пример, в нем есть еще один замечательный нюанс ---- изменение значения переменной цикла внутри цикла. Смею вас заверить, что такое поведение вполне допустимо внутри do или while; но когда используется for такого надо избегать, потому что отличительная черта for как раз и есть жестко определенное местоположение инициализации, проверки условия и инкремента (т.е., изменения переменной цикла). Я к чему: читатель исходного текста, увидев "полный" for (т.е. такой, в котором заполнены все эти три места) может и не заметить изменения переменной где-то внутри цикла. Хотя для циклов с небольшим телом это, наверное, все-таки допустимо --- такая практика обычно применяется при обработке строк (когда надо, например, считать какой-то символ, который идет за "спецсимволом", как "\\" в строках на C; вместо того, что бы вводить дополнительный флаг, значительно проще увидев "\" сразу же сдвинуться на одну позицию и посмотреть, что находится там). В общем, всегда надо руководствоваться здравым смыслом и читабельностью программы. Если здравый смысл по каким-то причинам становится в противовес читабельности программы, то это место надо обнести красными флагами, чтобы читатель сразу видел подстерегающие его опасности.

Тем не менее, вернемся к goto. Несмотря на то, что такое расположние операторов безусловного перехода несколько нелогично (все-таки, вход внутрь тела цикла это, конечно же, неправильно), это встречается. Я сам видел три вложенных бесконечных цикла, расположенных на пятистах строчках исходного текста, внутри которых примерно такой "зюзей" были расставлены goto. Понять такой текст, по-моему, просто невозможно.

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

Собственно, я как раз подошел к тому, что обычно называется "разумным" применением этого оператора. Вот пример:

switch(key1)
{
case q1 : 
    switch(key2)
    {
       case q2 : break;
    }
    break;
}

Все упрощено до безобразия, но, в принципе, намек понятен. Есть ситуации, когда нужно что-то в духе break, но на несколько окружающих циклов или операторов switch, а break завершает только один. Понятно, что в этом примере читабельность, наверное, не нарушена (в смысле, использовался бы вместо внутреннего break goto или нет), единственное, что в таком случае будет выполненно два оператора перехода вместо одного (break это, все-таки, разновидность goto).

Значительно более показателен другой пример:

bool end_needed = false;
for( ... )
{
   for( ... )
   {
      if(cond1) { end_needed = true; break; }
   }
   if(end_needed) break;
}

Т.е., вместо того, что бы использовать goto и выйти из обоих циклов сразу, пришлось завести еще одну переменную и еще одну проверку условия. Тут хочется сказать, что goto в такой ситуации выглядит много лучше --- сразу видно, что происходит; а то в этом случае придется пройти по всем условиями и посмотреть, куда они выведут. Надо сказать (раз уж я начал приводить примеры из жизни), что я не раз видел эту ситуацию доведенную до крайности --- четыре вложенных цикла (ну что поделать) и позарез надо инициировать выход из самого внутреннего. И что? Три лишних проверки... Кроме того, введение еще одной переменной, конечно же, дает возможность еще раз где-нибудь допустить ошибку, например, в ее инициализации. Опять же, читателю исходного текста придется постоянно лазить по тексту и смотреть, зачем была нужна эта переменная... в общем: не плодите сущностей без надобности. Это только запутает.

Другой пример разумного использования goto следующий:

int foo()
{
  int res;

  // ...
  if(...)
  {
     res = 10;
     goto finish;
  }
  // ...

finish:
  return res;  
}

Понятно, что без goto это выглядело бы как return 10 внутри if. Итак, в чем преимущества такого подхода. Ну, сразу же надо вспомнить про концептуальность --- у функции становится только один "выход", вместо нескольких (быстро вспоминаем про IDEF). Правда, концептуальность это вещь такая... неиспользование goto тоже в своем роде концептуальность, так что это не показатель (нельзя противопоставлять догму догме, это просто глупо). Тем не менее, выгоды у такого подхода есть. Во-первых, вполне вероятно, что перед возвратом из функции придется сделать какие-то телодвижения (закрыть открытый файл, например). При этом, вполне вероятно, что когда эта функция писалась, этого и не требовалось --- просто потом пришлось дополнить. И что? Если операторов return много, то перед каждым из них появится одинаковый кусочек кода. Как это делается? Правильно, методом "cut&paste". А если потом придется поменять? Тоже верно, "search&replace". Объяснять почему это неудобно не буду --- это надо принять как данность.

Во-вторых, обработка ошибок, которая так же требует немедленного выхода с возвратом используемых ресурсов. В принципе, в C++ для этого есть механизм исключительных ситуаций, но когда он отсутствует (просто выключен для повышения производительности), это будет работать не хуже. А может и лучше по причине более высокой скорости.

В третьих, упрощается процесс отладки. Всегда можно проверить что возвращает функция, поставить точку останова (хотя сейчас некоторые отладчики дают возможность найти все return и поставить на них точки останова), выставить дополнительный assert или еще что-нибудь в этом духе. В общем, удобно.

Еще goto очень успешно применятся при автоматическом создании кода --- читателя исходного текста там не будет, он будет изучать то, по чему исходный текст был создан, поэтому можно (и нужно) допускать различные вольности. Для примера посылаю к результатам lex или yacc.

В принципе, больше навскидку вспомнить примеров оправданного применения оператора goto я не смог, но это и не надо. Мне просто хотелось показать, что при правильном использовании он очень полезен. Надо только соблюдать здравый смысл, но это общая рекомендация к программированию на С/С++ (да и вообще, на любом языке программирования), поэтому непонятно почему goto надо исключать.

Резюме

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


Версия для печати


  Ссылки по теме:
Бъерн Страуструп
   Язык программирования C++, 3 издание.
  Рядом в разделе:
Виртуальный конструктор (12.08.00)
   Перелистывая архив сообщений из конференции SU.C_CPP, натолкнулся на любопытное письмо, которое решил поместить к себе на страничку. Автор --- опять Alexander...   >>>>
Запись структур данных в двоичные файлы (31.07.00)
   Чтение и запись данных, вообще говоря, одна из самых часто встречающихся операций. Сложно себе представить программу, которая бы совсем не нуждалась...   >>>>
  Рядом по дате:
Преобразование XML при помощи XSL (11.08.00)
   Одна из самых частых операций, которую требуется произвести с XML-документом, это показать его. Хочется предупредить сразу, что на этот раз я...   >>>>
Первый месяц (09.08.00)
   Месяц тому назад я нарисовал себе новую титульную страницу для размещения в интернете. В принципе, уже давно назревала мысль о том,...   >>>>
  Содержание:
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
   C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Просто так
Студенческое
Туризм
  Байки
Фотографии
Комментарии
   Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Благодарности
Форум
Хронология
 
  В этом разделе:
Простой, но полезный аллокатор памяти (18.02.03)
   Эта заметка --- продолжение "Postfix изнутри" в том смысле, что в качестве примера опять берется postfix. Но если в прошлый раз...   >>>>
C или C++? (09.07.01)
   Существуют два диаметрально противоположенных, но одинаково распространенных мнения, которые можно выразить как "C++ это C с классами" и "C++ и C...   >>>>
Религия и goto (14.04.01)
   Начнем несколько издалека. В программировании существует тенденция к алгоритмизации самого процесса программирования. То есть, выведение некоторых универсальных правил, использование которых в...   >>>>
ploticus (16.10.00)
   Есть такая программа, предназначенная для создания графиков различных видов из командной строки, называется ploticus. Программа сама по себе достаточно удобная ---...   >>>>
Шаманство, или ошибки работы с памятью (25.09.00)
   Когда программа становится внушительной по своему содержанию (то есть, не по количеству строчек, а по непонятности внутренних связей), то ее поведение...   >>>>
Библиотека консорциума W3, libwww (20.09.00)
   Популярный нынче термин "веб-программирование" обычно подразумевает под собой программирование, в лучшем случае, на perl, в худшем --- на PHP, в совсем...   >>>>
Инварианты внутри программы (18.09.00)
   Вы когда-нибудь задумывались, над тем, как вы пишите программы? Если нет, то, я думаю, сегодняшняя заметка будет вам полезна. Итак, как...   >>>>
Содержание раздела полностью...
   Примерно в тоже время
Преобразование XML при помощи XSL (11.08.00)
   Одна из самых частых операций, которую требуется произвести с XML-документом, это показать его. Хочется предупредить сразу, что на этот раз я...   >>>>
Первый месяц (09.08.00)
   Месяц тому назад я нарисовал себе новую титульную страницу для размещения в интернете. В принципе, уже давно назревала мысль о том,...   >>>>
Хронология полностью...
   Содержание
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
  C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Туризм
  Байки
Фотографии
Комментарии
  Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Студенческое
Просто так
Благодарности
Форум
Хронология
© 2000-2008, Andrey L. Kalinin
mailto:andrey@kalinin.ru
Rambler's Top100