Я верю в программистское клише о том, что большинство плохих фич имеет причины для существования. Ненавидимый многими оператор goto позволяет быстро и удобно выбраться из глубоко вложенной структуры, если пользоваться им с умом. Определённая степень нестрогости типов позволяет им быть более изящными. Указатели памяти могут заставить вас возненавидеть свою жизнь, но они были критически важны в те годы, когда компьютерное «железо» было слабее современного умного термостата. Список можно продолжать.

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

1. On Error Resume Next (классический VB 6)


On Error Resume Next реализует подход, который в большинстве ситуаций возникновения ошибок хуже всего: «просто продолжай выполнение». При этом он делает его правилом по умолчанию для любой проблемы. Представьте, если бы мы реагировали так же на сбои в реальной жизни. Попал в аварию на машине? Просто продолжай ехать!

' Напишем этот код на случай, если не сможем
' подключиться к базе данных.
On Error Resume Next 
Dim TotalPayment As Decimal
Dim TotalHours As Decimal
Dim HourlyRate As Decimal
TotalPayment = 5000
' Ой, мы забыли задать TotalHours!
' Код не должен работать, но у нас есть иммунитет к ошибкам.
HourlyRate = TotalPayment / TotalHours 
' Всё вроде правильно. Давайте обновим базу данных!
Call UpdateDatabase(HourlyRate)

Но подождите, это ещё не всё. On Error Resume Next не имеет никаких границ. Он будет продолжать свой путь через длинные цепочки ошибок. Он даже не обязан находиться в начале модуля вашего кода (можно вставить его в любое место кода и переключиться на обычный режим при помощи On Error Resume 0).

On Error Resume Next гарантирует, что вы достигнете конца процедуры кода, и это соблазняет нас своей определённостью. Вам не нужно беспокоиться, что будет пропущена какая-то важная часть кода очистки. Но в пункт назначения вы доберётесь не совсем в том виде, в котором начинали путь. Это агрессивная стратегия «дойти до конца любой ценой», только «доходить до конца», если тебе не удалось выполнить обязательную задачу, обычно и не стоит.


Единственная возможная причина, по которой можно использовать On Error Resume Next — это полное отсутствие кода обработки ошибок, когда вам нужно пропустить все не особо важные проблемы, которые вы игнорируете. Например, вы можете осознанно создавать файл, который уже может существовать, настраивать принтер, который может не поддерживаться, или выполнять вычисление, которое может привести к делению на ноль. Объективно, в такой момент вы поступаете не очень хорошо, но мы вас прощаем. Однако если вы используете On Error Resume Next, то усугубляете своё невежество, беззаботно двигаясь в сторону ещё большей катастрофы.

Не буду врать. Я пользовался этой конструкцией (и она мне нравилась!) задолго до того, как начал учиться программированию по-настоящему. А теперь она практически мертва. VB.NET, текущая версия VB, которая влачит жалкое существование в мире .NET, заменила On Error Resume Next логичной структурированной обработкой ошибок. Хотя так ли это на самом деле? Оказывается, в современном .NET можно по-прежнему использовать обработку ошибок из созданного десятки лет назад классического VB, в том числе и On Error Resume Next. Это полное безумие, но, к счастью, кажется, сегодня этим никто не занимается.

Тем не менее, On Error Resume Next всё ещё существует в реальном коде. Возможно, вы обнаружите, что эта конструкция ломает макрос электронной таблицы Excel, которую использует ваш отдел продаж. Спасибо тебе за это, VBA.

2. DoEvents (Windows Forms)


Даже по расплывчатому имени встроенной функции DoEvents() уже можно понять, что стоит готовиться к чему-то уродливому. И метод Application.DoEvents() нас не разочаровал.

DoEvents() — это когда-то популярный, но очень опасный костыль для Windows-приложений, не желающих работать с многопоточностью. Например, классическим сценарием его использования является обновление UI в коротком цикле. Представим, что мы выполняем очень затратную по времени математическую операцию и помещаем значения в list box. Пользователь не увидит ни одно из этих чисел, пока вы не вернёте управление и Windows не сможет обновить окно. Обычно этого не случается, пока код не завершит работу, если вы не выполняете эту работу в фоне в отдельном потоке.

DoEvents() просит приложение на мгновение приостановиться, чтобы Windows могла сделать то, что ей нужно. Так что если вы вызовете DoEvents() в цикле, то, вероятно, дадите операционной системе возможность перерисовать окно:

for (int i = 0; i < 1000000; i++) 
{
    listBox.Items.Add(someCalculation(i));
    // Этот цикл блокирует UI. Мы можем отрефакторить код
    // или просто вставить этот маленький костыль...
    Application.DoEvents();
}

Но после этого ситуация быстро начинает развиваться по наклонной. Проблема в том, что ты точно не знаешь, что будет делать DoEvents(). Другие части приложения могут получать сообщения Windows (допустим, если пользователь куда-то нажал), и могут начать выполнять свой код в месте, которое ты создал при вызове DoEvents(). Звучит печально, но на самом деле это очень весело (если вам нравится отладка по ночам), потому что результат на каждом компьютере будет чуть-чуть различаться!

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

Разумеется, работать с потоками сложно. Но в .NET уже есть простые в использовании инструменты, например, компонент BackgroundWorker, позволяющие выполнять всю нужную работу в другом потоке при помощи простой модели на основе событий. Нет необходимости придумывать какие-то костыли с помощью DoEvents(), и их создают очень немногие. Тем не менее, этот метод по-прежнему таится в библиотеке классов, импортированный из олдскульного VB и доступный для каждого языка .NET, в том числе и для современного C#.

Отвратительно.

3. Динамический Goto (COBOL)


В отличие от предыдущих проблем, этот кошмар программирования лично меня никогда не касался. (Может, я и стар, но я не стар как COBOL.) Однако эта фича настолько безумна, что я не могу её не упомянуть.

Представьте, что вы программируете на древнем языке с загадочными операторами управления потоком. А потом вы обнаруживаете, что этот язык способен изменять свой собственный код в процессе исполнения, меняя пути исполнения, по которым он движется через различные синтаксические структуры. Замысловато! Если всё работает, то программа сохраняет своё тонкое равновесие. Если она не работает, то вы получаете нечто вроде уробороса, поедающего свой собственный хвост.

Фича, которую я называю динамическим goto, имеет в языке COBOL имя оператора ALTER. Он позволяет изменять существующую команду GO TO так, чтобы она передавала контроль не той части программы, которая прописана в коде. Оператор GO TO даже можно изменять многократно, перемещать в разные части кода, как по лабиринту. Проще говоря, ALTER берёт всё то, что вы ненавидите в GO TO — запутывающий поток выполнения, спагетти-код — и значительно это усиливает.

Вот о какой структуре я говорю

IF WS-X = 2 THEN         
     ALTER SELECT-PATH TO PROCEED TO PATH-2.
ELSE IF WS-X = 3 THEN
     ALTER SELECT-PATH TO PROCEED TO PATH-3.
END-IF.
GO TO SELECT-PATH.
SELECT-PATH.
     GO TO PATH-1.

В этом примере команда GO TO SELECT-PATH может перенести нас к метке SELECT-PATH (которая затем направляет нас к PATH-1). Но в зависимости от предыдущей проверки переменной её можно изменить так, чтобы она перенаправляла нас к PATH-2 или к PATH-3. И кто знает, что могло бы произойти в других частях кода. Другими словами, мы не можем точно сказать, куда приведёт нас GO TO, если только не знаем полную историю выполнения приложения. Но не волнуйтесь, наверняка где-то есть лог, который можно изучить.

Можно назвать это метапрограммированием, но большинство из нас просто скажет, что это Очень Плохая Идея.


Да, на маломощных компьютерах того времени с небольшим размером стека подобная чёрная магия имела смысл (возможно?). И может быть, были времена, когда On Error Resume Next и DoEvents() были наилучшими компромиссами в случае сложных проблем. Сегодня, спустя шестьдесят с лишним лет после создания языка, оператор ALTER считается устаревшим, и запланировал к удалению. Что ж, по крайней мере, они решили исправить ошибку.

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

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

Комментарии (317)


  1. maedv
    05.11.2021 19:34
    -15

    Я не программист, я только учусь :) Но скажу в защиту On Error Resume Next (классический VB 6). А как быть, если исходные данные кривые, чреваты неожиданными ошибками? А с этим оператором хоть какая-то часть кода отработает.


    1. addewyd
      05.11.2021 19:49
      +57

      А смысл? На входе мусор, на выходе мусор.


      1. AntonioXXX
        06.11.2021 13:01

        А если вы обрабатываете 1 млн человек, они ждут результат, и их данные независимы, то почему бы не пропустить десяток ошибочных, записать в лог и разбираться с ними потом?


        1. navferty
          06.11.2021 14:00
          +10

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


        1. mayorovp
          06.11.2021 15:08
          +16

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


          А техники подобные On Error Resume Next запросто могут привести к тому, что вы обработали 1 миллион человек, и даже не знаете какие из них были обработаны успешно, а какие нет. Успехов вам в ручном поиске ошибок в миллионе записей...


          1. paluke
            07.11.2021 14:51
            -2

            Там не было исключений. Совсем. Было всего два варианта — либо программа сразу завершается с ошибкой, либо продолжаем выполнение. И да, если мы продолжаем выполнение, то была специальная функция, возвращающая код ошибки.


            1. mayorovp
              07.11.2021 14:59
              +3

              Там не было. Но сейчас-то есть!


            1. vrnvorona
              08.11.2021 13:16
              +1

              Так не бывает

              Если есть ошибка, есть исключение и его можно обработать явно. В программировании всегда явно > неявно.


    1. Xeldos
      05.11.2021 20:50
      +23

      Поймать ошибку и попытаться выдать какое-то осмысленное сообщение о её происхождении.


    1. arthin
      05.11.2021 20:55
      +8

      На самом деле очень полезная (в эмбеде, например, в некоторых случаях просто необходимая) вещь - иметь возможность добавлять обработку ошибок так, чтобы после обработки выполнение продолжилось с восстановлением состояния на момент ошибки. И вот это хорошо реализуется на чистом C, а в более абстрактных языках очень плохо. Но это имеет мало отношения к обсуждаемому.


      1. Rigidus
        06.11.2021 07:04
        +10

        Это есть как фича языка в Common Lisp и называется протоколом condition/restarts. Более того, вы можете предоставить несколько возможных "перезапусков", которые могут быть "применены" к возникшей ошибке пользователем или вызывающим кодом. Одним из этих "перезапусков" может быть "игнорировать" или "изменить на правильное значение и перезапустить сломавшуюся операцию". Работает лучше чем глобальный и опасный On Error Resume Next


        1. arthin
          06.11.2021 11:30
          +6

          Лисп - это специальный такой язык. Чтобы прерываться и вздыхать: эх, а вот эта конструкция на лиспе бы хорошо смотрелась.
          Я его уже и забыл почти, а все равно.


          1. mikhanoid
            07.11.2021 13:25
            +1

            Почему специальный?


            1. arthin
              09.11.2021 00:18
              +1

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


      1. netch80
        06.11.2021 10:03
        +2

        > чтобы после обработки выполнение продолжилось с восстановлением состояния на момент ошибки.

        Windows: Structured Exception Handling. Очень вкусно именно в этом плане, но
        1) Дороговато (каждая функция, которая делает опасные операции или аллоцирует ресурсы, требующие освобождения по выходу, должна ставить и снимать свой обработчик).
        2) Ошибки типа деления на 0 или доступа по запрещённому адресу — восстановление после них требует анализа источника вплоть до конкретной машинной команды. Обычно слишком сложно; проще сделать безусловный выход из опасного участка, а ловить проблему уже вверху (типовые исключения C++/Java/Python/etc.)

        > Но это имеет мало отношения к обсуждаемому.

        Думаю, вполне имеет :) такое обсуждение безусловно должно прийти к разным стилям обработки ошибок.
        В том же BASIC, о котором говорили, можно делать так
        1) Ставится on error goto <строка>;
        2) При переходе запоминается исходная строка в переменную ERL;
        3) Обработчик ошибки анализирует ситуацию и может вызвать resume next для обхода и resume <строка> для перехода на любую заданную строку (и, естественно, resume erl для повторной попытки).

        Да, это не структурно (поэтому в таком виде не перенесено), но в тех условиях вполне работало.


        1. tyomitch
          06.11.2021 12:08

          Как следствие из неструктурности — любая ошибка внутри обработчика ошибок была фатальной.

          On Error Resume Next — первый шажок в сторону структурности, позволявший (хоть и не требовавший) обрабатывать ошибки внутри обработчика ошибок.


          1. netch80
            06.11.2021 15:47

            > On Error Resume Next — первый шажок в сторону структурности, позволявший (хоть и не требовавший) обрабатывать ошибки внутри обработчика ошибок.

            Можно пример? А то совершенно непонятно, как оно могло бы работать.

            > Как следствие из неструктурности — любая ошибка внутри обработчика ошибок была фатальной.

            Это бы спокойно решилось явным push/pop контекста (можно через явную переменную).


            1. tyomitch
              06.11.2021 16:03
              +3

              Схематично:

              On Error Resume Next
              DoSomethingImportant
              If Err Then
                  SendEmail "admin@example.org", "Something important failed!"
                  If Err Then
                      WriteToLog "c:\something.log", "Something important failed, and email could not be sent"
                      If Err Then
                          MsgBox "Something important failed, email could not be sent, and log file could not be written"
                      End If
                  End If
              End If
              


              1. netch80
                06.11.2021 16:37

                Ага, понятно. По факту хак, но описанную цель более-менее выполняет…


        1. firehacker
          06.11.2021 15:36
          +2

          Windows: Structured Exception Handling. Очень вкусно именно в этом плане, но
          1) Дороговато

          SEH-фрейм занимает 8 байт 2 sizeof(void) на стеке — разве это дорого? В обходе односвязного списка и вызове обработчика по указанному адресу тоже вряд ли что-то дорогое можно узреть. Скорее всего по сравнению с тем, что обработчики будут делать, overhead от обхода цепочки вызовов будет незначительным.


          SEH был дорогим по совсем другой причине. Вызов RaiseException приводит к переходу в режим ядра, а это довольно медлительная вещь. В этом смысле SEH-исключения не могли стать альтернативой для кодов возрата статуса и должны были использоваться только для реально исключительны ситуаций. Попытка переделать цикл, проходящийся по миллиону элементов и вызывающий для каждого элемента какую-нибудь функцию, которая с вероятностью 30...50% может сбойнуть на выкидывания исключений очень значительно замедлила бы работу такого цикла, потому что 500 000 раз выкидывать исключение — это 500 переходов в режим ядра и обратно, что на порядок или несколько порядков медленнее, чем если бы вызываемая функция просто возвращала код ошибки, при условии что в в вызываемой функции только какая-нибудь арифметика и манипуляция данными и нет системных вызовов.


          1. netch80
            06.11.2021 15:50

            > SEH-фрейм занимает 8 байт 2 sizeof(void) на стеке — разве это дорого?

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

            В Unix мире тоже вначале использовали sjlj-exceptions — логически очень близко — но быстро перешли на табличные (exception handler лезет в спец. рантайм данные и ищет, куда передавать управление за catch и/или finally).
            (Кстати, судя по MSDN, x86-64 тоже использует табличный метод? я не сравнивал детали реализации)

            > SEH был дорогим по совсем другой причине. Вызов RaiseException приводит к переходу в режим ядра, а это довольно медлительная вещь.

            Да, это ещё дороже.


            1. firehacker
              06.11.2021 16:12

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

              Да что же в этом дорогого? Это не было дорогим удовольствием 25 лет назад, а уж тем более сейчас.


              Это не дороже, чем две лишних локальных переменных.


    1. cornknight
      05.11.2021 20:58
      +17

      Допустим, вы решили испечь сладкий пирожок, но вместо сахара всыпали в тесто соду. Переделаете, или доведёте выпечку до конца любой ценой?

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

      Проверять данные нужно не всегда. Код для проверки требует времени для написания и должен быть протестирован. Это накладные расходы.

      Но если из-за кривых данных программа может записать ошибочное значение в базу, или передать его клиенту или втихую прервать выполнение функции - вы не можете просто закрыть глаза, втопить педаль в пол и пусть оно как-нибудь доедет!

      Для бизнеса это финансовые и репутационные потери, в науке - мёртвые марсианские зонды, а в медицине могут и люди пострадать. Да и вам будет просто стыдно.


      1. maedv
        05.11.2021 21:04
        -7

        Да и вам будет просто стыдно.

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


        1. cornknight
          05.11.2021 21:35
          +14

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


        1. mayorovp
          06.11.2021 11:57
          +3

          А потом однажды у вас генерируется абсолютно пустая книга Excel, и вас ждут часы "счастливой" отладки в поисках того что именно пошло не так. В то время как те же структурные исключения вам бы сами подсказали что именно не так пошло.


    1. SadOcean
      05.11.2021 22:02
      +6

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

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


    1. netch80
      05.11.2021 22:46
      +3

      > А как быть, если исходные данные кривые, чреваты неожиданными ошибками? А с этим оператором хоть какая-то часть кода отработает.

      Использовать вместо него on error goto <номер строки> и обрабатывать по сути.


      1. firehacker
        06.11.2021 02:54

        Использовать вместо него on error goto <номер строки> и обрабатывать по сути.

        Не «номер строки», а метка (label). Просто номер строки является частным случаем метки, оставленным из соображений совместимости.


        И, что характерно, обработчик ошибки, к которому перейдёт выполнение, имеет возможно сделать [Resume], [Resume Next] или ничего из этого.


        Т.е. попросить «давай-ка попробуем сделать то же самое, но ещё раз — я вроде бы исправил первопричину неудачи», попросить «ладно, фиг с ним, что там у нас дальше», или же вообще перейти к совершенно иному плану действий.


        Диалоге в стиле «Не удалось выполнить <....> Повторить / Пропустить / Отмена» реализуются в буквальном смысле в виде пары строк.


        Предлагаю показать реализацию выполнения какой-то потенциально сбойной операции с показом диалога Повторить/Пропустить/Отмена при использовании try...catch-подхода. Как минимум, у вас будет ещё и цикл.


        1. netch80
          06.11.2021 10:16

          > Не «номер строки», а метка (label). Просто номер строки является частным случаем метки, оставленным из соображений совместимости.

          Ну, я смотрел по ресурсам, максимально близким к BASIC эпохи до того, как MS из него сделала VB/VBA/VB.NET. Изначально в языке все строки были нумерованы. Сейчас, да, надо думать о чём-то вроде меток.

          > Предлагаю показать реализацию выполнения какой-то потенциально сбойной операции с показом диалога Повторить/Пропустить/Отмена при использовании try...catch-подхода. Как минимум, у вас будет ещё и цикл.

          Да, будет цикл. Но я не думаю, что это так уж и плохо.
          Тут, должен сказать, надо учитывать один момент. Помните «структурное программирование», «GOTO considered harmful» и всё такое? Оно ведь взялось не из того, что Дейкстре надоело смотреть на плетёнки из перекрывающихся циклов с GOTO; к тому времени уже давно существовали правила, как писать понятно. Оно взялось из того, что
          1) Анализировать машинно программу (процедуру, функцию...) легче, когда она представлена в виде — один вход, один выход, всё состоит из последовательностей, развилок и циклов; технологии такого анализа как раз развивались в 60-х.
          2) Технологии свёртки плетёнки из GOTO в структурный вид без лишних действий более-менее нормально развились уже в 90-е, а на уровне пригодности для всех — с 2000-х (смотрим на LLVM).

          Поэтому, если вы видите что-то типа

          2500 on error goto 2900
          2510 k=0
          2520 gosub use_fast_algo(x, y, k)
          2530 goto 3000
          2600 gosub use_slow_algo(x, y)
          2610 goto 3000
          ...
          2900 print "Fast path with k=", k, "failed kurwa"
          2910 k=k+1
          2920 if k<10 resume 2520
          2930 print "All fast path attempts failed"
          2940 resume 2600


          то анализатор всё равно это перестроит (если он вообще такое умеет) в цикл вокруг строки 2520 и с вылетом в чём-то вроде try-catch. Ну и почему бы его не написать сразу? Человеку понятнее, компу тоже (не сделает тупой ляп).


          1. firehacker
            06.11.2021 13:56
            +2

            Разумный подход, как я считаю, состоит в том, чтобы не использовать GoTo, чтобы на его базе делать циклы. Не надо использовать GoTo вместо For, вместо While, вместо Do. Не надо использовать GoTo для перепрыгивания блока кода вместо использования блочного If или If Else.


            Но вообще объявлять GoTo абсолютным злом и запрещать его использовать абсолютно всегда — это глупость и максимализм.


            Тогда нужно запретить и break/continue в Си, ведь это тот же goto.


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


            1. netch80
              06.11.2021 15:25

              > Но вообще объявлять GoTo абсолютным злом и запрещать его использовать абсолютно всегда — это глупость и максимализм.

              Я такого и не объявлял и не запрещал :))

              Я объясняю, почему — и, главное, в чём — это могло быть разумной позицией по состоянию компьютерного дела в 1960-80-х. В определённых контекстах и ситуациях.

              > Тогда нужно запретить и break/continue в Си, ведь это тот же goto.

              Так и запрещали — как раз для возможности анализа потока исполнения.

              > Более того, Джоэль Спольски как-то высказал мысль, что обработка исключений — это тот же скрытый GoTo, только даже не в пределах одной процедуры, а через несколько процедур.

              Спольски, конечно, велик, но приписывать ему то, что было очевидно и говорилось ещё в 70-х, как-то странно.


        1. tyomitch
          06.11.2021 12:15

          И, что характерно, обработчик ошибки, к которому перейдёт выполнение, имеет возможно сделать [Resume], [Resume Next] или ничего из этого.

          Вариант Resume <label> тоже вполне оставался.
          Типичное применение — при ошибке внутри цикла пропустить остаток итерации, и перейти к следующей.


      1. JPEGEC
        07.11.2021 03:12

        Вспоминая босоногое детство и знакомство с Бэйсиком в упор не помню подобной конструкции.

        Когда оно появилось, и почему вообще упоминается если были времена когда подобного не было?


        1. netch80
          07.11.2021 10:22
          +1

          > Когда оно появилось, и почему вообще упоминается если были времена когда подобного не было?

          Проблема в том, что «Бейсик» это было нечто на десяток базовых команд и 100500 адаптаций под каждый компьютер — фрагментация платформы была такая, что сейчас тупо не с чем сравнить.
          Я вот, например, видел больше всего Бейсик на «Агат», который был перепилкой (без исходников, бинарными патчами) FP BASIC от Apple II. А рядом был (и один раз попался) INT BASIC (название говорящее — в нём не было плавучки), который можно было загонять в ПЗУ (и так делали как раз на Apple II; как по мне, это и сейчас удобно — места в флэше BIOS навалом!). И у Apple/Агат была куча своих специфических слов, там был onerr goto <номер> (именно onerr, не on error) и resume без параметра (по крайней мере Мымрин так писал, я уже не помню детали). А вот рядом присутствовал BASIC платформы MSX, которая в СССР попадала в виде компьютеров Yamaha (в основном в школы), и там было именно on error goto <номер>, и resume next, но не on error resume next. А вот последнее появилось где-то значительно позже (надо делать раскопки, но как бы не в доработках MS уже времён Windows?)

          Может, спросить на retrocomputing.SE? Там любят копать подобные детали. Мне лично не очень интересно, но вот есть упоминание, что on error gosub 65535 в Z-BASIC (это что за версия?) работало как on error resume next…


          1. tyomitch
            07.11.2021 15:21

            А вот последнее появилось где-то значительно позже (надо делать раскопки, но как бы не в доработках MS уже времён Windows?)

            В QuickBASIC


    1. diogen4212
      06.11.2021 10:59
      -1

      Попробую защитить точку зрения выше. Допустим, есть файл, где сперва идёт текстовая шапка, потом несколько числовых строк с данными, потом ещё текстовые данные (разделитель страниц вида "----", или новая шапка). Нам требуется вывести в Excel только числа, остальные данные просто не нужны. Повлиять на исходные данные мы не можем. Вопрос: зачем пользователю видеть уйму сообщений об ошибках, или зачем прерывать обработку на середине после первой текстовой строки? Лучше всего молча вывести все числовые данные, а ошибки просто проигнорировать, сделать это как «On Error Resume Next» или «try catch{}», или как-то иначе — не имеет значения. Понятно, что в идеальном мире там быть не должно, но бывает всякое.


      1. maedv
        06.11.2021 11:10
        +1

        Именно так! Благодарю за поддержку


      1. netch80
        06.11.2021 11:23
        +4

        > Лучше всего молча вывести все числовые данные, а ошибки просто проигнорировать

        Вы поставили общий on error resume next на весь вывод и продолжаете с этим, пока не закончите заполнять таблицу? Откуда у вас уверенность, что вы проигнорировали только ошибки форматирования в числовой вид, а не 100500 возможных других ошибок?

        Если вы игнорируете ошибку только в операции приведения в число — ok.

        > сделать это как «On Error Resume Next» или «try catch{}», или как-то иначе — не имеет значения

        Именно что имеет.
        1. Try-catch структурно удобнее для группировки обрабатываемых действий. Да, в ранних бейсиках этого не было, но сейчас мы пишем не на них.
        2. Catch позволяет ловить только конкретный тип ошибок, чтобы не гасить все, включая неожиданные тут.
        3. Вы явно пишете реакцию на ошибку, она видна (можно даже с пустым catch написать комментарий — уже неплохо).

        > Понятно, что в идеальном мире там быть не должно, но бывает всякое.

        Идеальности и не требовали вроде? А вот чтобы код был понятен и безопасен во всём предвиденном — надёжнее без такой фичи.


      1. WASD1
        08.11.2021 22:04

        Я присоединюсь к вашей беседе:

        Возможны два случая:
        1. Просто встретили секцию данных без чисел (по спецификации) - очевидно, что здесь Error исползовать не надо (с современных позиций программирования - даже ошибочно), надо секцию пропустить.
        2. В процессе парсинга произошла непредполагаемая ситуация - это Error. Но тут надо проинформировать пользователя "АУ, мы вообще правильно файл парсим, а то вдруг ты сейчас 100500 млн рублей контрагенту полатишь".

        Т.е. в обоих случаях "молчаливый Error" не походит.
        В первом - это не должен быть Error, во втором - он не должен быть молчаливым.


    1. amarao
      06.11.2021 13:12
      +10

      Многие десятилетия индустриального кода говорят, что чем раньше остановишься в случае ошибки, тем меньше кожанных мешков умрёт. В идеале останавливаться надо на этапе компиляции.

      А в отношении ошибок есть два класса ошибок: ошибка в программе и ошибка в вводе. Ошибка в программе может быть остановлена одним образом: стоп.

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

      В Rust это хорошо разделено: ошибка в программе (которую не поймали в компиляции) - это panic!. Ошибка в вводе/выводе - это Result, который либо Ok, либо Err.


      1. mikhanoid
        07.11.2021 14:06

        На практике в коде на Rust очень часто работают с Result через простой unwrap , то есть,panic!, в итоге. Теоретически так быть, конечно, не должно, но так есть. Я думаю, это потому что в Rust довольно синтаксически громоздко разбирать результаты цепочек вызовов функций, даже используя and_then, а в системном програмировании такое постоянно нужно делать и люди идут по пути наименьшего сопротивления. "Компилируется же, значит работает" (c).


        1. amarao
          07.11.2021 14:17
          +4

          Когда вы пишите unwrap, это осмысленное деяние. "Упади если тут фигня". На самом деле банальный '?' вполне работает.

          Более того, упавшая от кривого юникода на входе программа лучше, чем программа, которая молча продолжит "варить" непонятно что.


    1. snvtr
      07.11.2021 09:37

      в WSH с момента его появления это по сути средство для обработки исключений. Там по умолчанию goto 0. Но это конечно не программирование, а скрипты для автоматизации рутины ;)

      По пункту 2 - писал ли автор NLM под нетварь? Там многозадачность была такая многозадачность! Если этот условный DoEvents не делать, то сломается весь сервер.


  1. NeoCode
    05.11.2021 19:44
    +21

    Динамические метки и goto по ним есть в расширении GCC
    Никогда не пользовался, но зато как по-хакерски:

    static void *array[] = { &&foo, &&bar, &&hack };
    goto *array[i];

    или даже вот так, храним смещения между метками, а не сами метки:
    static const int array[] = { &&foo - &&foo, &&bar - &&foo,  &&hack - &&foo };
    goto *(&&foo + array[i]);


    1. netch80
      05.11.2021 22:23
      +2

      > Динамические метки и goto по ним есть в расширении GCC

      Там это явно записано в самом goto. А с коболовским читаешь и думаешь, что перейдёт на path-1 всегда, а оно оказывается где-то изменено…


      1. unsignedchar
        06.11.2021 09:52
        +2

        а оно оказывается где-то изменено…

        Глобальные переменные в любом языке. Разименование ссылок в С.


        1. netch80
          06.11.2021 10:21

          > Глобальные переменные в любом языке.

          1. В этом случае всё равно все доступы хотя бы можно нагрепать.
          2. Я не представляю себе глубокого смысла такого написания, в котором бы для назначенного goto использовалась глобальная переменная. Это за пределами норм любых возможных стилей, я твёрдо убеждён. Напоминаю, что goto допустим только в пределах одной функции. В крайнем случае будет переменная уровня модуля, это ещё понятно.

          > Разименование ссылок в С.

          В C, вообще-то, ссылок нет, есть только указатели. Если вы про них… повторюсь, когда явный вызов по указателю — читатель кода ждёт, что этот указатель кем-то присваивается и может меняться по условию.

          А когда вы видите вроде бы нормальный безусловный goto 1234, но оказывается, что его могут менять, и любой подобный переход надо проверять грепом по исходникам, а не меняет ли кто его… мягко говоря, бессмысленная затрата сил.


          1. unsignedchar
            06.11.2021 11:03
            +2

            В этом случае всё равно все доступы хотя бы можно нагрепать.

            любой подобный переход надо проверять грепом по исходникам

            Нет принципиальной разницы. Можно изменить значение переменной, можно изменить указатель, можно изменить ещё и метку. А в некоторых С++ можно ещё и операторы переопределить ;) Кстати, про

            #define true false

            не забываем ;)


            1. netch80
              06.11.2021 11:26

              > Нет принципиальной разницы.

              Есть. Только надо сравнивать BASIC не с C, а например с Java (хотя бы). Там возможности скрыто выстрелить всем в ногу резко сокращаются.

              > #define true false

              gcc -E к вашим услугам :) В случае проблем определённого уровня непонятности он неизбежен.


              1. unsignedchar
                06.11.2021 12:18

                Способы выстрела в ногу существуют, и их >1. Не надо пользоваться способами, которые могут привести к выстрелу, вот и всё. С++ тоже есть глобальные переменные, но никто же не заставляет писать наС++ в cobol-style.


        1. mayorovp
          06.11.2021 12:00
          +1

          Глобальные переменные в любом языке.

          Но не в любом языке каждая метка является скрытой глобальной переменной.


    1. gleb_l
      06.11.2021 01:44
      +5

      То, что написано на Си - это по сути индексный джамп. Вполне себе легальная вещь, может исполняться в ПЗУ. То, что на Коболе - это патчинг кода самим кодом. Подобные трюки можно было видеть на ассемблере во времена, когда кодовый сегмент не могли/не хотели защищать от модификации. Но иметь такую штуку на ЯВУ - это жить на вулкане.


      1. Rigidus
        06.11.2021 07:06

        Полезная идея для криптопротекторов однако..


    1. leshabirukov
      06.11.2021 12:57

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


  1. tyomitch
    05.11.2021 19:49
    +29

    On Error Resume Next живёт и здравствует в PHP под названием @

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


    1. static_cast
      05.11.2021 20:02
      +1

      Согласен. DoEvents это аналог старого доброго Sleep(0), который давал возможность ОС консистентно разгрести очередь сообщений, пока кто-то умный блокировал UI-йный тред.


      1. firehacker
        06.11.2021 02:24
        +5

        Технические подробности

        На самом деле, не многие догадываются, что VB является слиянием двух технологий: Ruby и EB — где EB это то, что позже стало известно как VBA, а Ruby это нечто, что придумал когда-то Алан Купер без всякой бейсиковости за этим.


        EB aka VBA — это технология добавления «программируемости» к чему угодно. Хотите — к Офису, а хотите — к АвтоКАДу. То, к чему прикручивают технологию VBA по терминлогиюю VBA называется «хостом» — хостом может быть Excel, Access, AutoCAD и т.п.


        В случае VB хостом является нечто, что люди из Microsoft называли Ruby — и в данном случае это не имеет ничего общего с ЯП «Ruby».


        Так вот DoEvents — это функция из «епархии» VBA. DoEvents можно найти и в самостоятельном VB, и в VBA (в отличие, например, от «объекта» App, которого в офисах не будет, так как эту сущность привнёс Ruby).


        И начинка rtcDoEvents (так называется реализация DoEvents) просто вызывает callback-функцию HostDoEvents: она не является частью EB/VBA, её должен предоставить хост, к которому прикручивают VBA. Что в этой функции будет сидеть — одному богу известно. Это зависит от хоста, точнее от фантазии его создателей.


        В случае standalone VB реализация HostDoEvents является частью исходного кода Ruby. Помимо того, что там сидит пресловутая цепочка PeekMessage—>TranslateMessage—>DispatchMessage, и вызов Sleep, там сидит вызов CheckFreeUnusedLibs, что приводит к вызову ole32!CoFreeUnusedLibraries.


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


        1. alan008
          06.11.2021 10:32
          +5

          В Delphi в VCL аналог DoEvents кстати тоже есть. Это Application.ProcessMessages. Надо просто уметь им пользоваться. Если как у автора вызывать его из какого-то расчетного цикла (для обновления прогресса или какого-то списка на форме) , то все контролы на форме или сама форма должны быть предварительно задизэйблены, чтобы нажатия на них не срабатывали.


          1. Osnovjansky
            08.11.2021 16:22
            +1

            то все контролы на форме
            Кроме весьма полезной в таком случае кнопки «Отмена», чтобы слишком длинную операцию можно было остановить штатно.

            Кроме того, добавлю. Если мне не изменяет память, этот подход родом из времен Delphi 1 / Windows 3.1, когда на персоналках процессор обычно был только один, многозадачность была невытесняющей, и современных развитых средств межпроцессной синхронизации не было ни в системе ни в подавляющем большинстве языков программирования.

            — — — —

            PS Прошу прощения что добавляю сюда — технические)) проблемы.
            JerleShannara
            к ветке:
            https://habr.com/ru/company/alfa/blog/587536/#comment_23672762
            у меня вопрос возник:
            у вас нет оперативной памяти, вообще нет(никакой, даже Cache as ram нельзя). А call ...
            Как поступаете с указателем на стек? И тем что по этому указателю идёт запись значений? Или от того что памяти нет — нечего плохого не происходит?

            Если будете отвечать — лучше в ту-же ветку
            Прошу прощения — после второго прочтения, я всё таки понял, что ответ в той ветке — есть.


          1. popov654
            13.11.2021 12:04

            А зачем дизэйблить контролы? Пользователь что-то нажмёт, это что-то отработает, а потом продолжится вывод нашего длинного списка. Если этому "что-то" не нужны наши числа, которые мы генерируем, то не вижу никакой проблемы


            1. mayorovp
              13.11.2021 13:17

              Как минимум, надо либо задизейблить ту кнопку, нажатие на которую и привело к выводу длинного списка, либо как-то разумно обработать повторное нажатие.


            1. geher
              14.11.2021 13:33

              Действия пользователя могут привести к удалению элемента интерфейса, на который должен выводиться этот длинный список. Достаточно, например, закрыть окно, тяпнув системную кнопку закрытия окна..

              Решений несколько.

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

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


            1. alan008
              14.11.2021 15:16

              Если есть кнопка "Начать расчет" и её нажмут еще раз, то мы снова окажемся в методе расчета, "как бы рекурсивно", но на самом деле с точки зрения стека вызовов это будет не совсем прямая рекурсия :) Ну и другие выше написали, что если закрыть окно или сделать еще что-то нехорошее, то когда код вернется из обработчика обратно в код нашего расчета, то в лучшем случае мы получим Access Violation при обращении к уже закрытому и освобожденному окну или какому-то контролу на нём.


      1. SpiderEkb
        06.11.2021 10:29

        Совершенно разные вещи.

        DoEvents - фактически вызов обработчика очереди сообщений приложения. Там много чего, в том числе и отдача слайсов в систему. Длительные глухие циклы не только подвешивают приложение, но могут всю систему притормозить (такая уж многозадачность в винде). Аналогично притормозить систему можно и в отдельном треде, сделав там длинный цикл баз вызовов функция синхронизации (WaitObject/WaitForMultiplyObjectcts или хотя бы Sleep).


        1. firehacker
          06.11.2021 14:02
          +3

          но могут всю систему притормозить (такая уж многозадачность в винде).

          В Windows 9x разве что. Хорошая многозадачность в винде, не надо наговаривать. По крайней мере на уровне ядра всё сделано безупречно.


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


          Не согласны — предоставьте PoC-код подвешивания системы длительным глухим циклом.


          1. Alexey2005
            06.11.2021 17:21
            +3

            Самое интересное, что подвесить глухим циклом какой-нибудь десктопный Ubuntu, в отличие от винды, даже вообще не проблема. Достаточно сделать серию malloc, чтобы выделить примерно 95% от всей наличной памяти, а потом в цикле делать постоянное перезаполнение выделенной памяти рандомным шумом.
            OOM Killer в Linux настолько дерьмовый, что ждать его срабатывания можно буквально часами, а разработчики ядра не видят никакой проблемы в том, что странички, ответственные за обработку пользовательского ввода, тоже свопятся, так что пользователь полностью теряет контроль над ОС и не может даже прибить глючную программу.


            1. HSerg
              07.11.2021 11:19
              +2

              На Windows с OOM не лучше - глухое подвисание UI (до нескольких часов) и последующий BSoD. Лучше бы уж приложения убивались.


            1. mikhanoid
              07.11.2021 13:34

              А что мешает oom-killer настроить? Довольно просто выдать всем процессам определённого пользователя повышенный приоритет в очереди на завршение при нехватке памяти. Cgroups тоже существуют


          1. SpiderEkb
            07.11.2021 13:03

            Код не покажу т.к. лет пять уже с виндой дел не имею. Но до этого 25+ лет под виндой. 3.11Б 98, 2000, 7 (32 бит). Может в новых 64 бит, на многоядерных процессорах с гипертрейдингом что-то и стало лучше, но еще в 7 на стареньком двухядерном целероне без гипертрейднга, поток (особенно с повышенным приоритетом), в котором не было ничего кроме сложных математических вычислений, без функций синхронизации, очень сильно тормозил систему. Попытка вызова диспетчера приложений затягивалась на 10-15 секунд. Попытка снятия задачи еще на столько же.

            Радикально помогал вызов синхронизирующей функции. Хотя бы казалось бы бесполезный Sleep(1) в теле цикла потока. И все сразу начинало дышать.

            Можете сами попробовать. Только для чистоты эксперимента без фреймворков, голый WinAPI.

            Ну так многозадачность в винде реализована. Просто надо знать и учитывать.


            1. firehacker
              07.11.2021 14:47

              Можете сами попробовать. Только для чистоты эксперимента без фреймворков, голый WinAPI.
              Я и пишу на голом WinAPI на C/C++ без всяких фреймворков.
              То, что вы описываете, для меня выглядит как нонсенс. Не наблюдал я такого в десятках и сотнях случаях, когда подобные циклы возникали.

              Sleep(1) «помог бы» (по вашим словам), но Sleep(1) сделает так, что то, что мега-фукнция вычислила бы за секунду, она будет вычислять за полторы минуты.


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


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


              В норме того, что вы описываете, не происходит.


            1. mayorovp
              07.11.2021 14:50
              +2

              особенно с повышенным приоритетом

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


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


            1. DreamingKitten
              07.11.2021 15:03

              Радикально помогал вызов синхронизирующей функции. Хотя бы казалось бы бесполезный Sleep(1) в теле цикла потока. И все сразу начинало дышать.

              Во-первых, Sleep(1) будет ждать не 1 миллисекунду, как вы, наверное, ожидаете, а заранее совершенно неизвестный интервал времени, зависящий от частоты таймера, которую задаёт ОС из своих личных соображений.

              Во-вторых, правильный способ отдать исполнение другим потокам изнутри вычислительно тяжёлого цикла, это не Sleep() а SwitchToThread(). Его преимущество в том, что он а) позволяет системе не тормозить б) если система больше ничем, кроме вашей программы не занята, то исполнение к вашему циклу вернётся на порядки быстрее, чем после Sleep()


    1. faiwer
      05.11.2021 22:27

      On Error Resume Next живёт и здравствует в PHP под названием @

      А разве это не аналог


      // @doSomethingDangerous() 
      try { doSomethingDangerous() } catch(err) {}

      ? т.е. действует ровно на ту конструкцию к которой задан, а не на весь код ниже.


      1. faiwer
        05.11.2021 22:36
        +4

        UPD: Память меня подвела. Это не аналог try-catch, это silence-operator. Возможность опустить вывод "diagnostic error". С обычными настоящими ошибками никакой связи.


      1. Finesse
        10.11.2021 05:05

        В PHP есть особый вид боли 2 вида ошибок: исключения и ошибки (это легаси). Первые можно поймать с помощью try-catch, а вторые приглушить с помощью @ и получить данные о приглушённых ошибках с помощью специальных функций.


        1. POPSuL
          12.11.2021 01:11

          Так и вторые можно превратить в исключения с помощью специальных функций.


    1. ollisso
      05.11.2021 22:27
      +6

      Эм, не согласен. В PHP @ значит не показывать ошибку, но не значит "пропустить строку в случае ошибки".

      Пример @notexistFunction() остановит программу с "пустым экраном", без показа ошибки.

      В VB6 on resume next перейдет к следующей строке в этой ситуации.


      1. riky
        05.11.2021 23:30
        +3

        неверно. @ с Fatal не справляется.

        попробуйте например @file_put_contents(1, []); - второй параметр должен быть строка но ошибка не будет выведена.

        или например чтение несуществующего ключа в массиве

        $a = []; $b = @$a['val'];
        аналог современного $b = $a['val'] ?? null;

        но, конечно, все равно эту конструкция лучше не исопльзовать.


        1. ollisso
          06.11.2021 13:15

          Про Fatal был удивлён - не знал.

          Редко пользуюсь @ - обычно всё же display_errors + log_errors + нормальные проверки, чтобы ошибок небыло.


      1. firehacker
        06.11.2021 02:27
        +2

        Эм, не согласен. В PHP @ значит не показывать ошибку, но не значит "пропустить строку в случае ошибки".

        Если уж на то пошло, то и в VB конструкция «On Error Resume Next» не означает «пропустить строку в случае ошибки».


    1. quwy
      06.11.2021 01:55
      +3

      On Error Resume Next живёт и здравствует в PHP под названием @

      @ в PHP -- это всего лишь приказ не гадить своими текстовыми ошибками в стандартный вывод.

      Представьте, что ваша программа на любом вменяемом языке при выполнении функции, скажем, fopen() над недоступным файлом, начнет не просто возвращать ошибку, а еще и самовольно комментировать произошедшее в общий поток вывода. Нормально? А в PHP все именно так.

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

      P.S. Последний раз брал PHP в руки лет десять назад (версия 5.x), может быть с тех пор что-то изменилось.


      1. SergeyDeryabin
        06.11.2021 02:07
        +4

        Сильно изменилось. Уже давно практически все можно через Exception отловить и обработать, без отображения предупреждений


        1. quwy
          06.11.2021 02:37
          +3

          Так речь не об эксепшенах даже, вот что бесит. Обычная функция, обычного открытия файла, при невозможности открытия выплевывает в "боевой" поток вывода какой-то левый текст. При этом она не бросает эксепшн и не валит скрипт, а просто возвращает false, как и написано в документации. Но, на кой-то хрен, еще гадит в stdout. Это же фейспалм вселенского масштаба!

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

          И ведь подобные решения во многом предопределили стиль PHP-кодинга на многие годы. А от ярлыка "для макак" языку уже никогда не отмыться.


          1. vgogolin
            06.11.2021 08:58
            +2

            https://www.php.net/manual/en/function.error-reporting.php

            похоже, далее hello world дело у вас не пошло )


            1. netch80
              06.11.2021 10:24
              +5

              Это глобальная настройка. Надо сбрасывать её в 0 каждый раз просто подозревая, что кто-то в вызванном коде её подкрутил?


              1. quwy
                07.11.2021 01:58

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

                Использование стороннего кода превращается в прогулку по минному полю. Никогда не знаешь какие грабли тебе подбросит чужой неисследованный код путем вызова ini_set().


              1. DreamingKitten
                07.11.2021 13:26
                -2

                Это глобальная настройка.

                Не только, её можно задавать и непосредственно в коде.


                1. quwy
                  08.11.2021 02:12

                  Об этом и речь, вообще-то. Если проект состоит из множества модулей разного происхождения, то никогда нельзя быть уверенным, что туда записано в данный момент времени. Каждый мелкий PHP-файл может менять эту настройку как ему хочется, а влияет она потом и на весь остальной код.


          1. bolk
            06.11.2021 12:14
            -2

            Так не используйте функции для работы с файлами, используйте объекты


      1. ollisso
        06.11.2021 13:11

        За 10 лет уже очень много изменилось. И текущий стандарт это делать : display_errors = off, log_errors = on. Т.е. ошибки не выводить, а логировать в файл.

        Ну и да, в случае с открытием файла: если проверять всё до того как делать fopen, то и вероятность ошибки близится к нулю :) Т.е. is_readable и тп.


        1. quwy
          06.11.2021 14:32
          +11

          если проверять всё до того как делать fopen, то и вероятность ошибки близится к нулю :) Т.е. is_readable и тп.

          Вы специально вредные советы даете, или прикалываетесь?

          Никогда нельзя так делать. НИКОГДА!

          Просто потому, что нет никаких гарантий, что на временном промежутке между is_readable и последующим fopen состояние файла не изменится.

          Даже если оно "вроде бы не должно меняться", это рано или поздно может произойти. Не в этой ревизии кода, так через пару лет, когда логика поменяется. А вылавливать подобного рода ошибки потом будет очень и очень трудно.


      1. popov654
        13.11.2021 13:10

        Не соглашусь, всё же довольно удобно в процессе разработки включить вывод ошибок прямо в браузер, чтобы не лазить постоянно в error лог (если и браузер, и лог открыты в фулскрин, нет второго монитора и/или используемая ОС - Windows, где нет tail с режимом отслеживания изменений).

        Кстати, разве в Python иначе? Там тоже ошибка приводит к выводу её в основной поток вывода, если я ничего не путаю. И в JS тоже. Единственная разница - там программа всё-таки падает на том месте, на котором произошла ошибка.


        1. quwy
          14.11.2021 01:48
          +1

          всё же довольно удобно в процессе разработки включить вывод ошибок прямо в браузер

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

          Я уже молчу про то, что в массе вариантов выплюнутый таким образом текст на экран все равно не попадает, а логику и/или верстку на фронтенде ломает к чертям.

          Windows, где нет tail с режимом отслеживания изменений

          Встроенный файл-въювер FAR Manager отлично справляется.

          Кстати, разве в Python иначе?

          Без понятия, но не удивлюсь, если да. Под стать, так сказать.

          И в JS тоже

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

          там программа всё-таки падает на том месте, на котором произошла ошибка

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


    1. bolk
      06.11.2021 12:12

      -


  1. aamonster
    05.11.2021 20:18
    +2

    3 – это ж предтеча указателя на функцию. При разумном использовании создаст примерно те же проблемы... Разумеется, с тех пор языки развились и использовать неразумно стало труднее :-)

    2 – так и не понял, какое отношение имеет к потокам. Но ситуации, когда внутри одного цикла обработки событий запускается другой (например, DoModal или обработку таскания мышкой через цикл с GetMessage) тоже недолюбливаю.

    1 – ну, автор, видимо, достаточно молод и не застал времена, когда 100 строк на бейсике – это было дофига, и хоть какая-то альтернатива "упасть на первой же ошибке" была праздником...


    1. netch80
      05.11.2021 22:38

      > и хоть какая-то альтернатива «упасть на первой же ошибке» была праздником…

      Альтернатива упасть это скорее on error goto <строка>. Вот это уже осмысленная обработка (там ещё давался номер ошибки и номер исходной строки в переменных — так было, например, в GW-BASIC).
      А полное замалчивание это таки несерьёзно.

      > 3 – это ж предтеча указателя на функцию. При разумном использовании создаст примерно те же проблемы…

      Это ужасная предтеча именно потому, что по чтению кода 1) нельзя предположить, что точку перехода вообще может кто-то менять (фича редкая и о ней обычно не думают), 2) сложно вычислить, чем и как она меняетcя.
      Когда есть указатель на функцию или переменная точки назначения в «назначенном GOTO» (так это звалось в Fortran), то сразу видно, кто этим управляет и как. Тут — такого нет, фича контринтуитивна.


      1. tyomitch
        05.11.2021 22:48

        (там ещё давался номер ошибки и номер исходной строки в переменных — так было, например, в GW-BASIC)

        VB6 сохранял эту возможность в полном объёме.
        Был даже какой-то «предрелизный» плагин, добавлявший номера всем строкам: если ошибка происходит в непронумерованной строке, то в Erl будет 0.


      1. aamonster
        05.11.2021 23:31
        +5

        Вы из другой эпохи)

        То есть сейчас я с вами во всём соглашусь, но 33 года назад был другого мнения, слаще репки ничего не пробовал. А ещё в ту эпоху подход был совершенно другим. Программу можно было держать в голове и продумывать все пути выполнения. Т.е. никаких "нельзя предположить", ты знаешь чуть ли не наизусть все 100 строк программы). Это сейчас мы стали ленивые и умные, и эффективности кода предпочитаем его понятность.


        1. netch80
          06.11.2021 00:11

          > То есть сейчас я с вами во всём соглашусь, но 33 года назад был другого мнения

          33 года назад я в школе баловался тем же Бейсиком на Агатах. Я не знаю, о какой вы эпохе говорите, но, похоже, она примерно совпадает.

          > Т.е. никаких «нельзя предположить», ты знаешь чуть ли не наизусть все 100 строк программы).

          У меня впечатление, что вы спутали контекст. Эта часть обсуждения про ALTER GOTO, которое в COBOL и в котором могли быть и миллионы строк программы. Точнее, в одном модуле вряд ли держали бы больше нескольких тысяч, но всё равно это сильно больше, чем охватывается со знанием наизусть.


  1. Exclipt
    05.11.2021 20:30
    +9

    С использованием ножа можно сделать салат, а можно и палец себе отрезать. С другой стороны особо одаренный программист не имея Error Resume Next, может каждую команду и в try/catch без обработки ошибок обернуть, еще и функцию удобную для этого сделать.


  1. kozar
    05.11.2021 21:19
    +17

    Другими словами, мы не можем точно сказать, куда приведёт нас GO TO, если только не знаем полную историю выполнения приложения

    Да это же прямо описание реактивного программирования! Control flow скрыт в графе зависимостей observable/computed переменных, удачной отладки!


    1. mayorovp
      06.11.2021 12:07
      +1

      Э-э-э, а зачем вообще отлаживать граф observable/computed переменных?


      1. Alexey2005
        06.11.2021 12:42
        -1

        Затем, что программисты на языках типа C# очень любят использовать композицию вместо наследования. В итоге у нас программа фактически собирается прямо в рантайме из отдельных кирпичиков, и из самого кода её структура совершенно не очевидна.
        Фактически, при этом у нас нет возможности без отладчика узнать, при каких условиях выполняется тот или иной кусок кода (и выполняется ли он вообще хоть когда-то), потому что он выполнится по событию, которое генерируется неявно (в момент применения какого-нибудь LINQ-выражения) и передаётся в обработчик, который устанавливается динамически в момент получения объекта, причём объект может быть не только вашим, а вообще любым, который поддерживает нужный интерфейс.
        Это просто блюдо спагетти, в котором за кодом не видно алгоритмов, и разбираться в такой динамической каше — натуральный ад, такой, что проще реверсить C++ бинарник в IDA, чем разбираться в C# коде, где «реактивные программисты» навертели этой долбанной динамики с композицией и фабриками фабрик.


        1. mayorovp
          06.11.2021 12:51

          Если у вас LINQ-выражения вызывают события — почему вы обвиняете в этом реактивное программирование?


          1. Alexey2005
            06.11.2021 13:27
            +4

            Потому что реактивное программирование скрывает от программиста контекст. Если вы, читая код, видите вызов метода, то перейти по этому вызову можно в один клик. А если вы видите что-то вроде emitEvent(NeedData)? Как понять, куда улетит управление, если обработчики событий динамически назначаются в рантайме?
            Но и это ещё не самое плохое. Хуже всего то, что когда программа написана в реактивном стиле, то даже и вызов событий спрятан. Вот вы выполняете присваивание. Обычное присваивание, записывая значение в поле объекта. Как, глядя на эту строчку, можно хотя бы догадаться, что тут у нас оказывается будет последовательно вызвано целых четыре разных события? Особенно если это поведение добавляется динамически при определённых условиях.
            В реактивных программах значительная часть информации, нужной для понимания кода, скрыта от программиста. Программист больше не видит, какие подсистемы будут затронуты при выполнении той или иной строчки кода, и потому понимание алгоритмов и поиск ошибок в такой программе — это ад.


            1. mayorovp
              06.11.2021 15:05

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


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


              1. inkelyad
                08.11.2021 10:37

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

                Вот только 'надо знать' программисту поддержки, который 'на эксплуатации произошло нечто, что нам не нравится' расследует. А использовал — совсем другой человек лет n назад.


                1. mayorovp
                  08.11.2021 11:27

                  Уточнение: если программисту поддержки надо знать всех кто зависит от реактивной переменной — возможно, реактивная переменная тоже лишняя или использована неправильно.


                  1. inkelyad
                    08.11.2021 11:33

                    А ему не надо? Потому что достаточно часто идет разбирательство 'что тут у нас вообще в системе происходило, что получилось так, как получилось?' Причем код разбирающийся видит первый раз в жизни. И воспроизведение ситуации в режиме отладки — не обязательно доступно.


                    1. mayorovp
                      08.11.2021 11:46

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


                      1. Alexey2005
                        08.11.2021 15:36
                        -1

                        А он упрощает? С учётом того, что современное программирование — оно уже давно не про написание кода, а про его чтение и правку, то любые техники, которые создают нечитабельный (или трудночитаемый) код — отстой.


  1. censor2005
    05.11.2021 21:20
    +6

    ProcessMessages из Delphi это не аналог ли DoEvents?


    1. SadOcean
      05.11.2021 22:04
      +2

      Да, мне по описанию очень напомнило.

      Использовалось ровно для этого - провернуть основной поток UI сообщений.


    1. quwy
      06.11.2021 02:03
      +2

      Именно оно.

      Разница только в том, что делфовый ProcessMessages тянется еще со времен Win16, когда потоков не было, и это реально был единственный способ "оживить" GUI при длительных операциях (по крайней мере не прибегая к вспомогательным процессам, что было и есть гораздо более сложной задачей).


      1. tyomitch
        06.11.2021 12:35
        +1

        Никакой разницы: DoEvents тоже тянется со времен Win16.


        1. quwy
          06.11.2021 14:35

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


    1. alan008
      06.11.2021 10:33

      Это именно оно (внутри оно просто вызывает WinAPI вызовы PeekMessage-TranslateMessage-DispatchMessage)


    1. HemulGM
      06.11.2021 22:53
      +3

      Это то же самое и это не костыль. Использование этого метода за частую костыль - да. Но сама суть метода - это принцип работы окон в Windows. Обработка сообщений.

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


  1. thelongrunsmoke
    05.11.2021 21:39
    +4

    В ассемблере есть старый добрый RET, который извлекает из стека верхнее значение и передаёт управление соответствующей строке кода, вот только в стек можно писать что-угодно и даже забыть это что-то оттуда достать, получив неопределённое поведение. Некоторые очень злые программисты используют этот манёвр в качестве неявного GOTO.


    1. JerleShannara
      05.11.2021 23:26
      +1

      Хехе, даю следующий уровень: у вас нет оперативной памяти, вообще нет(никакой, даже Cache as ram нельзя). А call MyCoolProc хочется сделать.


      1. kmeaw
        06.11.2021 01:14
        +3

        Link register?

        	mov ebp, RetFromCoolProc
        	jmp MyCoolProc
        RetFromCoolProc:
        MyCoolProc:
          …
          jmp ebp

        А для более сложных случаев есть romcc.


        1. JerleShannara
          06.11.2021 02:00
          +3

          Кидаем в ss текущий сегмент, в sp адрес в коде, в котором лежит адрес возврата из процедуры. Далее jmp MyCoolProc, которая в конце делает совершенно нормальный ret.


          1. CoolCmd
            06.11.2021 10:25
            +1

            видел такое в PC BIOS, до проверки памяти.


  1. vadimr
    05.11.2021 21:49
    -9

    Конечно, это не относится именно к VB, но вряд ли автору бы понравилось, если бы его кофеварка, стиральная машина или, не дай бог, легковая машина выпадала бы в кору из-за ошибки в каком-нибудь скринсейвере. А как-то однажды ракета Ariane V выпала в кору и затем на землю из-за неиспользуемого датчика. Так что resume next имеет своё применение. Хоть это и не лучший возможный вариант, но и не худший.


    1. LynXzp
      05.11.2021 23:10
      +5

      Поэтому в embedded не исключения, а коды ошибок. И вызывающая функция должна их все проверить, а не игнорировать. Вот кстати почитал про Ariane V: первый запуск закончился самоуничтожением потому что была обнаружена непредвиденная ошибка в ПО, а именно за допустимые пределы вышло число горизонтального смещения ракеты. И хорошо что ракета самоуничтожилась, а не On Error Resume Next вдруг там впереди город.


      1. vadimr
        06.11.2021 00:02
        -2

        Тем не менее, даже если все коды проверены, остаётся вероятность неучтённой ошибки. Хотя бы даже в компиляторе или системной библиотеке.

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


    1. Exclipt
      07.11.2021 00:27

      А когда кофеварка забудет выключить нагревательный элемент из-за resume next, посчитав, что процесс нормально прошел, как оно понравится автору?


      1. vadimr
        07.11.2021 05:49
        +1

        Как она может забыть из-за resume next? Он не влияет на порядок операторов. Забыть можно только из-за отсутствия resume next, вылетев по необработанному эксцепшену.


        1. netch80
          07.11.2021 10:42
          +1

          > Как она может забыть из-за resume next?

          Добавьте чуть воображения. Например, пусть есть 2 разных нагревателя (быстрый и медленный). Вызываем код типа (синтаксис C)

          active = get_active();
          if (active & 2) { stop_active_fast(); }
          if (active & 1) { stop_active_slow(); }
          


          Ну а каждый start_X() или stop_X() это, как принято во многих embedded подходах, это передача команды какому-то промежуточному контроллеру (у которого свои заботы — например, нагрузку надо отключать плавно, иначе что-то сгорит; или обогреватель надо ещё обдувать 20 секунд после выключения; есть тысячи подобных тонкостей в реальной технике).

          По каким-то причинам get_active() сломалось и выдало фигню (0). Вы это не видите, потому что из-за аналога on error resume next ошибка тупо проигнорирована. Для C это как не проверили возвращаемое значение из функции, которая возвращает признаки успеха или ошибки. Ошибка даже есть (в режиме on error resume next на каждую ошибку обновляется err и erl), но вызывающему коду пофиг…

          Если бы get_active() возвращало признак ошибки, программа бы знала: если ей надо сейчас включать нагрев, она просто отказывается и выдаёт ошибку на экран, а если выключать — она командует выключить оба (и только после этого, да, ошибку). Но всё ж заглушено…

          PS: Про хитрости embedded: они реально на каждом шагу даже в стандартном компьютере. Вот читаем про 8042 PS/2 controller: в PC (есть на любой материнке, где ещё есть круглые разъёмы для клавиатуры):
          > PS/2 Controller Output Port
          > Bit 0: System reset (output). WARNING always set to '1'. You need to pulse the reset line (e.g. using command 0xFE), and setting this bit to '0' can lock the computer up («reset forever»).

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


          1. vadimr
            07.11.2021 10:53

            Я разве говорил, что не надо проверять коды возврата?

            Весь смысл resume next состоит как раз в том, чтобы сохранить последовательность выполнения операторов. А не в экономии на проверках, тем более критически важных. Каждое средство надо применять с умом

            А правильной метафорой кода в данном случае будет такая:

            включить нагреватель;

            показать мультик;

            выключить нагреватель;

            и падение на мультике.


            1. mayorovp
              07.11.2021 11:05
              +1

              Весь смысл resume next в 21м веке состоит как раз в экономии на критически важных проверках.


              Те, кто на проверках не экономят, выбирают другие техники.


              1. vadimr
                07.11.2021 11:34

                Весь смысл resume next в 21м веке состоит как раз в экономии на критически важных проверках.

                Ну сдуру вообще можно многое сломать.


            1. netch80
              07.11.2021 11:06

              > Я разве говорил, что не надо проверять коды возврата?

              Прямо — не говорили. Косвенно — по факту, говорили. Сначала, давайте уточним: в статье говорится как про «ужасную фичу» не resume next само по себе, а про on error resume next. А это уже крайне важное уточнение.
              В этом режиме таки последняя ошибка фиксируется, но не обрабатывается.
              А вот тут уже открывается то, что одна необработанная ошибка может вызывать каскад следующих. И даже в случае, если вы после блока, обработанного в этом режиме, решили проверить ошибку, вы максимум заметите признак, что что-то случилось, но что стало причиной — исходная ошибка — давно перетёрто более поздними наведёнными ошибками.
              Это то, что в моём аналоге было внутри get_active() — в результате он выдаёт некорректный результат, который дальше рушит всё.
              А далее я привёл пример, как именно оно может рушить.

              > Весь смысл resume next состоит как раз в том, чтобы сохранить последовательность выполнения операторов.

              И опять таки, resume next из обработчика или on error resume next перед блоком целевого кода?

              Если первое — не вопрос, обработчик знает, что делает.

              Если второе — см. выше про невозможность узнать исходную ошибку и про наведённые последствия. Эту последовательность выполнения уже нельзя сохранять, раз ошибка, её надо прервать и перейти на обработку ошибки.

              > и падение на мультике.

              Почему будет падение на мультике, и почему вы считаете, что эта метафора правильнее?


              1. vadimr
                07.11.2021 11:33
                -1

                Сначала, давайте уточним: в статье говорится как про «ужасную фичу» не resume next само по себе, а про on error resume next. А это уже крайне важное уточнение.

                В статье говорится про on error resume next как про средство обработки исключений. Если автор не может представить себе работу с ошибочными ситуациями иначе чем через тотальный механизм исключений и не понимает, зачем этот механизм может быть нужно отключать, то это исключительно его проблема, о чём я и пишу.

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

                Если мы говорим про embedded, то там вообще неправильно строить логику работы на внутренних состояниях программы. Неясно, что происходит - проверь железо.

                Почему будет падение на мультике, и почему вы считаете, что эта метафора правильнее?

                Потому что это типичная проблема вообще. Затаскивание в отказоустойчивый код фрагментов, писавшихся из других побуждений или с другой квалификацией, и падение всей системы от незначимых для логики управления ошибок. А особенно явно это проявляется при реализации в управляющем коде элементов GUI, где кода много, возможностей для возникновения исключений полно, а никакой фактической ценности это не несёт.

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


                1. netch80
                  07.11.2021 12:16
                  +1

                  > В статье говорится про on error resume next как про средство обработки исключений. Если автор не может представить себе работу с ошибочными ситуациями иначе чем через тотальный механизм исключений

                  Я прогрепал исходную статью на слово «исключение» в любом падеже… его там нет совсем. Оно появляется только в комментариях. То же самое про exception. Я не знаю, зачем вы высасываете из пальца то, чего явно нет, и обвиняете в этом автора, но конструктивности обсуждению это не добавит.
                  В статье говорится про обработку ошибок. А вот она может делаться разными методами.

                  > Если мы говорим про embedded, то там вообще неправильно строить логику работы на внутренних состояниях программы.

                  Это было бы хорошо, если бы огромное количество железа не требовало своей логикой работы знания со стороны драйвера, в каком состоянии что находится. Например, есть write-only регистры управления, состояние которых прочесть нельзя — можно только писать и/или посылать общий ресет, если оборудование не отвечает как следует. Или, например, движитель позиционирования детали на станке — задача блока управления разогнать и потом плавно затормозить, и станок не рапортует «ой, движусь уже очень быстро, пора тормозить». Кажется, вы совершенно не представляете себе проблемы этой области.

                  > Неясно, что происходит — проверь железо.

                  Это если есть чем и как проверять. Разумеется, какие-то защиты всегда есть, но обычно только против самых крайних проблем (типа фатального сгорания).

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

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

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

                  Если это при включении (поворот ключа зажигания или аналог), это, конечно, сомнительно, но критической проблемы не видно.


                  1. vadimr
                    07.11.2021 14:25

                    Я прогрепал исходную статью на слово «исключение» в любом падеже… его там нет совсем. Оно появляется только в комментариях. То же самое про exception. Я не знаю, зачем вы высасываете из пальца то, чего явно нет, и обвиняете в этом автора, но конструктивности обсуждению это не добавит.
                    В статье говорится про обработку ошибок. А вот она может делаться разными методами.

                    В статье говорится про "вредный" оператор on error resume next, который сам по себе является частью механизма обработки исключений, не более и не менее. Не надо натягивать сову на глобус и представлять дело таким образом, как будто бы речь идёт об обработке ошибок вообще. Если бы автор написал, что критические ошибки нуждаются в обработке, вряд ли бы кто-то стал спорить с таким утверждением. Но оно тривиально.

                    Это было бы хорошо, если бы огромное количество железа не требовало своей логикой работы знания со стороны драйвера, в каком состоянии что находится. Например, есть write-only регистры управления, состояние которых прочесть нельзя — можно только писать и/или посылать общий ресет, если оборудование не отвечает как следует. Или, например, движитель позиционирования детали на станке — задача блока управления разогнать и потом плавно затормозить, и станок не рапортует «ой, движусь уже очень быстро, пора тормозить». Кажется, вы совершенно не представляете себе проблемы этой области.

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

                    И write-only регистры здесь не причём. Мы управляем физическим обьектом, а не содержимым регистров.


                    1. mayorovp
                      07.11.2021 14:58

                      Управление не может успешно функционировать без обратной связи. Если у вас станок не имеет данных о фактическом положении детали (реальном фактическом положении, а не модели положения, созданной на основании своих усилий по её перемещению), то такой станок не сможет работать.

                      На самом деле может, и даже работает. Датчик-то денег стоит, а процессорное время на расчёт модели — бесплатное до определённого предела.


                      1. vadimr
                        07.11.2021 16:21

                        Процессорное-то время бесплатное, но модель отличается от реальности.


                    1. netch80
                      07.11.2021 15:52
                      +1

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

                      Мне всё больше кажется, это вы как раз стали представлять именно таким образом.

                      > Управление не может успешно функционировать без обратной связи. Если у вас станок не имеет данных о фактическом положении детали (реальном фактическом положении. Если у вас станок не имеет данных о фактическом положении детали (реальном фактическом положении, а не модели положения, созданной на основании своих усилий по её перемещению), то такой станок не сможет работать.

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

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

                      Это вполне типовой метод, и не только в станке. В компьютере он работал, например, в приводе дискет — всегда, и в приводе жёстких дисков — где-то до объёмов в 200MB. Если контроллер хочет переустановить позицию (рестарт или подозрение на некорректность позиции), он крутит позицию к нулю, пока не срабатывает датчик нулевой позиции, а дальше хранит в памяти состояние, до которого он «докрутил» управление. Впрочем, в «Агатах» крутили на 80 DD дорожек назад безусловно, не проверяя датчик, что приводило к постепенному разбалтыванию механизма :) но пока он был правильно состроен — работало.

                      А чтобы датчик постоянно проверял позицию… никто так не делает.

                      И это не единственный метод (хотя один из самых простых и при этом работающих).

                      > Мне кажется, это вы себе совершенно не представляете проблемы этой области.

                      См. выше. Банальности, но вы их не знаете.

                      > И write-only регистры здесь не причём. Мы управляем физическим обьектом, а не содержимым регистров.

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


                      1. vadimr
                        07.11.2021 16:20
                        -1

                        Когда я начал читать про шаговый двигатель, то как раз хотел в ответ написать, что таким образом было реализовано печально известное управление дисководом в Apple II... и тут дочитал до того, что Вы сами пишете про Агат. А раз Вы знаете про Агат, то и постоянный звук "трррррррр" должен быть Вам хорошо знаком. А это как раз издержки принятого хренового решения.

                        При этом дисковод Агата - это далеко не станок, там всего 80 позиций для 40 дорожек.

                        > И write-only регистры здесь не причём. Мы управляем физическим обьектом, а не содержимым регистров.

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

                        Разница в том, что значение регистра само по себе не представляет интереса.


                      1. netch80
                        07.11.2021 16:23
                        +1

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

                        Хреновое решение было, да, но оно в отсутствии датчика нулевой дорожки, а не в отсутствии мифического датчика, который бы всегда мерял положение головки и выдавал что-то вроде «дорожка 17½».

                        > Разница в том, что значение регистра само по себе не представляет интереса.

                        Представляет, когда оно даёт любой эффектор для чего-то ещё.
                        Даже если просто на шину один будет выводить 1, а другой 0 (вместо Z-состояний), можно сжечь выходные каскады.
                        А пример с вечным Reset пожёстче :)


                      1. vadimr
                        07.11.2021 16:32

                        В микроконтроллерах часто делают один write-only регистр на выход и другой read-only регистр для чтения того, что фактически получилось. Именно по указанной выше причине. Интенция не всегда бывает равна результату.

                        а не в отсутствии мифического датчика, который бы всегда мерял положение головки и выдавал что-то вроде «дорожка 17½».

                        Однако, фактически именно так в современных дисках и происходит. Номер дорожки читается из сервоинформации, а не подсчитывается по числу шагов.


                      1. netch80
                        07.11.2021 17:51

                        > Номер дорожки читается из сервоинформации, а не подсчитывается по числу шагов.

                        Потому я и проводил аналогию с древними дисками, а не современными. Вы не найдёте такую сервоинформацию на станке, или она будет слишком ненадёжной.

                        > Интенция не всегда бывает равна результату.

                        Это всё-таки особые случаи (я даже с ходу пример не вспомню).


                      1. vadimr
                        07.11.2021 17:59
                        -1

                        Поэтому на станке стоят просто датчики положения. Оптические энкодеры какие-нибудь.


  1. OvO
    05.11.2021 22:48
    +3

    Одна из моих любимых баек про "On Error Resume Next". Во времена моей юности я писал программу управления, которую прогоняли на натурных испытаниях. И вот запуск всей работы и... ничегошеньки не работает, вообще. Стоимость одного испытания была ооочень большой поэтому вояки были очень недовольны полным провалом. Принесли коробку, смотрю логи, а там физический отвал одного устройства и действительно, видно как забыли привинтить разъем, который отвалился при перегрузках. Программа, не найдя устройства, постоянно запускалась и падала. Они в претензиях, смысл которых сводился к "И чего? Один прибор не работает, а остальное то цело? Почему не смогли проверить другие функции?". С тех пор, я пишу программы, которые никогда не сдаются, пытаются исправить ошибки и оживить отпавшие устройства.


    1. LynXzp
      05.11.2021 23:17
      +8

      On Error Resume Next и отсутствие ответа с датчика это разные вещи. Нет ответа, но можно делать остальное — хорошо. Но выставить температуру равной числу деленному на ноль нельзя. Нельзя продолжать работу после выхода за рамки массива. Хотя конечно есть разные ситуации. В стимуляторе сердца критически важно не останавливать импульсы. Но в БД лучше сказать «ну не могла», не закрыть транзации и упасть чем затереть всю базу в случае ошибочного адреса указателя.


    1. aamonster
      05.11.2021 23:36
      +2

      Ну, это скорее не про ветхозаветный resume next, а про нормальную обработку ошибок (неважно, на try-catch, кодах ошибок или монаде Maybe/Either).


      1. beerchaser
        06.11.2021 11:58
        +1

        "Ветхозаветный" resume next - это как раз про обработку ошибок в кодах. В vb6, как минимум, on error сбрасывал статус объекта Err. При возникновении ошибки у объекта устанавливались атрибуты Code и Description. Обработка знания о возникновении и о коде ошибки - в руках пишущего. Может не так структурировано, как try-cath-fin, но вполне функционально. А отстрел обработки ошибок без вывода сообщений или с невнятным сообщением - достаточно часто встречаемое решение даже в настоящее время и даже с использованием современных языков программирования и даже с использованием современных конструкций языка.


        1. aamonster
          06.11.2021 12:35

          Емнип он появился задолго до VB6 (на восьмибитных компах), и, насколько я помню, объекта Err тогда ещё не было (хотя не поручусь).


          1. tyomitch
            06.11.2021 13:18

            ON ERROR RESUME NEXT появился в QuickBASIC (на 16-битных компах); тогда ERR был целочисленным (без поля Description), но принципиальной разницы не было.


            1. aamonster
              06.11.2021 14:56

              Точно именно там? Мне казалось, что я resume next в 8-битных видел, но мог попутать (у меня-то его тогда не было, я писал проверки и завидовал).


          1. beerchaser
            06.11.2021 15:12
            +1

            Заглянул в туториал gwbasic ( по старой памяти, сталкивался в школе). Там данная конструкция делится на две: on error goto n и, отдельным оператором, resume next. При возникновении ошибки устанавливаются переменные err и erl, которые содержат код ошибки и строку, в которой возникла ошибка. Оператор resume next (насколько я понял) возвращает исполнение на строку, следующую за строкой возникновения ошибки (erl+1). Недостаток данной конструкции (имхо) - плохая читаемость и структурированность (для улучшения которой обработчики ошибок выносились на номера строк, заведомо большие тех, на которых располагался код основной программы). В остальном - вполне функциональная конструкция, корректность использования которой опять таки возложена на разработчика.


            1. tyomitch
              06.11.2021 15:37

              Между ON ERROR RESUME NEXT и парой ON ERROR GOTO 1000 / 1000 RESUME NEXT есть одна существенная разница: RESUME NEXT сбрасывает ERR, так что проанализировать его в точке возникновения ошибки, как после ON ERROR RESUME NEXT, не будет возможным. Выносить из основной программы приходится не только строчку RESUME NEXT, но и обработчик целиком.


              1. aamonster
                06.11.2021 15:54
                +1

                Если кому-то позарез нужен аналог ON ERROR RESUME NEXT – можно ERR скопировать в другую переменную.


    1. alan008
      06.11.2021 10:35
      -2

      Почитайте у Джоэла статью "Труп прибитый гвоздями стене"


      1. netch80
        06.11.2021 10:53
        +1

        Можно точную ссылку? Сайт русских переводов сдох лет 5 назад, по-английски не гуглится.


        1. alan008
          06.11.2021 22:03

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


          https://www.ozon.ru/product/dzhoel-o-programmirovanii-141734878/?ectx=0&sh=0sVjyyOV


          1. netch80
            06.11.2021 23:16
            +1

            Оригиналы на английском вроде же доступны свободно? У неё на английском было иное название?


  1. SlFed
    05.11.2021 23:00

    А как вам вычисляемый GOTO в фортране ? Нет, если его использовать в стиле Switch-Case, то вроде норм. А если метки раскиданы по программе....


    1. Rigidus
      06.11.2021 07:12
      +3

      Я знаю вещи и похуже - оператор COME FROM


    1. Dorval
      06.11.2021 10:13

      В PL/1 есть переменные типа метка. Наверное из Cobol'a идею позаимствовали.


      1. netch80
        06.11.2021 10:31

        Я не следил, кто у кого заимствовал, но вот полезная классика тех времён. И вынужден в 4-й раз тут сказать — его явность тут оправдывает существование.


  1. Shatun
    05.11.2021 23:32
    +1

    On Error Resume Next (классический VB 6)

    Я могу ошибаться но это не дефолтное поведение, которое явно завдавалось в коде, примерно также как try-catch. Поэтому не совсем понимаю проблему-с таким же успехом можно ругать например пустые try-catch.


    1. sshikov
      06.11.2021 10:29
      +1

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


  1. robotikz
    05.11.2021 23:35

    в Lotusscript есть всё это, добро пожаловать динозавры ))))


  1. bodun666
    05.11.2021 23:38
    +6

    Выскажусь в защиту On Error Resume Next (хоть и с болью вспоминаю VB6). Это просто инструмент, который можно грамотно пользовать, а можно и в колено себе выстрелить... Вы почему то показываете странный (и безусловно опасный) механизм использования - задать эту директиву для процедуры. Обычно так не делалось. Эта конструкция использовалась явно в любом месте кода для того, чтоб следующая строка (или блок) не навернулась. К примеру имеем какую-то переменную String, ожидаем, что там число. Вот перед приведением к числу (что-то вроде CLng или как там его) и давали On Error Resume Next. Но только на тот кусок кода, где это нужно - в моем примере после приведения возвращаем нормальное поведение (On Error GoTo кудаТоТам), после чего спокойно проверяем значение и решаем, что делать дальше.


    1. sshikov
      06.11.2021 10:31

      >задать эту директиву для процедуры
      Если процедура написана в соответствии с принципами, и делает что-то ровно одно — то она как правило небольшая.

      >для того, чтоб следующая строка (или блок) не навернулась
      Я вам больше скажу — в документации написано, что ее следует использовать при доступе к объектам. Вот прямо так, конкретно. Но если документацию не читать — то конечно, будет опасно.


      1. bodun666
        06.11.2021 11:18

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

        следует использовать при доступе к объектам

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

        Если процедура написана в соответствии с принципами

        Ох, видели бы Вы те авгиевы конюшни из легаси... Уж сколько лет стараюсь забыть об этом - а вздрагиваю до сих пор :)


        1. sshikov
          06.11.2021 11:26
          +2

          >В принципе я это и имел ввиду
          Так я с вами и не спорю, а скорее подтверждаю.

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

          ON ERROR RESUME NEXT
          некая строка, обычно обращение к объекту
          IF… проверка наличия ошибки и обработка

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

          И да, я видел конюшни. У меня был проект по переписыванию 120 тыс строк VBA. Это то еще развлечение, скажу я вам.


  1. lunacyrcus
    05.11.2021 23:58
    +6

    Вообще-то On Error Resume Next тоже при применении с умом был очень эффективным и избавлял программиста от обработки тонн исключений, включая и тех что являются частью нормальной работы программы. Под его областью действия все равно производились проверки на ошибки где это было нужно (методы с получением кода ошибки и т.д, в конце-концов валидация результатов). Суть его была именно в том чтобы некритичные ошибки не выбивали всю работу программы и не заставляли прописывать обработку каждой из них (профит конечно в том что это повышало эту самую "продуктивность"), а грамотность использования как и во многих вещах тогда, уже зависела от человеческой прослойки между интерпретатором и кодом.

    DoEvents тоже был вполне хорошим решением на свое время, потому что в те времена городить многопоточность для большинства программ было абсолютно неуместно (из-за массовости однопоточных и одноядерных процессоров + ощутимых расходов на переключение потоков), а впридачу еще в общем-то очень опасно и дыряво (это тебе не ява, где не нужно понимать ничего чтобы просто взять и использовать кучку потоков, в те времена грамотно делать что-то многопоточное мог явно небольшой % программистов, вся синхронизация и т.п. велась вручную + еще и насколько помню, отлаживать многопоточное тогда было особо нечем, то сидеть и 1000 раз перепроверять сам код скорее.). Ну и более того, DoEvents вроде как было задумано для "безопасных" сред, которые по-умолчанию исключали сценарии того что оконные сообщения в перерыве наделают что-то конфликтующее (тот же VB и .NET). Хотя конечно снова ж прослойка могла что-угодно наделать при старании)

    Ну или как-то так. Автор в общем больше из пальца попытался высосать страшилки, но получилось явно так себе. Эти фичи скорее были все же "прогрессивными" по своим временам. Разница в том что тогда бородачи на них ругались и продолжали сидеть на своем С с ассемблерными вставками, а сейчас все жрут еще не такую дичь (тонна языков и фреймворков например с идиотскими названиями/синтаксисом и внутренней логикой наверно уровня бьющейся об стенку рыбки) и носятся с ней как с чем-то революционным. Вот уж скорее стоило бы про убогость современных написать наверно, типа 3 наиболее идиотских модных языка)


    1. Nubus
      06.11.2021 00:51
      +1

      Вообще-то On Error Resume Next тоже при применении с умом был очень эффективным и избавлял программиста от обработки тонн исключений, включая и тех что являются частью нормальной работы программы

      Приведите примеры пожалуйста. Потому что исключения при нормальной работе программы это ненормально.


      1. lunacyrcus
        06.11.2021 01:32

        Приведите примеры пожалуйста. Потому что исключения при нормальной работе программы это ненормально.

        За примерами далеко не надо ходить, даже вызов встроенных в язык методов оборачивается исключением, которое нужно обрабатывать. Как и вызов методов каких-либо сторонних классов. Это что касается VB/VB6. Без использования "On Error" (в любом виде, там еще были варианты вроде On Error Goto [line]), программа закрывалась после первого же такого исключения.

        Ну или в том же .NET с try/catch даже на каком-нибудь int.Parse() может вылететь если не был валидирован ввод до этого, тоже как бы норма. Аналогично в java/javascript. Это все очевидные примеры исключения при нормальной работе программы (вызваны например средой или там изменением состояния устройства). А еще собственные исключения (генерируемые самым кодом как способ передать результат).

        (ну в более низкоуровневых конечно да, допускать какие-нибудь IMA и их ловить и пропускать, это была бы смешная норма. хотя и такое бывало.)


      1. firehacker
        06.11.2021 02:47
        +3

        Типичный пример использования On Error Resume Next — это начинка обработчика события Resize формы, которое долго перемасштабировать начинку окна.


        Например, у вас окно, всё пространство которого должен занимать TextBox, за исключением рамки в, скажем, 5 пикселей.


        И вы пишите что-то вроде


           TB.Height = Me.ScaleHeight - Padding*2
           TB.Width = Me.ScaleHeight - Padding*2

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


        On Error Resume Next «улаживал» ситуацию. В случае try...catch-подхода пришлось бы каждую строку обрамить в try с пустым catch. Или вы можете сначала вычислять ширину/высоту, проверять, меньше ли она нуля, и если меньше, то не присваивать полунный результат свойству Height/Width, или же присваивать, но «клэмпнув» до нуля. Поздравляю, вы делаете двойную работу дважды: и кода у вас будет больше, и всё равно внутри set-хендлеров свойств Height/Width выше значение проверяет на <0, чтобы, в случае чего выкинуть исключение.


        То есть и вы делаете проверку, и ваши проверенные данные всё равно второй раз подвергнут проверке, чтобы в случае чего выкинуть исключения. Так может быть лучше, раз вам в руки дали механизм реагирования на исключения, воспользоваться и сказать: «просто проигнорируй это»?


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


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


        Прошу сейчас не передёргивать и не говорить, что при таком подходе может нарушиться бизнес-логика и пострадать данные. On Error Resume Next никогда не был дефолтным поведением. Это то, что программист сознательно выбирал или не выбирал для использования.


        В GUI-приложениях, где много какого кода отвечает за обновление UI, за визуальные знаки, всплывающие подсказки, звуки, перекраивание UI — очень много случаев, когда ошибка не фатальна, и пусть лучше в приложении контролы не отмасштабируются, чем оно упадёт.


        Если же вы предлагаете в случае ошибки выходить из процедуры, то, ещё раз напомнив, что VB не мешает вам поступать именно так, предложу такой пример: в окне формы 10 контроллов, которые надо масштабировать. Контроллы писал неизвестно кто, и каждый при попытки изменить его размеры может взбрыкнуть.


        У вас ест процедура с примерно таким кодом:


        ToolBar.Width = NewWidth
        LogPane.Width = NewWidth
        MainPanel.Width = NewWidth
        SecondarySlider.Width = NewWidth
        ...
        StatusBar.Width = NewWidth

        Итак, любой контрол может взбрыкнуть. Хотя бы, например, потому, что при изменеии размера контрол может пытаться переведить память под какой-нибудь backbuffer, и памяти внезапно может не оказаться.


        Так вот, что вы выбираете?
        1) Из-за облома в одном контроле (возможно облом был единократным), вся программа вылетает
        2) Из-за того, что один контрол взбрыкнул, при изменении размеров окна все контролы перестают менять свои размеры, потому что кто-то в коде написал On Error Goto exit_sub
        3) Только один косячный контрол перестаёт реагировать на изменение размеров окна, а остальные 9 ведут себя адекватно.


        Какой подход больше всего обрадовал бы пользователя? Обеспечивал бы сохранность данные? А какой взбесил бы больше всего?


        1. VXP
          06.11.2021 06:31
          -3

          4) Дать задание адекватному программисту переписать контролы


          1. firehacker
            06.11.2021 09:55
            +1

            Это не опция.


            Во-первых, контрол писали не вы. А другая фирма из другой страны, очень даже может быть. Контрол вам дали в форме .OCX-файла.


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


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


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


            1. GerrAlt
              06.11.2021 10:36
              -1

              То что вы сейчас предлагаете выглядит как попытка спрятать баг.

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


        1. GerrAlt
          06.11.2021 10:20
          -1

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

          Использовать On Error Resume Next в такой ситуации это как раз игнорировать что на самом деле произошло, а возможно там уже и все окно куда-то делось, и ошибка в самой попытке присваивания, а не в отридцательном результате.


          1. firehacker
            06.11.2021 13:50
            +1

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

            Такие контракты могут просто отсутствовать, именно так и было для большинства контролов во времена «золотого времени» VB. Если вы пишите контрол на том же VB, вы обычно вообще не имплементируете свойства Height/Width, имплементация как бы унаследована от базового класса UserControl.


            Но бог с ними, с контрактами.


            Перемасштабирование не удалось потому, что не хватило памяти. Что более умное можно сделать, кроме как игнорировать эту ошибку?


            Если вообще не обрабатывать её, программа вылетит.
            Если обрабатывать, то что делать?


            Ничего не делать — тот же On Error Resume Next.
            Выходить из процедуры — ломать ресайз всего остального UI из-за одного дефективного контрола.
            Выводить сообщение об ошибке — бредовейшая затея. Ресайз превращается в ад (смещение на каждый пиксель вызывает новое сообщение), а в условиях жесткой нехватки памяти попытка показать сообщение обернётся новым исключением/ошибкой, которую, как правило, никто не уже не будет ловить, а это вылет приложения.


            Вы подходите с позиции идеалистов, которые считают, что нужно всё покрыть тестами, что есть классный отдел Q&A, который все дефекты выявит, и пользователь получит классный продукт.


            Это недостижимая утопия. Те же ОС тестируют в хвост и в гриву, а критические уязвимости и ошибки всё равно появляются.


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


            мне кажется что в такой ситуации делают отдельную функцию, которая возвращает положительное значение или 0 (если язык не имеет встроенной функциональности для подобного,

            Есть IIf, что приближённо соответствует тернарному ?: в Си. С этой проверкой, как я уже написал, вы застрахуете себя от отрицательных координат, но если автору контрола вздумается, что минимальным размером контрола должно быть 40×80, а не 0×0, ваша страховка внезапно потеряет силу. Но даже если не вздумается, вы не застрахуете себя от Out of memory или Out of stack space. Реально невозможность подвинуть контрол должна быть поводом аварийного завершения программы и потери всех данных? Кроме того, реализация контрола всё равно будет проверять входные данные.


            и ошибка в самой попытке присваивания,

            Как понять «в самой попытке присвоения»? Присвоение такому свойству значения это вызов обычной процедуры вида


            HRESULT Xxxxxxx::put_Width(INT NewVal)
            {
               ...
            }


            1. GerrAlt
              07.11.2021 10:42
              +1

              Перемасштабирование не удалось потому, что не хватило памяти. Что более умное можно сделать, кроме как игнорировать эту ошибку?

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

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

              Это недостижимая утопия. Те же ОС тестируют в хвост и в гриву, а критические уязвимости и ошибки всё равно появляются.

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

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

              И всетаки мне кажется предпочтительнее явный вылет, который будет обнаружен на этапе тестирования, кмк в прод не должен ехать код который может иметь подобное поведение

              но если автору контрола вздумается, что минимальным размером контрола должно быть 40×80, а не 0×0, ваша страховка внезапно потеряет силу.

              Т.е. вы отправляете в рабочую среду код, который не понятно как работает, а чтобы тестировщики ничего не нашли прикрываете это On Error Resume Next, получается так?

              Как понять «в самой попытке присвоения»? Присвоение такому свойству значения это вызов обычной процедуры вида

              я имел ввиду что у вас что-то вроде NPE - вы пытаетесь вызвать присваивание для поля, когда у вас вместо объекта null (или вызвать метод у не существующего в момент исполнения объекта)


      1. Bronx
        08.11.2021 04:37

        Приведите примеры пожалуйста

        Попытаться распарсить строку, введённую пользователем, в число, а при ошибке парсинга (вполне ожидаемой) присвоить дефолтное значение.


        1. Nubus
          08.11.2021 05:19

          Эм, тут тогда условия if then или проверку ввода выполнять, а не On Error resume next делать.


          1. Bronx
            08.11.2021 05:28

            Проверку ввода делает собственно парсер. Только у него дизайн такой, что у него возвращаемое значение -- это распарсенное число, а не код ошибки или монада Either, потому что в VB "парсер" -- это просто приведение типов в Variant. А если распарсить и привести не выходит -- бросается исключение. Предлагаете перед парсером самому парсить, чтобы уберечь его от ошибок?

            On Error Resume Next как раз и позволяет поставить If Err <> 0 Then ... на следующей строке, а не улетать в хэндлер ошибки. Фактически, это получается аналог if (Int.TryParse(...)) { ... } в шарпе.


            1. mayorovp
              08.11.2021 11:30
              +1

              Выглядит как недостаток языка, а не как достоинство On Error Resume Next


              1. Bronx
                09.11.2021 01:58

                Я и не утверждаю, что это достоинство — это скорее workaround для случаев, когда "дружественный" интерфейс прозрачного приведения типов, рассчитанный на happy path, вдруг становится недружественным при отходе от happy path.


                Эта ситуация и в "правильных" языках с исключениями встречается — например, сеттеры свойств как правило кидают исключение при невалидных данных, и если нужно сделать "присвой либо входное значение, либо дефолтное" на множестве свойств объекта (скажем, конфиг читаемый из файла), а полноценного валидирующего конструктора/билдера не завезли, то каждую индивидуальную попытку присвоения проперти придётся обернуть в try..catch.


    1. Gryphon88
      06.11.2021 01:30

      Мне описанный сценарий напоминает использование Option/Maybe: проверяем исходные данные, на каком-то участке программы говорим, что скорость и ненарушение потока исполнения важнее раннего выхода, в конце участка делаем "разбраковку" выходных данных.


      1. lunacyrcus
        06.11.2021 02:01

        Да, похоже. Можно и без проверки входных данных)

        Но вообще конечно делалось обычно с промежуточными проверками, как минимум чтобы выводить не просто "Все пропало", а что-то конкретней.


      1. 0x1000000
        06.11.2021 09:50
        +2

        В Option/Maybe работа цепочки вызовов (при обнаружении ошибки) как раз прерывается и происходит ранний выход.


        1. tbl
          06.11.2021 16:44
          -1

          Option::map_or()
          Option::map_or_else()
          Option::unwrap_or()
          Option::unwrap_or_else()

          Позволяют продолжать выполнение


          1. 0xd34df00d
            08.11.2021 00:21
            +2

            Это по большому счёту проверка на то, лежит внутри Maybe Nothing или нет. Взять значение типа Maybe Int, в котором на самом деле Nothing, и притвориться, что там Just x, у вас не получится при всём желании.


  1. saboteur_kiev
    06.11.2021 00:37
    -1

    Ненавидимый многими оператор goto позволяет быстро и удобно выбраться из глубоко вложенной структуры

    Что-то не понял как это. Что значит "из глубоко вложенной структуры"?

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

    Все остальные примеры - точно такие же. Практически любую команду можно использовать настолько неправильно, что это нанесет вред и процессу и возможно системе. Так зачем выбирать какие-то отдельные и рассказывать как их использовать неправильно? Или данные примеры прямо являются таки общепринятыми и популярными? Сомневаюсь.

    Тот же On Error Resume Nextскорее всего изначально был задуман для использования во время отладки и написания ПО, когда еще не готов весь функционал, и нужно посмотреть как работает дальнейшая часть программы, игнорируя ранние ошибки, а не для использования в продакшене. Ну или если программист юзает в продакшене, то понятно что все риски он берет на себя.


    1. static_cast
      06.11.2021 01:19
      +2

      Что-то не понял как это. Что значит "из глубоко вложенной структуры"?

      Из вложенных областей видимости, которые образуются стандартными языковыми конструкциями, - циклами, условиями. Допустим, у нас есть трехмерный неупорядоченный массив с данными и нам надо найти ячейку по какому-то там условию. Соотвественно, есть три вложенных цикла, внутри проверка, и возникает вопрос, - как нам прекратить дальнейшие итерации, когда мынашли требуемое. Стандартное средство прерывания, - break, - дает выход на один уровень вверх. Чтобы выйти из нескольких, то приходится вводить дополнительные флаги выхода, выносить эти вычисления в лямбды или отдельные функции (и использовать в них уже return). А у некоторых чешутся руки поставить метку на верхнем уровне вложенности и сделать goto. В embedded C это может прокатить, но если подобное заметят коллеги на ревью в коде на каких-нибудь плюсах, то можно потом и не отмыться.


      1. Tsvetik
        07.11.2021 15:37
        +2

        Очень удобно с помощью goto освобождать память после серии malloc-ов


    1. Akon32
      06.11.2021 01:23
      +1

      Что-то не понял как это. Что значит "из глубоко вложенной структуры"?

      Вложенные циклы, например. На Java есть операторы break/continue с меткой, на C это нужно делать через goto:

      outer: 
      for(...) {
        for(...){
          break outer;
        } 
      } 
      for(...) {
        for(...) {
          goto exit_outer;
        }
      }
      exit_outer:
      ... 

      Это минимальные примеры, уровней вложенности может быть намного больше 2х.

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


      1. transcengopher
        08.11.2021 21:57
        +1

        Мало кто об этом говорит, но в Java так именовать можно любой блок — и, соответственно, выходить так из любого блока — из if {}, else{}, try {}:


        public int doSomething(String userInput, int defaultValue) {
            attempt: try {
                var value = Integer.parseInt(userInput);
                if (value == 0) {
                    break attempt;
                }
                ...
            }
            finally {
              LOG.debug("Finished doing something");
            }
            return defaultValue;
        }


      1. saboteur_kiev
        14.11.2021 01:04

        Вложенные циклы, например. На Java есть операторы break/continue с меткой, на C это нужно делать через goto:

        Так на джава есть, например, garbage collector, а если на С вы делаете какое-то действие вроде аллокации памяти под данные, а потом при выходе из цикла это чистите, то goto это прямые утечки памяти.


        1. netch80
          18.11.2021 10:53

          > то goto это прямые утечки памяти.

          Если их явно не отрабатывать. Это громоздко, но принципиальных проблем нет.


    1. tyomitch
      06.11.2021 01:26
      +1

      Тот же On Error Resume Next скорее всего изначально был задуман для использования во время отладки и написания ПО, когда еще не готов весь функционал, и нужно посмотреть как работает дальнейшая часть программы, игнорируя ранние ошибки, а не для использования в продакшене. Ну или если программист юзает в продакшене, то понятно что все риски он берет на себя.

      Пример из официальной справки:
       On Error Resume Next ' Defer error trapping. 
       ObjectRef = GetObject("MyWord.Basic") ' Try to start nonexistent object
       ' Check for likely Automation errors. 
       If Err.Number = 440 Or Err.Number = 432 Then 
           ' Tell user what happened. Then clear the Err object. 
           Msg = "There was an error attempting to open the Automation object!" 
           MsgBox Msg, , "Deferred Error Test" 
           Err.Clear
       End If 
      

      Т.е. это просто способ вставить (или забыть вставить) обработку ошибок в том же месте процедуры, а не отдельным блоком где-то далеко.


      1. netch80
        06.11.2021 10:40
        +1

        > On Error Resume Next ' Defer error trapping.

        Вообще-то выглядит откровенно некорректно, потому что в результате err и erl оказываются хранителями _последней_ случившейся ошибки, которая может быть ни о чём, потому что индуцирована кривыми данными и прочими нарушениями — а не первой, которая это всё вызвала. Не должно такое продолжаться, ошибка должна быть или обработана снаружи, или замолчена каждая индивидуально, с явным обходом с необходимыми действиями (типа подставить умолчание).

        По сравнению с этим — неудивительно, что try-catch начал побеждать на многие годы (несмотря на многословность).


    1. kmeaw
      06.11.2021 01:29
      +3

      Что значит "из глубоко вложенной структуры"?

      Из глубоко вложенного цикла, например. В языке может не быть конструкции "break label".

      Из любой функции, …, выйти можно через return.

      Но в языках без RAII и сборки мусора нужно будет перед return освободить все ранее выделенные ресурсы. Если не использовать goto, то придётся копировать код освобождения перед каждым return и помнить исправлять все эти места, когда набор выделяемых ресурсов меняется.

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

      А можно ограничить себя в "плохом" использовании goto: не покидать границы функции, прыгать только вперёд, делать это только в исключительных ситуациях.


    1. lunacyrcus
      06.11.2021 02:07

      А если выйти через goto, то это путь в stackoverflow, и я не про вебсайт. В общем с этой фразой я не согласен.

      Это такое отличие еще между тем где и как goto работал. Если в С выйти goto то да, будет и stackoverflow и утечка памяти да и что-угодно еще, смотря куда выйти. Именно в С это реально опасная штука которой многие себе поостреливали ноги и остальное.

      А в VB иначе работало все же, там интерпретатор позволял выходить так из любой (или почти любой) задницы "вполне законно".


      1. Dikoy
        06.11.2021 07:07

        В Си и по указателю можно выйти в неведомые дали, особенно имея талант.


      1. firehacker
        06.11.2021 14:09
        +1

        А в VB иначе работало все же, там интерпретатор позволял выходить так из любой (или почти любой) задницы "вполне законно".

        Я вас умоляю. Читайте вот этот коммент до просветления:
        https://habr.com/ru/post/582566/#comment_23578554


        1. lunacyrcus
          06.11.2021 15:23

          Ну подробный разбор конечно. Даже момент с компиляцией в натив со своими особенностями и присутствовавшими оптимизациями упомянут.

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


          1. firehacker
            06.11.2021 15:45
            +2

            Источники

            • Першиков В. И., Савинков В. М. Толковый словарь по информатике / Рецензенты: канд. физ.-мат. наук А. С. Марков и д-р физ.-мат. наук И. В. Поттосин. — М.: Финансы и статистика, 1991. — 543 с. — 50 000 экз. — ISBN 5-279-00367-0.
            • Борковский А. Б. Англо-русский словарь по программированию и информатике (с толкованиями). — М.: Русский язык, 1990. — 335 с. — 50 050 (доп.) экз. — ISBN 5-200-01169-3.
            • Толковый словарь по вычислительным системам = Dictionary of Computing / Под ред. В. Иллингуорта и др.: Пер. с англ. А. К. Белоцкого и др.; Под ред. Е. К. Масловского. — М.: Машиностроение, 1990. — 560 с. — 70 000 (доп.) экз. — ISBN 5-217-00617-X (СССР), ISBN 0-19-853913-4 (Великобритания).


            1. lunacyrcus
              06.11.2021 16:08
              +1

              Плохо читали — ничего там не упущено. Оно скрыто под спойлером «Немного подробностей о том, как происходит компиляция в Native-код», внутри которого есть свой под-спойлер «Интересный вопрос касательно IL на входе бэкенда C2».

              Да, уже обновил свой коммент 100 раз по мере того как пересмотрел те все)

              Ну а на счет терминов если докапываться, то наверно наиболее уместно будет все же "нтерпретация байт-кода" для режима P-CODE, потому что в таком режиме библиотека машины все равно интерпретирует его по ходу выполнения в отличие от режима компиляции в натив, когда на выходе уже машинный код. Еще сама компиляция в натив появилась далеко не сразу, в какой-то из более новых версий.

              А то что VB и его среда были тогда явно недооценены это очевидно, как и логично предположить что поначалу о нем судили вообще на уровне ложных догадок и стереотипного мышления, не зная как на самом деле все внутри. Но тем кто этим не страдал и мог оценить объективно, ясно было насколько он продуман и превосходит существовавшее тогда (ну и отлично применим на прикладном уровне, явно не универсален из-за ограничений). Ну в общем даже когда уже списали его, первые версии .NET и его IDE сильно отставали в этих всех фичах и особенностях и внутреннем устройстве, не говоря уже про другие языки.


              1. firehacker
                06.11.2021 16:21

                "нтерпретация байт-кода" для режима P-CODE, потому что в таком режиме библиотека машины все равно интерпретирует его по ходу выполнения


                Тогда скомпилированные Java-программы работают путём интерпретации Java-байкода, дотнетовские программы работают путём интерпретации MSIL-байткода. Да и процессоры интерпретируют машинный код.


                отому что в таком режиме библиотека машины все равно интерпретирует его по ходу выполнения в отличие от режима компиляции в натив,

                Можно было бы говорить «в отличие от режима компиляции в Native», если бы суть обработки P-кодных инструкций заключалась бы в том, что виртуальная машина P-код переписывала бы в Native-код (генерируя для каждой пи-кодной инструкции дюжину машинных), а потом отдавала бы сгенерированный Native-код на выполнению процессору.


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


                Приведённое выше академическое определение понятия «интерпретор» настаивает на том, что входными данными для него является исходный текст программы, а обработка происходит построчно.


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


                В теории можно было бы вообще создать архитектуру процессора, которая бы машинно умела исполнять VB-шный P-код. Ну какая же это интерпретация?


                1. lunacyrcus
                  06.11.2021 16:43

                  В теории можно было бы вообще создать архитектуру процессора, которая бы машинно умела исполнять VB-шный P-код. Ну какая же это интерпретация?

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

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

                  Ну в общем такое себе, можно конечно спорить о терминах но как не называй в общем-то разницы 0. Я раньше вообще все эти слова даже не знал и просто по-своему все называл как придумывалось, суть-то та же остается.


          1. firehacker
            06.11.2021 16:03
            +3

            Я смотрю, пока я писал, вы перекроили свой коммент.


            или например нативные вызовы winapi которые после перехода останутся незакрытыми.

            Как понять «незакрытый вызов WinAPI»? Вызовы WinAPI были не хаком, а штатной возможностью, точнее даже были осуществимы через два разных механизма: через Declare Sub/Function и через TLB.


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

            Процедуры генерировались в чистейший машинный код.


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


            Реализация виртуальной машины VB-шного P-кода жила в MSVBVMxx.DLL (что и давало название этой библиотеки), но помимо виртуальной машины значительную часть библиотеки составляло то, что можно назвать «стандартной библиотекой» по аналогии с языками Си/Си++. Т.е. это просто различные «встроенные функции», заявленные как «часть языка», а также служебные функции (в случае C++ примером такой функции может стать _purecall). Это обычные процедуры в виде машинного кода, они к виртуальной машине отношения не имеют.


            Так вот, то, что генерировалось в режиме создания Native-кодных исполняемых файлов, представляло собой обычный машинный код x86 и не с вкраплениями P-кодных кусочков, а с вкраплениями вызовов служебны функций для выполнения тех задач, которые приходится очень часто решать и которые раздули бы объём кода, если бы эти задачи инлайнились.


            Например — копирование структур. Копирование структур можно было бы «инлайнить», сгенерировав код для копирования каждого члена структуры (в общем случае структуру нельзя копировать банальными memcpy, ведь там могут быть указатели на строки или COM-интерфейсы, массивы — и копировать это надо по умному). Вместо генерировалась одна единственная call-инструкция, вызывающая функцию рантайма, которая брала на себя все заботы по правильному копированию структуры. Ей передавался некий дескриптор структуры, предопределявший правила копирования.


            Или же после каждого вызова метода COM-интерфейса (читай «метода вызова любого VB-объекта или любого внешнего COM-объекта»), который мог выбросить ошибку путём возврата HRESULT, ставился вызов __vbaHresultCheck, который брал на себя всю работу по «транслированию» кода сбоя (HRESULT) в VB-ошибку (VB использовал SEH-исключения для работы своего механизма ошибок).


            1. lunacyrcus
              06.11.2021 16:32
              +1

              Как понять «незакрытый вызов WinAPI»? Вызовы WinAPI были не хаком, а штатной возможностью, точнее даже были осуществимы через два разных механизма: через Declare Sub/Function и через TLB.

              Банальные утечки памяти или незакрытые handle. Один из сценариев когда goto мог создать проблемы несмотря на безопасную среду. То же касается и использования любых внешних компонентов с которыми встроенный сборщик мусора ничего сделать не мог. Ну и к этому относится и использование "хаков" вроде прямых вызовов управляющих функций вирт. машины чтобы что-то там оптимизировать, все это один и тот же сценарий проблем с goto по сути. Если же ничего такого не использовать, то проблем с ним не могло быть by design. Речь у меня в этом контекста примерно.

              Так вот, то, что генерировалось в режиме создания Native-кодных исполняемых файлов, представляло собой обычный машинный код x86 и не с вкраплениями P-кодных кусочков, а с вкраплениями вызовов служебны функций для выполнения тех задач, которые приходится очень часто решать и которые раздули бы объём кода, если бы эти задачи инлайнились.

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


              1. firehacker
                06.11.2021 19:33
                +2

                Банальные утечки памяти или незакрытые handle. Один из сценариев когда goto мог создать проблемы несмотря на безопасную среду.

                Ну так и файл, открытый родными средствами средствами VB, не будет закрыт при выходе из процедеры. И оконо, показанное из процедуры, тоже не будет закрыто.


                А если обернуть в класс хоть получение хендла через WinAPI, хоть открытие файла родными средствами — тогда экземпляр класса будет правильным образом уничтожен (если других ссылок нет).


                В общем, деланье чего-то через WinAPI тут приведено зря, потому что в связанных с этим проблемах оно ничем не отличается от октрытия файлов родными средствами.


                Ну и к этому относится и использование "хаков" вроде прямых вызовов управляющих функций вирт. машины чтобы что-то там оптимизировать, все это один и тот же сценарий проблем с goto по сути.

                Что за управляющие вызов виртуальной машины? Имплементации P-кодных инструкций не экспортируются — вызвать их нельзя. Да и функциями эти имплементации не являются.


                Из управляющих можно назвать разве что ProcCallEngline/ProcMethEngline — эдакие гейты между native-кодом и P-кодом — позволяют вызывать P-код из native-среды.


                Но не представляю, каким образом их использование совместно с goto может нести какие-то проблемы.


                1. lunacyrcus
                  06.11.2021 21:54

                  Что за управляющие вызов виртуальной машины? Имплементации P-кодных инструкций не экспортируются — вызвать их нельзя. Да и функциями эти имплементации не являются.

                  Я здесь имел ввиду любую из около 2000 функций что экспортирует msvbvm60.dll (6 версия). Многие из них вызываются в процессе работы программы.

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

                  Вот какой-то пример по-быстрому найденный в единственном коде VB6 что у меня еще завалялся с более-менее актуальных и не портированных.

                  Call CopyMemory(ByVal VarPtr(byteKey(fID)), fID, 1&)

                  VarPtr/StrPtr это такая себе "классика" и прям необходимый минимум был, ну и многие другие так можно было костылять типа GetMem или как-то так (не помню уже сходу, вообще давно в VB не бывал). Но там много их и не только тривиальные такие.

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

                  // ну на счет goto то да, я упомянул еще что все сценарии его опасности похожи по своей сути


  1. elektroschwein
    06.11.2021 01:18
    +10

    На тему On Error Resume Next вспоминается, как когда-то давно тут на Хабре один безо утверждал что Delphi гораздо лучше чем C++ потому что на нём можно поймать Access Violation (SIGSEGV/SIGILL) и продолжить выполнение рад дальше, и благодаря этому программа будет способна работать даже на машине с глючащими плашками памяти (!). Попытки объяснить товарищу, что если у нас случился access violation из-за повреждения памяти, то это вне зависимости от языка означает что мы уже в полной заднице и гарантировать корректную работу программы после этого невозможно в принципе, ни к чему не привели...


    1. quwy
      06.11.2021 02:41
      +3

      программа будет способна работать даже на машине с глючащими плашками памяти

      Какая-то деформация от работы в нищей госконторе с разбитыми хламом вместо компьютеров.


      1. Ivan22
        06.11.2021 03:02
        +3

        ну или программы для луноходов писал.... а ля - "умри но не сдавайся"


        1. unsignedchar
          06.11.2021 22:05

          программы для луноходов писал

          На Delphi, ага ;)


          1. quwy
            07.11.2021 01:34
            +2

            Ну, хоть и не луноход, но промышленного робота на Delphi вполне себе программировал, ничего особенного.

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


    1. HemulGM
      06.11.2021 23:00
      -1

      Вы немного ошибаетесь, уважаемый. AV, по большей части - это попытка обращения к объект уничтоженному или не созданному. Обращение к несуществующему объекту не портит память, а значит работоспособность программы не под угрозой.

      Т.е. обратившись к объекту и отловив эту ошибку мы можем смело продолжать работать, естественно, понимая все последствия выполнения текущего блока кода.

      Я ни в коем случае не защищаю того человека, однако нужно всё же понимать всю суть прежде чем делать подобные выводы.


      1. quwy
        07.11.2021 01:47
        +1

        AV, по большей части - это попытка обращения к объект уничтоженному или не созданному

        По большей части -- да, но это ведь не единственная причина. Особенно, если речь идет не о пионерском коде, а о работе профессионального разработчика. Банальный buffer overflow и AV скорее всего сгенерит, и кучу попортит.


      1. elektroschwein
        07.11.2021 13:54
        +1

        Нет, я как раз таки хорошо все понимаю.

        Потому что "обращение к объекту уничтоженному или не созданному" очень даже может произойти, когда изначально у нас в какой-то ячейке памяти лежал валидный указатель на валидный созданный и не-уничтоженный объект, вот только либо из-за какого-нибудь уже упомянутого тут buffer overflow, из-за использования в другом месте неинициализированной переменной, из-за сбоя модуля памяти (о чем и говорил герой комментария, между прочим), его значение изменилось, стало некорректным, и при обращении к нему мы получили access violation. И это уже означает, что что-то пошло совсем не так, и весьма вероятно что наш указатель будет далеко не единственной испорченной вещью в памяти, и к чему это может привести одному Рандому известно.


      1. Akon32
        08.11.2021 00:27
        +1

        Обращение к несуществующему объекту не портит память, а значит работоспособность программы не под угрозой.

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

        Пример:

        Допустим, есть указатель *a на элемент связного списка A. Мы удаляем A и создаём какую-то другую структуру данных, например таблицу на основе сбалансированого дерева В. Внезапно оказывается, что какая-то часть B, например указатель на первый элемент left_child некоторого узла, размещён по указателю *a.

        Предположим, что мы пишем в *a что-то. На самом деле это "что-то" уходит в left_child, и дерево незаметно портится - прочитать уже его нельзя. Данные потеряны.

        Предположим, что мы читаем из *a, ожидая увидеть, очевидно, элемент списка A. На самом деле данные считывается указатель на узел дерева, который бессмысленно интерпретировать как элемент списка. Данные искажены.

        А дальше проблемы нарастают. Мы можем случайно испортить указатель на функцию. Мы можем считать не тот указатель на деструктор. Мы можем попортить структуры, например, менеджера памяти процесса, и что произойдёт дальше, предсказать трудно.

        И только часть таких проблем будет отловлена AV. Возможно, AV посыплются чуть позже, когда данные уже сильно повреждены, и возможно были записаны в файл или в БД в повреждённом виде.


        1. HemulGM
          08.11.2021 00:53

          В вашем примере АВ не возникло и успешно записались данные по ложному указателю. Но вы также считаете, что в программе всё ок. Разве нет?

          АВ не позволит записать данные по ложному указателю молча, а следовательно не испортит память.

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


          1. Akon32
            08.11.2021 01:43
            +1

            Но вы также считаете, что в программе всё ок. Разве нет?

            Я не считаю, что в программе всё ок. Видимость, что всё ок - да (особенно если не проверять целостность структур).

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

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


          1. quwy
            08.11.2021 02:07
            +1

            АВ - это отловленная ошибка, которая может сказать гарантирует, что память ещё цела.

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


  1. tyomitch
    06.11.2021 01:25

    (промашка)


  1. third112
    06.11.2021 01:55
    +1

    Вот уж скорее стоило бы про убогость современных написать наверно, типа 3 наиболее идиотских модных языка)

    ИМХО интересная идея. Напишите!


    При этом я не за goto, т.к. в середине 1980х мне пришлось разбирать ассемблерный листинг (бумажный) в 10 000 строк для нестандартной графической станции ЕС ЭВМ.


    1. lunacyrcus
      06.11.2021 02:42

      При этом я не за goto, т.к. в середине 1980х мне пришлось разбирать ассемблерный листинг (бумажный) в 10 000 строк для нестандартной графической станции ЕС ЭВМ.

      Ну конечно интересно. Сложно даже представить сколько за этот период видели уже недовольств на счет "современных" языков)

      Сам не напишу все же, я в этом плане не очень модный и явно лишний раз лень даже качать что-то вот такое очередное с сомнительным названием.


    1. sshikov
      06.11.2021 15:31
      +2

      Так 10000 это же некрупная программа, вообще-то? Даже по тем временам. Я писал десятки тысяч на ассемблере S/360 сам, вообще никаких проблем это не приносит, так же как скажем чтение исходников VM/SP, которых тоже довольно много. Не знаю, что у вас там за ассемблер, но штатный S/360 позволял на уровне макросов реализовать например вложенные конструкции типа if/then/else, do/while, и т.п., и вполне себе обходиться без goto. Сам по себе ассемблер — далеко не приговор.


      1. third112
        06.11.2021 22:21

        Согласен:


        и вполне себе обходиться без goto

        без goto — "далеко не приговор".


        1. third112
          06.11.2021 22:33

          PS Знаю и люблю ассемблеры PDP-11, Macintosh Classic, IBM PC AT, OS 360/370, Pentium 4, но без излишних goto.


        1. sshikov
          06.11.2021 22:41
          +1

          >без goto — «далеко не приговор».
          Ну, я бы так бы сказал — goto это инструмент, так же как и язык в целом. И если вам для вашей задачи удобно будет его применить — то такое решение вполне возможно будет верным. Скажем, если вам нужно реализовать FSM, то реализовать стрелки графа как goto — вполне себе решение (утрирую, но думаю смысл понятен). Особенно если это решение будет сгенерировано.


          1. third112
            06.11.2021 22:45

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


            1. sshikov
              06.11.2021 23:05
              +1

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

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

              Я это к тому, что любое блюдо надо уметь готовить, а то переваришь :)


              1. third112
                07.11.2021 01:07

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


            1. netch80
              06.11.2021 23:14

              Нет такого закона (даже статистически).


              1. third112
                07.11.2021 01:08

                Есть такой закон — базовые принципы топологии (см. выше).


                1. netch80
                  07.11.2021 01:23

                  Почему не соотношение неопределённостей Гейзенберга?
                  По-моему, оно и то ближе к обсуждаемой теме.


                  1. third112
                    07.11.2021 02:11

                    На строке M в проге стоит метка, на строке N — переход на эту метку. Где здесь неопределённость? — Всё однозначно определено.


                    1. netch80
                      07.11.2021 10:43

                      > Всё однозначно определено.

                      Да, и безо всяких топологических проблем, если их явно не вводить.

                      Правила типа: переход назад только на начало тела цикла, иначе только вперёд — и ваши зубы становятся белыми и пушистыми ™.


                      1. third112
                        07.11.2021 20:19

                        Как эти правила разрешат проблемы, если в программе 10 000 строк и 100 переходов?


          1. Gryphon88
            07.11.2021 02:01

            Скажем, если вам нужно реализовать FSM, то реализовать стрелки графа как goto — вполне себе решение 

            Обычно их всё-таки декорируют, как-то так (отсюда):

            #define FSM            for(;;)
            #define STATE(x)       x##_s 
            #define NEXTSTATE(x)   goto x##_s
            
            FSM {
                STATE(s1):
                  ... do stuff ...
                  NEXTSTATE(s2);
            
                STATE(s2):
                  ... do stuff ...
                  if (k<0) NEXTSTATE(s2); 
                  /* fallthrough as the switch() cases */
            
                STATE(s3):
                  ... final stuff ...
                  break;  /* Exit from the FSM */
             } 

            Ну и Дийкстра имел в виду не сишный goto с весьма скромным scope, а предположительно goto в фортране или алголе, который может указывать почти в любое место в программе.


            1. sshikov
              07.11.2021 09:46
              +2

              >Обычно их всё-таки декорируют
              Так я не настаиваю на конкретно таком решении. Наверное декорируют — потому что с переходами связаны какие-то действия, то есть код тут не goto в буквальном смысле.

              Я имею в виду, что если у вас goto логично получается из какой-то модели, например FSM (и в идеале не строится руками, а генерируется из другого представления) — то у этого кода есть вполне определенная понятная структура. Ну т.е. тут goto как раз не «может указывать почти в любое место в программе».

              Дийкстра, насколько я помню, критиковал goto за сложность понимания и построения выводов о коде, и предлагал взамен структурное программирование, где у куска кода есть один вход и один выход, а куски эти — последовательность, if, цикл. В принципе, вполне можно себе представить и более сложные варианты блоков, при условии что они будут как-то компоноваться в единое целое.


      1. VM390
        11.11.2021 13:47

        VM/SP - это в те времена уже S/370 или S/390. В остальном все верно, десятки и сотни тысяч строк кода IBM - читается очень легко и быстро (подозреваю, что это легкость достигается за счет того, что исходный код IBM OS VM/SP написан на PL/S) и во-многом благодаря развитым макросам. Даже название IBM ассемблера было заменено на HLASM (High Level ASM).


  1. kai3341
    06.11.2021 02:48
    +2

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

    Лучше сначала код с умом писать. Глубоко вложенная структура = высокая сложность кода. Автор предлагает приправить его ещё и лапшой goto. Как решать: вынести эту структуру в отдельную функцию (или класс), сильно сократив уровень вложенности, и заменить goto на return, имплементировав тем самым early return pattern


    1. netch80
      06.11.2021 10:47
      +4

      > Как решать: вынести эту структуру в отдельную функцию (или класс), сильно сократив уровень вложенности,

      Не везде было возможно/удобно/эффективно. Компиляторы C/C++, например, массово доросли до поддержки правильного инлайнинга в этом случае только в 2000-х, и в inline сложно переносить много параметров. Лямбды без замыканий, которые позволили писать такие вложенные функции, в C++ появились только в 11-м стандарте. В C их до сих пор нет (расширение GCC не считаем). В других языках — по обстановке, но в большинстве вроде тоже нет эффективной реализации.

      > приправить его ещё и лапшой goto

      _Лапши_ как раз нет, это у вас полемическое преувеличение. Выход по goto в позицию сразу за концом цикла читается беспроблемно.
      Во многих стандартах кодирования с C допускается переход по goto только вперёд. Это избавляет от «лапши», одновременно допуская подобные приёмы.

      Конечно, лучше было бы как в Perl и Java — метка на операторе цикла и break/next на неё — но, увы, не везде завезли.


  1. Dikoy
    06.11.2021 07:03

    В защиту ALTER - это вполне стандартная фича однозадачного программирования, пришедшая из ассемблера (где хождение по адресам - вообще базовая функция ИМХО). В умелых руках позволяет ОЧЕНЬ сильно экономить объёмы и производительность. Просто не стоит смешивать методы из разных систем в одну кучу.

    Собственно, ассемблерный листинг любого if/else/switch и есть "ненавистный goto", только реализованный компилятором.


    1. VM390
      09.11.2021 18:46
      +1

      Абсолютно верное возражение! В стародавние времена мы экономили и размер программы и время выполнения кода. Люди тогда знали и ассемблер и кобол, соответственно, идеи оптимизации плавно перекочевывали из ассемблера в COBOL/PLI/FORTRAN/LISP и т.д. Экономия памяти была очень значительной, при этом код также ускорялся, а единственным минусом была потеря реентерабельности.


      1. Dikoy
        10.11.2021 18:40

        И до сих пор огромное количество железа программируется без ОС и нет вообще никакой проблемы динамически менять себе вектора перехода, адреса выхода из прерывания, состояние сохранённых регистров и пр. Просто в одних языках это возможно встроенными способами, в других - через гланды или ассемблерные вставки. А какие возможности открываются на P-SOC-ах!!!!111!!

        Но на хабре опасно такое писать, минусят... )))))


        1. VM390
          10.11.2021 23:32

          Неужели Вы серьезно относитесь к аудитории Хабра? Нет, 10-15%% здесь профи, подстраиваться под остальных - так себе занятие. Я вот давно жду статью с условным названием: "ООП - враг серьезных программных проектов. Функциональное программирование - пусть не лучший, но выход из тупика ООП". Очень хочется изучить поведение хабровцев, как реакцию на эту статью.


  1. andreyverbin
    06.11.2021 07:40

    Про ALTER я не понял, чем оно принципиально отличается от

    let func = path1;
    if (wsX == 1) func = path2 
    else func = path3;
    func();

    ?


    1. Dikoy
      06.11.2021 07:45
      -2

      Так и я о том же, тот же листинг на ассме будет весь на джампах:

      jmp @хххх


      1. Dikoy
        06.11.2021 14:07
        -6

        Гы, минуснувший явно не знает что такое метка директивы JMP с точки зрения адресного пространства, где находится и как её можно динамически менять. А есть же ещё и RET ))))


        1. Dikoy
          11.11.2021 00:47

          Просто оставлю это здесь, чтобы не повторяться https://habr.com/ru/company/alfa/blog/587536/comments/#comment_23672460


    1. netch80
      06.11.2021 10:50
      +3

      > Про ALTER я не понял, чем оно принципиально отличается от

      В вашем примере явно видно, что func это указатель, надо только прочитать код.
      С alter goto каждый goto может оказаться изменённым. При том, что это используется в менее чем 1% кода, читателю следует опасаться каждый раз, где оно того не стоит. Подобное притупляет бдительность и в результате пропускается реальная опасность.
      Если бы те goto, которые можно менять, требовали явной пометки alterable, жалоб было бы в разы меньше.


      1. aamonster
        07.11.2021 15:02
        +1

        Очень напоминает ситуацию с [[fallthrough]] в C++. Сколько (десятков) лет прошло до его появления?


        1. netch80
          07.11.2021 18:01

          Switch менее проблемен IMHO: локализован в пространстве кода, надо помнить про диверсию. А грепать в исходнике на много тысяч строк «а для этой секции есть alter goto?» — заморочнее в разы.

          Исправлять, да, надо. В новых языках таки это лечат…


          1. aamonster
            07.11.2021 18:26

            Исправлять уже нет смысла, надо переписывать с Кобола на другие языки :-)

            Исследование alter goto имеет смысл только в историческом аспекте – почему так было сделано? Почему это терпели?

            С сишным switch всё хуже, кодовую базу так просто не похоронишь.


  1. shornikov
    06.11.2021 10:09

    Как я помню - VB нет (типа того) возможности проверить существование переменной. И единственный способ - это сделать ф-ию isExist(переменная)

    on error resume next
    //проводим операции над переменной и возвращаем true
    //или падаем от ее отсутствия, но из за on error - не падаем а переходим ниже
    //return false


    1. netch80
      06.11.2021 10:51
      +1

      Это можно сделать через on error goto <строка> с таким же успехом и не оставляя возможность пропустить ещё какую-то проблему.


      1. Emulyator
        06.11.2021 14:09
        +1

        А как переход по goto позволит не пропустить еще какую-то проблему? Он просто позволит обработать ошибку в другом куске кода, а что мешает её обработать сразу, без скачков, и в дальнейшем отключить игнорирование ошибок? Во многих случаях последовательный код будет нагляднее.


        1. tyomitch
          06.11.2021 14:55

          Когда после On Error Resume Next происходит вторая ошибка, то управление переходит к следующей строке — как и после первой. Таким образом, ошибка в обработчике ошибок рискует остаться незамеченной.

          Когда после On Error GoTo ... и до Resume или выхода из процедуры происходит вторая ошибка, то VB вызывает обработчик выше по стеку, а если такого нет — то остановку программы. В более старых бейсиках ошибка в обработчике ошибок приводила к безусловной остановке программы.


          1. Emulyator
            06.11.2021 18:18
            -2

            Все так, но положа руку на сердце, признаем, что количество кода между "On Error Resume Next" и возвратом к обработке ошибок "On Error GoTo err_label" или отказом от неё "On Error GoTo 0" определяется только опытностью разработчика. Опытный ограничится парой стандартных строк проверки, в которых ошибку ожидать глупо, неопытный просто не воспользуется ни одним из вариантов. Нужно ли за это гнобить "On Error Resume Next"?


    1. tyomitch
      06.11.2021 13:11

      Нет, вы не можете передать несуществующую переменную в функцию: ошибка будет в строчке вызова, а не внутри функции.

      Option Explicit справляется с обнаружением необъявленных переменных гораздо лучше.


      1. Emulyator
        06.11.2021 14:36
        +1

        Как ни странно, в коллекциях VBA нет встроенной проверки наличия элемента по ключу (раньше по крайне мере так было), и народ выкручивается обработкой ошибок. Может это припомнил @shornikov?


  1. Myclass
    06.11.2021 11:28
    +2

    У большинства людей ножи дома тупые, незамеченные. Это ничего не говорит о том, что нож - отстой. Нет, их используют и ухаживают за ними не так, как это надо-бы. Но для большинства это норма. Если многие из людей верят в свои возможности при например покраски чего либо, получают пии этом сходительный результат, сами обмазаны краской, вокруг себя мноие вещи попортили - это же проблема не в краске и кисточке. Как проф. Преображенский сказал -разруха в головах.

    Так и с языком. С какими инструментами большинство тех, кто программирует и превращает код в говнокод - не важно. Это уже 'талант' этих программистов.

    И в говнокоде таких программистов всё, не только goto используется неправильно, но и циклы, и объявление переменных, и постройка функции или классов. Goto- это одна из тысячи вещей, которыми плохие программисты не умеют пользоваться.

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


  1. vvbob
    06.11.2021 12:20
    +2

    On Error Resume Next

    Не полный аналог, но что-то похожее в Java:

    try{
    ...
    } catch (Exception e){
    }

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


  1. KvanTTT
    06.11.2021 14:15
    -1

    On Error Resume Next — оператор превращения языка в JavaScript.


    1. firehacker
      06.11.2021 14:22
      +2

      Почему не в «Си или C++ в стиле Microsoft»?


      При вызове WinAPI вы обязаны проверять GetLastError cами.
      При вызове COM-методов вы обязаны проверять HRESULT сами.


      1. tyomitch
        06.11.2021 14:58
        +2

        Почему не в стиле POSIX? Возвращаемые значения и/или errno тоже нужно проверять самому.


        1. firehacker
          06.11.2021 15:11
          -1

          Как это элегантно сформулировать?


          «В Си в стиле WinAPI или POSIX или подавляющего большинства паттернов обработки ошибок в Си или С++ в стилке Microsoft»?


          Что характерно, ядро Windows написано на Си, но там очень много где (если IRQL позволяет) используется расширение try...except для обуздания SEH.


      1. quwy
        06.11.2021 15:16
        +1

        И при вызове POSIX API вы обязаны проверять errno сами. Так что MS тут не при чем.

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


        1. firehacker
          06.11.2021 16:50

          Кто говорил о кросс-процессности?


          1. quwy
            07.11.2021 01:23

            Передача чего-либо из ядра в пользовательскую программу подразумевает кросс-процессность или ее близкий аналог.

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


            1. firehacker
              07.11.2021 04:25
              +1

              Речь шла совершенно о другом.
              Недавно тут была статья «Как понять, чо вы пишите не на C++, а на Си с классами».


              Так вот, если вы пореверсите продукты MS, написанные на C++, да хоть тот же обсуждаемый здесь VB6, то откроете для себя, что внутри MS для своих же продуктов С++ используется как «Си с классами».


              Они не используют STL или сишный механизм исключений внутри своих продуктов. Не на уровне API ОС, не между разными DLL-библиотеками, а внутри одной программы/библиотеки они это обходят стороной.


              1. Dikoy
                10.11.2021 18:30

                В эмбэдде тоже обычное дело Си с классами. Собственно, там только классов не хватает в голом Си, всё остальное из плюсов не особо нужно.

                Вот когда появляется ОС, там уже плюсы могут раскрыться.


  1. Emulyator
    06.11.2021 18:43
    +1

    Вспомнилась древняя многостраничная тема на sql.ru:

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

    Private Sub ReportHeader_Format(Cancel As Integer, FormatCount As Integer)
        On Error Resume Next
        DoCmd.OpenReport Me.Name, acViewPreview
        If Err.Number Then
            Debug.Print "Printing..."
        Else
            Debug.Print "Preview..."
        End If
    End Sub


  1. acklamterrace
    07.11.2021 01:41
    +1

    "On Error Resume Next" живее всех живых в shell скриптах.


    1. ivegner
      08.11.2021 21:18

      Тоже удивляюсь, почему сохабровчане не опознали в нём старый добрый set +e, который стоит по умолчанию в баше.


  1. geher
    07.11.2021 14:53
    +1

    1. On error resume next.

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

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

    2. DoEvents.

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

    3. Динамический оператор перехода в Коболе.

      А вот это уже странно. Интересно, чего пытались этим вообще добиться? Все, что дает этот оператор, реализуется другими средствами более очевидно и понятно. Разве что там есть что-то специфичное вроде ограничения на длину перехода или количество операторов в программе, и этот трюк банально позволяет иногда как-то выкрутиться.


  1. StanEgo
    07.11.2021 16:40
    -1

    1. Костылём здесь может являться сама стековая реализация исключений, но это совершенно допустимый код. Факт его наличия не говорит, что объект Err потом не исследуется и не осуществляется соответствующая диагностика. Как бы вы себе видели это иначе (без использования maybe монады)? Сделать rethrow исключения, которое уже возникло, а потом его отдельно ловить или просто проверить Err после выполнения функции? А вообще "silencing" часто помогает централизовать обработку ошибок, чтобы достичь хорошего SoC и не тащить в математичекую функцию логгеры, диагностику и т.п.

    2. Во-первых, никаких BackgroundWorker в .NET 1.0, 1.1 не было. И в целом странно критиковать Windows Forms, которая сохранилась для обеспечения совместимости с древними версиями API. Во-вторых, добро пожаловать в мир однопоточных UI и буферизации изменений для обеспечения оптимальной скорости перерисовки. Которые можно найти начиная от древнего досовского Turbo Vision с его Application.Update|Refresh до современных веб-фреймворков вроде React с его реконсиляциями, shouldComponentUpdate, forceUpdate и т.п. Придумайте что-нибудь лучше, вам весь мир будет благодарен. То, что вы отгрузите сложные вычисления в BackgroundWorker - это решение lvl 1. На lvl 2 вам понадобится управление этим воркером из основного потока, чтобы иметь возможность остановить эти вычисления, дождаться остановки вычисления очередного шарда и т.п. На lvl 3 у вас уже скорее всего какие-нибудь реактивные стриминговые асинхронные модели, которые забудут про BackgroundWorker вообще и изредка будут делать DoEvents, только это будет скорее WPF и увидим мы какие-то замысловатые Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })). Что суть тоже самое

    3. Совершенно нормальный подход для FSM или акторных моделей обработки.


    1. mayorovp
      07.11.2021 21:13
      -1

      На lvl 3 у вас уже скорее всего какие-нибудь реактивные стриминговые асинхронные модели, которые забудут про BackgroundWorker вообще и изредка будут делать DoEvents, только это будет скорее WPF и увидим мы какие-то замысловатые Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })). Что суть тоже самое

      Современный аналог DoEvents — это await Task.Yield();, а никак не то что вы написали.


      Совершенно нормальный подход для FSM или акторных моделей обработки.

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


      1. StanEgo
        08.11.2021 14:10
        -1

        Современный аналог DoEvents — это await Task.Yield();, а никак не то что вы написали.

        Вы сейчас на полном серьёзе говорите, что метод в официальной документации звучащий как [do not rely on await Task.Yield(); to keep a UI responsive](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.yield) лучше, чем Dispatch.Invoke, который MS [сам использует для симулации DoEvents](https://github.com/search?q=org%3Adotnet+doevents+DispatcherPriority&type=code)? Есть какие-то аргументы, что они чего-то не знает о своём продукте? А то как дураки вводили в том же Dispather отдельно метод Yield, в котором DispatcherPriority.Background из моего примера и Dispatcher.InvokeAsync под капотом. Видимо не хотели, чтобы люди выстреливали себе в ногу полагаясь на волатильный SynchronizationContext.Current.

        Да и отмечен бы данный подход как "изредка будут делать", а не рекомендуемый. Что основывается на статистике гитхаба. Вот так повелось, используют. Не IAwaitable единым живёт мир многопоточности, некоторые ортодоксы могу использовать тредпулы напрямую, BackgroundWorker, BackgroundService и т.п. А так получилось, что обновление UI из них рекомендуют делать через семейство методов Dispatcher.Invoke.

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

        Судя по фразе "несколько экземпляров" вы пытаетесь натянуть весь богатый мир софта на свой опыт в ООП и считаете, что если в какой-нибудь Akka оверхед на актора всего 300 байт, то и любой ембеддед затащит (хотя и в Akka instance-per-message не является рекомендуемым).

        Начнём с того, что речь шла о языке 59 года рождения, там не то что объектами не пахло, там свитчей не было, сердца всех аляповатых FSM. И выживали за счёт динамических goto пока [Multi-way branching replaces the computed goto](https://en.wikipedia.org/wiki/Goto).

        А вот нормальные FSM, которых вы своим авторитетным мнением растоптали - это Google с их Dalvik VM, Erlang Beam VM (ни разу не связан с акторными моделями), Python, GCC, GNU Forth, Tarantool, Ruby, Lua и много других. Ссылки предоставить или сами справитесь? Благо "computed goto", "branch table", "jump tables" - техники про которые написаны сотни научных работ, читаются в большинстве технических ВУЗов. Даже как-то странно всё это разжёвывать в мире где можно ввести пару слов в поиск гитхаба и увидеть мировую практику использования того или иного подхода.

         И в целом не понял, какая связь между FSM, акторными моделями и состоянием в "экземплярами". У меня дизайн не может использовать чистые функции с referential transparency и всеми выгодами FP в том числе для потокобезопасной разработки? И ваш вопрос превращается в "не позволяет создавать несколько экземпляров функции".


        1. mayorovp
          08.11.2021 15:04
          -1

          Начнём с того, что речь шла о языке 59 года рождения

          А закончим тем, что сейчас уже 2021й год, и у нас доступны не только упомянутые вами "computed goto", "branch table", "jump tables", но и паттерн State из ООП, асинхронные сопрограммы, паттерн-матчинг и даже акторные фреймворки. Каждый из этих вариантов лучше так называемого "совершенно нормального подхода для FSM" родом из 1959 года.


          1. StanEgo
            08.11.2021 16:51
            -1

            Я же говорю, вы своим авторитетом растоптали всех разработчиков и исследователей, короткий список из которых я привёл. Ребята из Google или Tarantool заламывают руки, что в далёком 1959 они не догадались использовать "паттерн State из ООП". Команда наиболее актуальной VM для Erlang, языка известного тем, что он само воплощение actor model, уволилась всем составом. При такой диспозиции кто я такой чтобы с вами спорить, конечно я капитулирую.


            1. mayorovp
              08.11.2021 17:25
              -2

              Разработчикам из 1959 года простительно не знать про ООП и паттерн State. Но мы-то в 2021м году живём, и вы именно в 2021м году назвали тот древний ужас "совершенно нормальным подходом".


              Это вы, а вовсе не я, пытаетесь перечеркнуть и растоптать 62 года развития индустрии.


              1. StanEgo
                08.11.2021 18:04
                -1

                Извините, что не разжевал всё до мельчайших деталей. Понимаю, поиск это сложно: - Python. Актуальный master, разжёвано донельзя, утверждают, что "computed gotos" быстрее на 15-20% - - Dalvik VM. Актуальный master. Также всё расписано. - Tarantool. Актуальный вики содержащий в том числе графики сравнения. Был представлен на Google Summer of Code 2021.

                Вот такой вот 1959. Ой, простите за сарказм. Это всё 2021. Там, кстати, ссылки, на них можно кликать. Если вдруг вы не только поиск не умеете.

                Я думаю вам не составит труда предоставить какие-то бенчмарки со своей стороны, коль скоро вы столь безапелляционно заявляете о доминировании, прости меня бог Computer Science, паттерна State.


                1. mayorovp
                  08.11.2021 18:08
                  +1

                  По вашей первой ссылке я вижу нормальные computed goto, которые отличаются от обсуждаемого ужаса. Остальные я не смотрел, надоело уже спорить с человеком который читать не умеет.


              1. Dikoy
                10.11.2021 18:22
                -1

                Разработчики 59 года ООП придумали и реализовали, чтобы вы сейчас могли рассуждать о развитии индустрии. А разработчики 21 года, чаще всего, не понимают, что программируют не только винду/PC.


                1. mayorovp
                  10.11.2021 20:16

                  Тем не менее, в 21м году для любой современной платформы существуют языки программирования, в которых можно использовать что-то кроме конструкции ALTER… TO PROCEED TO для выбора перехода.


                  1. JerleShannara
                    10.11.2021 21:34

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


                    1. Dikoy
                      11.11.2021 00:37
                      +1

                      Да не требует она мощного процессора. Китайцы делают u8 за 30 центов, примерно аналог attiny2313. Мигай не хочу. И это дешевле, чем транзистор с обвязкой.

                      Вот если чтобы помигать нужно андроид/винду накатить сначала, тогда да, тогда всё хуже.


                      1. JerleShannara
                        11.11.2021 04:17

                        В этом и беда — туда, куда не надо было раньше ставить МК — ставят МК, где раньше всё спокойно крутилось на 8051 ставят STM32, на который ещё и ОСРВ накатывают. А далее на сцену выходит Андроид и становится совсем плохо.


                  1. Dikoy
                    11.11.2021 00:42
                    -1

                    Любая конструкция (даже HTML) в конце концов станет машинным кодом, который прямое воплощение имеет в ассемблере. Поэтому вопрос не в том, имеет ли язык конструкцию, а в в том, позволит ли компилатор или ОС добраться до нужных ниточек. Выше я писал как такое делается, меня заминусили, поэтому дам ссылку на точно такой же комент, но ещё пока не заминусованый https://habr.com/ru/company/alfa/blog/587536/comments/#comment_23672460


                    1. mayorovp
                      11.11.2021 01:12

                      Что-то комментарий по вашей ссылке не открывается.


                      Нет, вопрос именно в конструкции. Она просто не нужна в ЯВУ.


                      1. Dikoy
                        11.11.2021 01:19

                        Надо открывать в новой вкладке. 21 век на дворе, понимать надо.

                        Не знаю что вам там не нужно, мне в опытах над андроидом пригождалось. А потом они закрыли дырку. Но над 4.4 ещё можно отводить душу долгими зимними вечерами.


                      1. transcengopher
                        11.11.2021 01:29

                        Нет, дело не в новой вкладке, а в разнице между форматом ссылок на комментарий между новой и старой версиями UI хабра. У меня вот ссылка тоже правильно не открывается если не удалить из неё блок "/comments".


                        Зато вот так должно работать:
                        [Тыц](#comment_23672460)
                        Результат:
                        Тыц.


                      1. Dikoy
                        11.11.2021 01:33

                        У меня хром и срабатывает открытие в новой вкладке. Но буду знать. И ждать статью "три ужасные фичи работы со ссылками прошлого", вухаха.


                      1. mayorovp
                        11.11.2021 11:56

                        Если она пригождалась вам — так расскажите зачем, а то одни общие слова у вас.


                        Что же до того комментария на который вы сослались — то там опять рассказывается про аналог computed goto, а не про ALTER… TO PROCEED TO


  1. nickolaym
    08.11.2021 02:31
    +6

    Буду груб. Про DoEvents автор абсолютно не вкурил в суть. Если он рукожопил с DoEvents, то и без DoEvents у него получалось бы такое же рукожопие.

    Суть в том, что существуют длинные операции. И эта длительность - часть взаимодействия с пользователем. Которая состоит в том, что пользователь должен ждать.

    А вот как именно пользователь должен ждать, - тут могут быть варианты.

    Самый простой из правильных вариантов - заблокировать пользователю ввод. А какой ввод у оконных программ? Это любые значимые действия с окнами. Закрыть, потыкать в кнопки, вот это всё... Это - в чистом виде модальное ожидание.

    А где ещё у нас такое ожидание встречается? Баа, да в модальных диалогах же! Пока диалог не закрыт, нельзя ковыряться в других окнах программы. При этом, что интересно, цикл прокачки сообщений в программе не заблокирован. {Точнее, мы в одном месте цикла вошли в некую функцию, и там запустили новый цикл прокачки. Но не просто запустили... а} аккуратно задисаблили окна, кроме модального диалога. А в обработчиках, завершающих диалог, раздисаблили обратно. (Ещё точнее говоря, модальный диалог не обязан делать такие отступления в цикле, как указано в {}, - можно и немодальное с точки зрения системы окно сделать модальным с точки зрения пользователя, только логику придётся размазать по обработчикам).

    Ну и чем это отличается от безоконных модальных вычислений? Да ничем, кроме наличия модального окна.

    Ну и варианты:

    • Тупой и плохой: вообще отключить прокачку сообщений, пусть всё замёрзнет. Модальность при этом, естественно, сохранится. Но интерфейс испоганится.

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

    • Продвинутый запаристый: не просто заблокировать окно, но и показать модальный диалог прогресса/прерывания. Нужно будет научиться прерывать операцию. В случае с фоновой работой это дополнительные заморочки в логике при стандартном диалоге, с DoEvents - дополнительные заморочки с диалогом при простой логике.

    • Супер-продвинутый: научить программу совмещать фоновую работу с деятельностью пользователя. Но это уже немодальные вычисления.

    А, да!

    • Тупой, плохой и запаристый: вынести длинную операцию в отдельный поток, а в главном сидеть и ждать, пусть интерфейс замёрзнет. Зато с многопоточностью!

    • Тупой, плохой, запаристый и неправильный: вынести длинную операцию в поток - и забыть блокировать окно. Пусть пользователь устроит погром.

    По своему опыту - включая опыт консультирования коллег - могу сказать, что непонимание сути в этом вопросе гарантированно приводит в лучшем случае к колхозу, в худшем - к багам. Но виноват, конечно же, микрософт и DoEvents. Кривые кирпичи подсунули.


    1. Myclass
      08.11.2021 10:16

      Я так и писал выше, хоть и перефразирую сейчас — в руках мастера и пластмассовая линейка будет резать, а у человека с руками из ж.опы — и нож не сможет. Но. Автор прав в другом. Он разговаривает не о мастерах, а повсеместном использовании commands как goto или DoEvents. И очень часто оно так и есть.

      Хотя ваш развернутый ответ мне больше нравится. В нём причины, обоснование и возможные варианты. Это больше, чем просто расктириковать какой-нибудь command.


      1. mayorovp
        08.11.2021 11:36

        Я тогда тоже перефразирую: все "недостатки" DoEvents являются просто следствиями решаемой им задачи. Если вы "разморозили" пользовательский интерфейс — будьте готовы что пользователь там что-нибудь нажмёт.


        1. Myclass
          08.11.2021 13:27

          согласен, но если не «разморозите», то гарантия, что он будет много чего нажимать — 100%


          1. mayorovp
            08.11.2021 13:53

            Вот только размораживать-то — надо.


            1. Myclass
              08.11.2021 14:19

              Согласен ещё раз


    1. eugeneyp
      08.11.2021 19:24

      Т.е. Yield() или Sleep(0), не могут привести к тому что событие от пользователя не будут обработаны другим потоком? Я давно не писал на MFC/OWL/TV/Swing но насколько я помню в части библиотек только один поток мог обрабатывать события от UI и длинный код в нем вешал приложение даже для вытесняющей многозадачности.


      1. nickolaym
        11.11.2021 00:21

        Теоретически, можно написать программу, в которой будет несколько UI-потоков.

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

        Но взаимодействие окон (или, более общо, обработчиков сообщений) через границы потоков становится сложнее. У нас появляются недетерминированные задержки, усложняется протокол взаимодействия... не хочу сейчас закапываться в подробности. Если вкратце и грубо, то система синхронный SendMessage превращает в PostMessage туда и оттуда.

        Запутывается граф владения: кто какие окна может добавить/пристрелить в какой момент времени. Пока UI-поток один, то понятно, что всё делается из него (ну или рабочий поток посылает сообщение, а обработчик в UI-потоке находит подходящий момент).

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

        Я говорю про винду, где всё сделано на SendMessage / PostMessage, - включая почти все изменения свойств окон. Текст там установить, флажок disabled поднять-сбросить, и т.д. Но в общем виде это справедливо для любых оконных систем. (Просто в потроха того же гнома я не лазал, а в потроха винды - вовсю, потому и говорю про винду).

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


        1. mayorovp
          11.11.2021 01:17

          Ну, дедлоки-то решаются полным отказом от SendMessage, не так это и трудно когда в языке есть сопрограммы.


          1. nickolaym
            20.11.2021 04:55
            -1

            Полный отказ от SendMessage - это полный отказ от виндоуза.

            А как сопрограммы тут помогут?


            1. mayorovp
              20.11.2021 10:53

              Если вы использовали SendMessage для вызова к своему коду — то его можно заменить на два PostMessage. Если вы вызывали SendMessage для отправки сообщения стандартному компоненту винды — его можно заменить на два PostMessage и SendMessage посередине (последний будет обращаться гарантированно к своему потоку, а потому никогда не приведёт к взаимоблокировке).


              Только что нашёл в winapi функцию SendMessageCallback. Кажется, всё уже реализовано до нас. А сопрограммы помогут не запутаться в колбеках: код с ними останется таким же какой и был, только вместо SendMessage будет стоять какой-нибудь await SendMessageAsync.


    1. Zalechi
      09.11.2021 14:08
      -1

      del - продублировалось


    1. Zalechi
      09.11.2021 14:11

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


  1. XenRE
    08.11.2021 21:53
    -1

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

    Хе-хе.
    1 — Норм для одноразовых тулз, когда данные заранее известны и достоверны, а единственный пользователь — ты сам;
    2 — А если у вас дохлый микроконтроллер вообще без ОС?;
    3 — Повсеместно используется при переключении real<>protected mode x86 и прочем низкоуровневом.


    1. mayorovp
      08.11.2021 22:10

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


  1. Zalechi
    09.11.2021 11:47

    старые идеи настолько плохи, что лучше всего было бы сжечь их навечно

    Сжечь идею? - Оригинально...

    UPD: хотя, да, идеи же на бумаге часто излагают, тем более раньше. Не слыхал, чтоб так переводили напрямую.


  1. Zalechi
    09.11.2021 14:25

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


  1. Pochemuk
    13.11.2021 16:16

    COBOL никогда не знал, но в каком-то древнем языке встречал переменные типа LABEL. Им можно было присваивать значения реальных меток и использовать в операторах перехода.


    1. netch80
      18.11.2021 10:54

      Как минимум IBM Fortran с «assigned GOTO», вспоминалось тут в комментариях несколько раз.