GOTO в прикладном программировании


    Картинка из книги Thinking Forth

    По мотивам «обобщенной» статьи про GOTO и статьи про GOTO в системном программировании:

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

    Основной тезис: в прикладном программировании GOTO однозначно лучше обходить.

    Как докажем:
    1. В прикладном программировании критически важен один параметр кода — сопровождаемость.
    2. Goto не ухудшает однозначно сопровождаемость только в небольшом проценте случаев, и даже в этих случаях принципиально от альтернатив не отличается
    3. Ради небольшого процента случаев его использовать вредно:
      1) очень низкоуровневое, поэтому сильно развращает программиста (возникает соблазн использовать и в других местах) — большой вред из-за небольшого процента случаев, когда GOTO можно применить;
      2) даже в таких случаях есть более красивые альтернативы.



    GOTO — свойства и влияние на качество кода


    Параметры качества кода
    (считаем, что код написан правильно, все нештатные ситуации учтены):
    1. Потребление ресурсов (памяти, процессорных тактов) — приоритет «от машины»
    2. Сопровождаемость (легкость сопровождения) кода — приоритет «от человека»:
      1) читаемость кода — насколько легко понять написанный код (соответственно, насколько легко и написать, и отладить),
      2) ошибкоустойчивость при изменениях — насколько сложно внести ошибку/насколько легко ее заметить при изменении кода,
      3) легкость поддержки общего стандарта — насколько написание кода приучает программиста к отклонениям от неких общих стандартов
    Общие свойства GOTO:
    1. неструктурировано: можно вставить в почти произвольное место, сложно понять, как мы туда попали, в отличие от остальных конструкций ветвления;
    2. закрепощает исходный код: если структурированные блоки можно менять по-разному, перестраивать их порядок, как в конструкторе, то GOTO — это гвоздь, соединяющий какие-то блоки конструктора — после его внедрения код изменить уже очень непросто.

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

    Что не является GOTO:
    1. другие конcтрукции управления потоком выполнения — if,switch,while и т.п.: в них всех ветвления потока жестко заданы синтаксисом, находятся на границе того же блока — верхней или нижней (для return — граница функции), а GOTO можно размещать произвольно
    2. автоматически сгенерированный код — как и в сгенерированном ассемблере, в нем копаться и его поддерживать не приходится.

    Особенности GOTO в прикладном программировании


    Прикладное программирование здесь — программирование на языках высокого уровня, поддерживающих структурирование кода, в том числе структурный подход к обработке исключений: Java, C#, C++, интерпретируемые языки и т.п. — в общем, стандартный прикладной мэйнстрим. C не рассматриваю как низкоуровневый язык, используемый сейчас в основном для системного программирования.

    Особенности прикладного программирования:
    1. нет необходимости в точечных оптимизациях — отдельных тактов или ячеек памяти, поэтому экономию ресурсов можно отбросить из рассмотрения GOTO — остается только сопровождаемость
    2. есть возможность как угодно структурировать логику — как угодно объединять в функции/методы, заводить сколько угодно переменных, классов и т.п. с любыми названиями, возможность бросать исключения
    GOTO только ухудшает сопровождаемость кода

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


    GOTO — проблемы и варианты исправлений


    Рассмотрим применение GOTO в различных вариантах перемещения по структуре кода и альтернативы ему:

    1. Вход в блок извне:

    1.1 Вход в «не цикл»:
    легко и очевидно переписывается без GOTO:

    		if(a)
    			GOTO InsideIf;
    
    		if(b) {
    			foo();
    	InsideIf:
    			bar();
    		}

    =>

    		if(b) {
    			foo();
    		}
    		
    		if(a || b) {
    			bar();
    		}

    1.2 Вход в цикл:
    нельзя: вообще непонятен поток выполнения:

    		if(goIntoCycle)
    			GOTO MiddleOfCycle;
    			
    		for(...) {
    			foo();
    			
    	MiddleOfCycle:
    			bar();
    		}


    2. Переход внутри одного блока:
    нет необходимости, легко переписывается, обычно на if/else:

    		if(bNotNeeded)
    			GOTO startFromC:
    		
    		B();
    		
    	startFromC:
    		C();

    =>

    		if(bNeeded) {
    			B();
    		}
    		
    		C();


    3. Выход из блока наружу

    Это основной случай возможного применения GOTO. Разобьем его на еще более мелкие и рассмотрим подробно на примерах.
    Общий подход — максимально декомпозируем: разбиваем на методы по смыслу, логику фиксируем в флагах с говорящими названиями — получаем читабельный и самодокументированный код.

    Важные правила:
    1. НЕ ИСПОЛЬЗУЕМ ИСКЛЮЧЕНИЯ:

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

      2) можем случайно «проглотить» исключение с внутреннего уровня вложенности;

      3) это дорогое удовольствие — в прикладном программировании крохоборствовать негоже, но и так разбрасываться ресурсами при наличии простых альтернатив нет смысла;
    2. Избегаем множественных return, особенно с разных уровней вложенности: иначе мало отличий от GOTO — читабельность кода так же затруднена в большинстве случаев. (Возможно, это требует отдельного обоснования — тоже тот еще холивар).

    3.1. Единственный выход из одного уровня вложенности:
    тривиально заменяется if/break и т.п.

    3.2. Несколько выходов из одного уровня вложенности:

    3.2.1 Обработка ошибок — только через исключения
    (надеюсь, это очевидно; если нет — могу объяснить в отдельной статье)

    3.2.2 Перебор вариантов — на примере if:

    	class Tamagochi {
    
    		function recreate() {
    			if(wannaEat) {
    				eat();
    				GOTO Cleanup;
    			}		
    			
    			if (wannaDrink) {
    				drink();
    				GOTO Cleanup;
    			}
    			
    			if(wannaDance) {
    				Dance();
    			}		
    			
    			return HAPPY; //true
    			
    	Cleanup:
    			return washHands() && cleanKitchen();
    		}
    	}

    Проблемы (кроме всегда присущей GOTO неочевидности потока выполнения):
    захотели добавить поведение sleep в случаях wannaEat и wannaDance — все, обобшение для wannaEat и wannaDrink разрушено.

    Как сделать красиво (сразу расширенный вариант):

    		function recreate() {
    			if(wannaEat) {
    				eat();
    				needCleanup = true;
    				needSleep = true;
    			}		
    			
    			if (wannaDrink) {
    				drink();
    				needCleanup = true;
    			}
    			
    			if(wannaDance) {
    				Dance();
    				needSleep = true;
    			}		
    			
    			result = HAPPY; //true
    			
    			if(needCleanup)
    				result = result && washHands() && cleanKitchen();
    			
    			if(needSleep)
    				result = result && sleep();
    			return result;
    		}


    3.3. Выход из нескольких уровней вложенности.

    3.3.1 Если легко выделить разную логику (разные ответственности):

    	class BatchInputProcessor {
    
    		function processInput(inputRecords) {
    		
    			for (inputRecord in inputRecords) {
    				for (validator in validators) {
    					if (!validator.check(inputRecord)
    						GOTO ValidationFailed;
    				}
    				
    				item = new Item();
    				for (fieldValue in inputRecord.fieldValues)	{
    					setFieldValue(fieldValue.field, fieldValue.value);
    				}
    				
    				itemsToStore.add(item);
    			}
    
    			return store(itemsToStore);
    			
    	ValidationFailed:
    			log(failedValidation);
    			
    			tryToCorrect(inputRecords);
    			...
    		}
    	}

    =>

    		function processInput(inputRecords) {
    			allRecordsAreValid = true;
    			for(inputRecord in inputRecords) {
    				recordIsValid = validateRecord(inputRecord, validators);
    				
    				if(!recordIsValid) {
    					allRecordsAreValid = false;
    					break;
    				}
    				
    				else {
    					itemToStore = createItemFromRecord(inputRecord);
    					itemsToStore.add(item);
    				}
    				
    			}
    			
    			if(allRecordsAreValid) {
    				result = store(itemsToStore);
    			}
    			
    			else {
    				log(failedValidation);			
    				tryToCorrect(inputRecords);
    				...
    			}
    		}


    3.3.2 Сложнее выделить разную логику или при этом усложняется код.

    Как правило, это может быть в случае однотипных вложенных циклов:

    	function findOrCreateTriadaOfEqual(firstArray, secondArray, thirdArray) {
    		result = null;
    		for(first in firstArray) {
    			for(second in secondArray) {
    				for(third in thirdArray) {
    					if(first == second && second == third) {
    						result = array(first, second, third);
    						GOTO Found:
    					}
    				}
    			}
    		}
    		
    		equal = new Item();
    		result = array(equal, equal, equal);
    		
    	Found:
    		log(result);
    		return result;
    	}


    Вариантов все равно много:
    1. вынести в отдельную подфункцию с return из внутреннего цикла — самый простой
    2. обобщить — сделать рекурсивную функцию вида findEqualInArrays(arrayOfArrays, currentArrayIndex, currentFoundItemsArray);
    3. «if(result) break» — самый топорный:
    		result = null;
    		for(first in firstArray) {
    			for(second in secondArray) {
    				for(third in thirdArray) {
    					if(first == second && second == third) {
    						result = array(first, second, third);
    						break;
    					}
    				}
    			
    				if(result)
    					break;
    			}
    			
    			if(result)
    				break;
    		}


    Это — единственный вариант, который смотрится хуже, чем GOTO, и GOTO даже понятнее. Но практически всегда есть и другие варианты.

    Для оставшегося исчезающе малого процента случаев, когда других вариантов нет, нужно просто решить, что все равно можно сделать хотя бы флагами, зато гайдлайны будут проще — «Без GOTO!».

    Резюме:


    Важнее всего — сопровождаемость.

    GOTO всегда ухудшает сопровождаемость, поэтому

    нужно обходиться без GOTO — достаточно стандартных средств:
    1. обработка ошибок через исключения;
    2. декомпозиция — большой метод, решающий много задач, разбивается на маленькие, решающщие отдельные задачи;
    3. фиксация логики (вычисленных условий и выражений) в переменных с говорящими названиями.

    Средняя зарплата в IT

    111 111 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 6 788 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 46

      +6
      Ждём статью «goto в спортивном программировании» :)
        +1
        спортивное как раз хорошо попадает под прикладное:

        там важна общая реализация алгоритма,

        а ограничения по ресурсам ставятся такие, что их выполнение/невыполнение зависит только от алгоритма, а не конкретных инструкций в его реализации.
          +2
          Не скажите — иногда один и тот же код с vector'ами не проходит, а с массивами получает AC.
          Хотя goto, разумеется, таких «оптимизаций» не даст.
            +1
            да, STL — такой STL :)

            не учел, так как когда занимался олимпиадным программированием, задачи решал на Паскале, хотя как основной язык изучал С++ — для меня тогда было критично не наступать на грабли C/C++ — кстати, мои более продвинутые товарищи так же делали :)
          +1
          Чаще всего просто не нужно (зато break и continue юзается десятками раз в каждом исходнике).
          Единственный пресловутый случай, когда мне goto на олимпиаде помогало — это тот самый выход из нескольких циклов.
          +4
          Задачка:
          Есть страшный расчет, который надо проводить тысячи раз. В каждом есть10 подзадач, каждая занимает по пол часа. (Реально есть)
          В качестве вычислителя есть только ноутбук 2 ядерных по 1.6ггц. Ни каких других вычислительных мощьностей ближайшие пару месяцев не предвидится, а посчитать надо.
          Держать ноутбук не выключая более 2 часов не возможно.

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

          Скажите лучшее решение, чем без goto, тк мне самому оно не нравится ибо велосипед.
          Я серьезно!

          Мое решение, но пока оно только сделано не используется.

          Я реализовал паттерн- моменто(я бы назвал его скорее дампом).
          При поступлении события, все память складывается в xml, вместе со всеми значениями счетчиков циклов и временными переменными. Затем, чтобы возобновить процесс вызывается точная копия метода, где происходил процесс с несколькими отличиями, во первых он получает в качестве параметра номер метки goto на которую надо перейти, чтобы начать процесс с той же точки, что и он был прерван. Далее идет инициализация всех переменных из дампа, и через маленький switch по тому номеру метки идем в нужное место циклов. В итоге расчет начинается с той же точки на которой он был прерван.

          Чем плохо- есть полный дубляж счетного метода, есть goto.
          Какое лучшее решении можно сделать? Я на полном серьезе спрашиваю, если кто предложит ответ- буду рад как кот сметане.

          Язык C#(переписывать на С++ не предлагать, это на долго и в отладке я погрязну еще на долго.)

            +1
            Действительно страшно. На какой глубине находится самая глубокая точка сохранения?
            Можно предложить:
            — реализовать методы, которые будут выполнять завершение внутренних циклов, возвращаться и передавать управление методам для более внешних циклов (плохо, потому что код умножается во много раз)
            — пропускать уже вычисленные части циклов с помощью if() (которые будут срабатывать только при первом входе)
            — описать состояние процесса в самой глубокой точке сохранения и реализовать всю программу как один цикл, реализующий переход от такого состояния к следующему.

            Я бы, наверное, стал делать так. Тело каждого цикла выделил бы в функцию, которая что-то бы делала, а дойдя до внутреннего цикла, вызывала бы в нем функцию следующего уровня. У функции — параметры, позволяющие описать досрочный выход и продолжение счета (блок с копией локальных переменных?). Но это тоже громоздко.
              +1
              Вот ваш вариант мне вполне себе нравится, но тогда придется либо кучу(порядка 15, не хочу вызовы превращать в winapi стиль) параметров передавать в функцию и все по ссылку ref, либо на плодить глобальных переменных.

              Реализовывать все как 1 цикл- я вас не понял.

              Вариант с if действительно выглядит элегантно относительно.
              Получится что есть 3 переменных булевских, которые говорят работал ли цикл в момент отключения или нет. И потом if(var1) то заходим в блок. далее if(var2) заходим, а в последнем if(var3) оказывается false, значит с этой точки и считаем. Соответственно при выходе из цикла переменную ему соответствующую ставим в false, что мы от туда вышли.

              Надо попробовать сделать.
                +1
                Думаю, что сохраненные переменные лучше организовать в классы, причем структура классов должна отражать структуру циклов: цикл на каком-то уровне получает инициализирующую переменную, если она null — значит это штатный вход, а если нет — сохраненный. Извлекает из нее объект для цикла следующего уровня. Тот тоже смотрит: если это не null — значит, это его сохраненные данные, а если null — сохраненных данных не было, вход штатный. По-моему, должно сложиться. Плохо, если точки входа образуют не линейный список, а дерево. Но и тогда можно разобраться.
                  0
                  Т.е. предлагаете наплодить 3 класса, переписать алгоритм(120), провести заново тестирование и верификацию, снизить скорость работы довольно сильно?

                  Пожалуй оно того не стоит.

                  Вариант с 3 if мне больше нравится и я его уже реализовал. Он работает. goto нет, и код практически не изменился
              +2
              Надо бы что-то типа этого: ru.wikipedia.org/wiki/Setcontext
              Но тут про C. Может аналог есть.
                +1
                Вариант хороший, но я не нашел ничего подобного для C#

                Как то у одного товарища моего знакомого было желание взять весь thread(а лучше объект обертка над ним из .net 4) остановить его и в файл сохранить… Чтобы потом восстановить.
                По моему у него это не удалось ибо это все таки не системное программирование и вроде на C# средств таких нет.
                  +1
                  Реализовать это можно, но понадобится довольно много муторной и нетривиальной работы с определением точек безопасной остановки и разработке механизма сериализации и десериализации данных. Сложность зависит от сложности применяемого алгоритма.
                +4
                в аналогичных случаях, правда, гораздо более примитивных, поступал так:

                Поток обработки UI при нажатии кнопки «пауза» просто поднимал флаг «пауза». Рабочий поток проверял этот флаг в фиксированном месте, в котором легко и удобно можно было все задампить, и так же легко можно было позднее восстановить состояние и продолжить работу — это было в конце оборота глобального цикла, когда стек минимален, все промежуточные расчеты получены.

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

                  Но вот потеря второго цикла- это уже пол часа- а это плохо. Можно избавиться от 1 цикла и 1 goto соответственно, но еще 1 получится останется.

                  Полумера выходит. Избавился от 2 строк, но добавлю проверку на флаг+ ожидание выхода из нижнего цикла.

                  Если бы понять как во второй цикл войти и выйти без goto, то я бы так как вы сказали и сделал
                    +1
                    А если попробовать «развернуть» 3 цикла в один:

                    int L1 = 0;
                    int L2 = 0;
                    int L3 = 0;
                    while (L3 < 2){
                    Console.WriteLine(«L1:{0} L2:{1} L3:{2}», L1, L2, L3);
                    L1++;
                    if (L1 == 2){
                    L1 = 0;
                    L2++;
                    }
                    if (L2 == 2){
                    L2 = 0;
                    L3++;
                    }
                    }

                    Оформляем этот кусок кода в виде функции, которой в параметрах передаются L1, L2, L3 и вход в нужное состояние осуществляется без GOTO.
                      0
                      Если бы так цикл просто раскрутился, цены бы не было вашему совету.
                      К сожалению алгоритм переписывать в 120 строк с динамическими определяемыми границами не очень хочется.

                      Я не очень представля как алгоритм с не статически задаными границами можно раскручивать стандартно.

                +2
                «1. нет необходимости в точечных оптимизациях — отдельных тактов или ячеек памяти, поэтому экономию ресурсов можно отбросить из рассмотрения GOTO — остается только сопровождаемость»

                Не совсем понятно, почему это так. Во внутреннем цикле обработки большого объема данных, который выполняется 10^9 и более раз, каждые несколько тактов — это лишняя секунда времени работы. И заставлять процессор работать с флагами там, где достаточно обойтись явно прописанным переходом, может оказаться не очень разумно. Лишняя переменная — это не просто ячейка памяти, это еще и регистр процессора. А их запас ограничен.
                  +3
                  Хорошее замечание.

                  В таком случае нужно:

                  1) определить, насколько высокоуровневый код используется в этом цикле: если код выполняется на заведомо высокоуровневом языке — C#, Java, C++ с использованием продвинутого ООП (наследование, шаблоны), интерпретируемом языке и т.п., — то goto Вас не спасет: уже есть много накладных расходов, на фоне которых goto теряется.

                  2) если все-таки хотите попробовать оптимизировать, плясать не от пристального взгляда на исходники, а от показаний профайлера — какие узкие точки показывает он (хотя, возможно, на таких масштабах оптимизации профайлер начинает искажать показания за счет изменения исходного кода)

                  3) если все-таки взялись оптимизировать, и при этом попадаете в тему этой статьи — занимаетесь прикладным программированием на высокоуровневом языке — нет смысла париться о таких правилах: это значит, вы уже отказались от всех высокоуровневых концепций, и спустились на уровень ассемблера/Си, чтобы быть в состоянии контролировать такую точность оптимизации.

                  В общем, максимальная оптимизация всегда усложняет код, нарушая кучу рекомендаций — никуда от этого не деться, но главное — 7 раз проверить, что это имеет смысл
                    0
                    Со всем согласен, кроме «продвинутого ООП (наследование, шаблоны)» — во-первых шаблоны вообще не очень-то ООП, а во-вторых они сами по себе не делают код медленнее, а зачастую используются как раз для достижения обратного эффекта. Кроме того, наследование тоже само по себе ничем не грозит, т.к. это просто расширение структуры данных (плюс мизерный оверхед от конструктора-деструктора которые можно и заинлайнить). В любом случае, нано-оптимизации о которых идёт речь в случае с goto могут иметь измеримый эффект только в очень нагруженном цикле, и то не факт, что он там будет. Во всех остальных случаях имхо нужно писать так, как этого ожидают другие люди которые будут потенциально работать с этим кодом.
                • НЛО прилетело и опубликовало эту надпись здесь
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      да, я неспроста описал свойство прикладного программирования «возможность обработки ошибок через исключения». наверное, недостаточно четко описал, поэтому подредактировал начало темы, добавив определение прикладного программирования в данном контексте. Язык C не входит в это определение — более низкоуровневый.
                        0
                        Да в C++ механизм обработки исключений тоже не идеален. Нет блока try/catch/finally — попробуй сделай нормально финализацию. А блок try/catch не перехватывает Win32-исключений (например, ошибка деления на ноль). В свою очерень, SEX-блок __try/__finally может не перехватывать исключения C++.

                          0
                          В си++ вместо этого используется RAII, и если посравнивать с граблями в том же Java которые возникают при использовании finally, то выходит что RAII лучше :)
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • НЛО прилетело и опубликовало эту надпись здесь
                        +6
                        На самом деле достаточно было написать
                        «В прикладном программировании критически важен один параметр кода — сопровождаемость.»

                        И заставить всех программистов в мире это выучить. А потом убедиться, что поняли.
                        Остальное — следствия.
                          0
                          Да, было бы достаточно :)

                          Только мы же люди аналитические — набиваем шишки, анализируем, выводим общие правила, потом дзен, а потом пытаемся заставить других этот дзен понять быстрее. Но заставление плохо проходит даже с теми, кто рядом. Хотя рычаги всегда есть — сводятся к «заставлению» поддерживать свой код.
                          0
                          Автор делает одно очень важное допущение — он считает что все используют C++.

                          В C есть конечно longjmp, но лишний раз его использовать тот ещё аттракцион.
                            0
                            Я явно написал, что это общее для всех высокоуровневых языков, поддерживающих goto — C#, Java, PHP,…
                              +1
                              MSDN: msdn.microsoft.com/ru-ru/library/13940fs2#Y240

                              Кратко: в C# применение оператора goto оправдано для:

                              1) Перехода между case-метками внутри switch-блока;
                              2) Для выхода из вложенных циклов.

                              p.s. Иначе такие примеры не приводились бы в официальных руководствах, причём без всякого рода предостережений, верно?
                                +1
                                Конечно, читал это во время написания (только английский вариант — этот русский перевод удивил своим «качеством»).

                                К сожалению, примеры насквозь кривые.

                                Конкретно по примерам:

                                1. switch — хоть и маленький, а уже непонятный пример (понять можно, но нужно включить голову и внимательность):

                                1) сначала хотел написать альтернативу — вешать флаг внутри switch, затем в соответствии с флагом выполнять. Но данный извращенный код вообще из пальца высосан, таких авторов надо увольнять, а код заменять на примитивный:

                                Hashtable pricesBySizes = new Hashtable();
                                pricesBySizes[smallSize] = smallSizePrice;
                                ...
                                
                                if(pricesBySizes.ContainsKey(size)
                                   //запишем цену
                                else
                                  //отругаем за неверный ввод
                                


                                2) за magic constants 25 и 50 уже можно руки оторвать любому, независимо от фирмы. А Вы говорите «MSDN», эх(((

                                2. вложенный цикл: 1) создает сразу 2 метки; 2) дублирование — несколько раз вызывается Console.Writeline — как раз потому, что вместо отслеживания результата поиска применили goto.
                                А так было бы:

                                result = findEqualNumber(array, myNumber);
                                resultString = "The number {0} " + (result == null ? "was not" : "is") + "found";
                                
                                //вывести результат 
                                
                                  +1
                                  Это автоматический перевод на русский. Примеры, как можно понять, демонстративные — демонстрируют, какой синтаксис нужно использовать для goto внутри того же switch — и я не вижу смысла где-то зараннее объявлять числа, чтобы они перестали быть волшебными, а пример от этого стал длиннее.

                                  Может, авторитетным для вам окажется мнение 9 специалистов (Симон Робинсон, Олли Корнес...), написавших «C# для профессионалов»: «Как правило, не рекомендуется использовать оператор goto. Вообще говоря, его применение противоречит объектно-ориентированному стилю программирования. Однако в одном случае этот оператор очень удобен — в случае переходов между метками в операторе switch, в частности потому, что оператор switch в C# не позволяет „проваливаться“ сквозь метки case.»

                                  И да, никого не надо увольнять.
                                    +1
                                    Тоже подозреваю, что автоматический перевод, но при этом прямо написано — «переведен вручную».

                                    Согласен с Вами — примеры демонстрируют именно синтаксис и семантику goto. Но никак не образец написания кода.

                                    Безотносительно отношения к авторитетам рассмотрим приведенное Вами высказывание:
                                    1. Не рекомендуется. 2. Противоречит ООП. 3. Удобен в switch. 4. в частности, чтобы проваливаться сквозь метки case:

                                    3. удобен — я это понимаю как «удобен в написании»: если имеем индульгенцию использовать его в switch, то уже не будем париться, думать, как сделать по-другому — возьмем и напишем;
                                    4. «проваливаться сквозь case»:
                                    1) заметьте, пример MSDN даже сюда не подходит: в нем прыгают наверх — возможно, разница тонкая, но есть;
                                    2) разве хорошо проваливаться сквозь метки только потому, что в С/С++ это было, и мы привыкли? Нет, это ошибкоопасно: сложнее заметить, где есть break, где нет — для этого и сделали обязательным break. Это, если хотите, синтаксическая соль, а мы хотим ее обойти.

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

                                      +1
                                      Да, и меня смущает всё время это «переведен вручную». Но зачастую там откровенные моменты есть, которые… ну никак не мог бы допустить человек с минимальным знанием английского.

                                      Едем дальше:

                                      1) «Не рекомендуется» — это да. Но обратите внимание, как осторожны здесь авторы. Они говорят «Как правило, не рекомендуется» (<=> «Обычно, не рекомендуется»). Это значит, что есть исключения, и именно в конкретных исключительных ситуациях (блок switch, выход из вложенных циклов) goto оправдан и является одним из верных решений.
                                      2) «Удобен в switch» — удобен и в написании (не нужно объявлять меток) и в использовании, но только в ситуациях, когда он там требуется.
                                      3) "… в частности, чтобы проваливаться сквозь метки case" — «разве хорошо проваливаться сквозь метки только потому, что в С/С++ это было, и мы привыкли?» — именно поэтому в C# теперь нельзя проваливаться сквозь case — разработчики языка пытались максимально уменьшить число ошибок, допускаемых программистом («после case cтавь break»), но при этом сохранить гибкость С++ («юзай лучше goto, когда надо повторно использовать код внутри switch и выделять для этого отдельную функцию малоприемлемо»).
                                      4) «Код должен быть скучной инструкцией, а не детективом» — если имеется ввиду, что goto «перебрасывает» через код, то вызов функции — это тоже «скачёк» к другому участку, в конце while, for тоже скачёк к началу цикла.

                                      … У меня вёл преподаватель, который категорически запрещал goto, break (кроме как в switch), continue и несколько return-ов. Просто сказал бы «переписывай», если бы я заюзал «сами знаете что». Попытавшись с ним поспорить однажды нарвался на некоторую грубость и нежелание слушать. Не то, чтобы «запретный плод сладок», но в процессе своего самообразования я понимал, что это скорее предвзятость и «старая школа», а новая учит использовать весь доступный в языке инструментарий (иначе зачем его туда ввели разработчики языка — думаю первые, кто знает, что делают), но с умом.
                                        +1
                                        упрощенная схема рассуждений такая:

                                        в большинстве случаев goto не нужен,

                                        остальные — спорные случаи: в них без goto сильно хуже не станет, дублирование кода всегда можно убрать

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

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

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

                                            И только постепенно, потратив кучу общего времени и нервов, понимаешь, что твое чутье — это чутье «заднего места», что нужно сначала сесть на кактус, чтобы потом его замечать заранее. Но всегда остается вопрос — где позволить ошибиться, а где постараться убедить сразу, так как ты за все отвечаешь, и как обойтись без авторитарности. Но потом и этому учишься )
                                              0
                                              (Опыт) ~ ((Количество кактусов, на которые сел) = (Количество граблей, на которые наступил)) (с)

                                              Меня переучит только необходимое количество кактусов.
                            +4
                            Неделя GOTO на хабре? :)
                              0
                              Дай бог не вторая революция GOTO в интернете…
                              –2
                              Жутчайшее форматирование кода.
                                0
                                Я бы сказал жутчайшее форматирование ТЕКСТА.

                                Основной посыл был понятен с самого начала, а потом еще 15 серых заголовков и 25 выделений болдом какбэ доказывают нам…

                                Одного — двух параграфов хватило бы, чесслово.
                                0
                                А ещё я всё ещё помню, что в Google C++ Coding Standards исключениями пользоваться не разрешают из-за того, что с самого начала писали без исключений, а теперь есть шанс нарваться на нетривиальные проблемы, когда код, не использующий исключения, начнёт вызывать код с исключениями. Вот так. Взяли и запретили ;-)
                                  0
                                  Отличная статья, спасибо.

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое