Comments 322
Я не программист, я только учусь :) Но скажу в защиту On Error Resume Next (классический VB 6). А как быть, если исходные данные кривые, чреваты неожиданными ошибками? А с этим оператором хоть какая-то часть кода отработает.
А если вы обрабатываете 1 млн человек, они ждут результат, и их данные независимы, то почему бы не пропустить десяток ошибочных, записать в лог и разбираться с ними потом?
И даже в таком случае, вместо полного игнорирования ошибки, лучше добавить отдельный обработчик - пусть даже он будет пустой. В дальнейшем в этом обработчике можно будет добавить логирование ошибки, или например формирование отчёта обо всех ошибках, которые произошли при обработке этого миллиона.
В таком случае лучше перехватить исключение на уровне обработки человека, и записать его в отдельную таблицу/список/файл с ошибками — именно чтобы потом можно было разобраться потом.
А техники подобные On Error Resume Next запросто могут привести к тому, что вы обработали 1 миллион человек, и даже не знаете какие из них были обработаны успешно, а какие нет. Успехов вам в ручном поиске ошибок в миллионе записей...
На самом деле очень полезная (в эмбеде, например, в некоторых случаях просто необходимая) вещь - иметь возможность добавлять обработку ошибок так, чтобы после обработки выполнение продолжилось с восстановлением состояния на момент ошибки. И вот это хорошо реализуется на чистом C, а в более абстрактных языках очень плохо. Но это имеет мало отношения к обсуждаемому.
Это есть как фича языка в Common Lisp и называется протоколом condition/restarts. Более того, вы можете предоставить несколько возможных "перезапусков", которые могут быть "применены" к возникшей ошибке пользователем или вызывающим кодом. Одним из этих "перезапусков" может быть "игнорировать" или "изменить на правильное значение и перезапустить сломавшуюся операцию". Работает лучше чем глобальный и опасный On Error Resume Next
Windows: Structured Exception Handling. Очень вкусно именно в этом плане, но
1) Дороговато (каждая функция, которая делает опасные операции или аллоцирует ресурсы, требующие освобождения по выходу, должна ставить и снимать свой обработчик).
2) Ошибки типа деления на 0 или доступа по запрещённому адресу — восстановление после них требует анализа источника вплоть до конкретной машинной команды. Обычно слишком сложно; проще сделать безусловный выход из опасного участка, а ловить проблему уже вверху (типовые исключения C++/Java/Python/etc.)
> Но это имеет мало отношения к обсуждаемому.
Думаю, вполне имеет :) такое обсуждение безусловно должно прийти к разным стилям обработки ошибок.
В том же BASIC, о котором говорили, можно делать так
1) Ставится on error goto <строка>;
2) При переходе запоминается исходная строка в переменную ERL;
3) Обработчик ошибки анализирует ситуацию и может вызвать resume next для обхода и resume <строка> для перехода на любую заданную строку (и, естественно, resume erl для повторной попытки).
Да, это не структурно (поэтому в таком виде не перенесено), но в тех условиях вполне работало.
On Error Resume Next
— первый шажок в сторону структурности, позволявший (хоть и не требовавший) обрабатывать ошибки внутри обработчика ошибок.Можно пример? А то совершенно непонятно, как оно могло бы работать.
> Как следствие из неструктурности — любая ошибка внутри обработчика ошибок была фатальной.
Это бы спокойно решилось явным push/pop контекста (можно через явную переменную).
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
Windows: Structured Exception Handling. Очень вкусно именно в этом плане, но
1) Дороговато
SEH-фрейм занимает 8 байт 2 sizeof(void) на стеке — разве это дорого? В обходе односвязного списка и вызове обработчика по указанному адресу тоже вряд ли что-то дорогое можно узреть. Скорее всего по сравнению с тем, что обработчики будут делать, overhead от обхода цепочки вызовов будет незначительным.
SEH был дорогим по совсем другой причине. Вызов RaiseException приводит к переходу в режим ядра, а это довольно медлительная вещь. В этом смысле SEH-исключения не могли стать альтернативой для кодов возрата статуса и должны были использоваться только для реально исключительны ситуаций. Попытка переделать цикл, проходящийся по миллиону элементов и вызывающий для каждого элемента какую-нибудь функцию, которая с вероятностью 30...50% может сбойнуть на выкидывания исключений очень значительно замедлила бы работу такого цикла, потому что 500 000 раз выкидывать исключение — это 500 переходов в режим ядра и обратно, что на порядок или несколько порядков медленнее, чем если бы вызываемая функция просто возвращала код ошибки, при условии что в в вызываемой функции только какая-нибудь арифметика и манипуляция данными и нет системных вызовов.
Дорого создавать его в каждой функции, где нужна хоть какая-то обработка, добавлять в список и исключать из списка по выходу.
В Unix мире тоже вначале использовали sjlj-exceptions — логически очень близко — но быстро перешли на табличные (exception handler лезет в спец. рантайм данные и ищет, куда передавать управление за catch и/или finally).
(Кстати, судя по MSDN, x86-64 тоже использует табличный метод? я не сравнивал детали реализации)
> SEH был дорогим по совсем другой причине. Вызов RaiseException приводит к переходу в режим ядра, а это довольно медлительная вещь.
Да, это ещё дороже.
Допустим, вы решили испечь сладкий пирожок, но вместо сахара всыпали в тесто соду. Переделаете, или доведёте выпечку до конца любой ценой?
Пилоты самолёта и заправщики не согласовали единицы измерения. Пилоты указали количество топлива в галлонах, а заправщики использовали литры. Лучше сделать дополнительную проверку или пусть у самолёта закончится топливо над океаном?
Проверять данные нужно не всегда. Код для проверки требует времени для написания и должен быть протестирован. Это накладные расходы.
Но если из-за кривых данных программа может записать ошибочное значение в базу, или передать его клиенту или втихую прервать выполнение функции - вы не можете просто закрыть глаза, втопить педаль в пол и пусть оно как-нибудь доедет!
Для бизнеса это финансовые и репутационные потери, в науке - мёртвые марсианские зонды, а в медицине могут и люди пострадать. Да и вам будет просто стыдно.
Да и вам будет просто стыдно.
Всякие бывают ситуации. У меня задачи скромные. На входе текстовая портянка, в которой запрятаны числа. Чем в Ворде или pdf глаза ломать, глядя на числа, может хоть какую-то часть в Excel перегнать.
Если бы все программисты на VB6 писали программы для решения скромных задач, для себя, обсуждать было бы нечего. Но это не так.
А потом однажды у вас генерируется абсолютно пустая книга Excel, и вас ждут часы "счастливой" отладки в поисках того что именно пошло не так. В то время как те же структурные исключения вам бы сами подсказали что именно не так пошло.
Такое поведение чревата самым страшным - порчей данных пользователя.
Если программа упадет - это будет не так печально.
Конечно существуют ситуации, когда такое поведение необходимо - есть программы, например ядро ОС, гипервизоры или какое-нибудь низкоуровневое оборудование, которое должно остаться работающим несмотря ни на что.
В этом случае отказоустойчивость - часть их архитектуры, но и в этом случае оно достигается не съеданием ошибок на уровне выполнения, а съеданием ошибок на уровне транзакций или компонентов - чтобы авторы конкретного модуля определяли, какой объем данных можно дропнуть и какому доверять.
Использовать вместо него on error goto <номер строки> и обрабатывать по сути.
Использовать вместо него on error goto <номер строки> и обрабатывать по сути.
Не «номер строки», а метка (label). Просто номер строки является частным случаем метки, оставленным из соображений совместимости.
И, что характерно, обработчик ошибки, к которому перейдёт выполнение, имеет возможно сделать [Resume], [Resume Next] или ничего из этого.
Т.е. попросить «давай-ка попробуем сделать то же самое, но ещё раз — я вроде бы исправил первопричину неудачи», попросить «ладно, фиг с ним, что там у нас дальше», или же вообще перейти к совершенно иному плану действий.
Диалоге в стиле «Не удалось выполнить <....> Повторить / Пропустить / Отмена» реализуются в буквальном смысле в виде пары строк.
Предлагаю показать реализацию выполнения какой-то потенциально сбойной операции с показом диалога Повторить/Пропустить/Отмена при использовании try...catch-подхода. Как минимум, у вас будет ещё и цикл.
Ну, я смотрел по ресурсам, максимально близким к 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. Ну и почему бы его не написать сразу? Человеку понятнее, компу тоже (не сделает тупой ляп).
Разумный подход, как я считаю, состоит в том, чтобы не использовать GoTo, чтобы на его базе делать циклы. Не надо использовать GoTo вместо For, вместо While, вместо Do. Не надо использовать GoTo для перепрыгивания блока кода вместо использования блочного If или If Else.
Но вообще объявлять GoTo абсолютным злом и запрещать его использовать абсолютно всегда — это глупость и максимализм.
Тогда нужно запретить и break/continue в Си, ведь это тот же goto.
Более того, Джоэль Спольски как-то высказал мысль, что обработка исключений — это тот же скрытый GoTo, только даже не в пределах одной процедуры, а через несколько процедур.
Я такого и не объявлял и не запрещал :))
Я объясняю, почему — и, главное, в чём — это могло быть разумной позицией по состоянию компьютерного дела в 1960-80-х. В определённых контекстах и ситуациях.
> Тогда нужно запретить и break/continue в Си, ведь это тот же goto.
Так и запрещали — как раз для возможности анализа потока исполнения.
> Более того, Джоэль Спольски как-то высказал мысль, что обработка исключений — это тот же скрытый GoTo, только даже не в пределах одной процедуры, а через несколько процедур.
Спольски, конечно, велик, но приписывать ему то, что было очевидно и говорилось ещё в 70-х, как-то странно.
И, что характерно, обработчик ошибки, к которому перейдёт выполнение, имеет возможно сделать [Resume], [Resume Next] или ничего из этого.
Вариант
Resume <label>
тоже вполне оставался.Типичное применение — при ошибке внутри цикла пропустить остаток итерации, и перейти к следующей.
Вспоминая босоногое детство и знакомство с Бэйсиком в упор не помню подобной конструкции.
Когда оно появилось, и почему вообще упоминается если были времена когда подобного не было?
Проблема в том, что «Бейсик» это было нечто на десяток базовых команд и 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…
Именно так! Благодарю за поддержку
Вы поставили общий on error resume next на весь вывод и продолжаете с этим, пока не закончите заполнять таблицу? Откуда у вас уверенность, что вы проигнорировали только ошибки форматирования в числовой вид, а не 100500 возможных других ошибок?
Если вы игнорируете ошибку только в операции приведения в число — ok.
> сделать это как «On Error Resume Next» или «try catch{}», или как-то иначе — не имеет значения
Именно что имеет.
1. Try-catch структурно удобнее для группировки обрабатываемых действий. Да, в ранних бейсиках этого не было, но сейчас мы пишем не на них.
2. Catch позволяет ловить только конкретный тип ошибок, чтобы не гасить все, включая неожиданные тут.
3. Вы явно пишете реакцию на ошибку, она видна (можно даже с пустым catch написать комментарий — уже неплохо).
> Понятно, что в идеальном мире там быть не должно, но бывает всякое.
Идеальности и не требовали вроде? А вот чтобы код был понятен и безопасен во всём предвиденном — надёжнее без такой фичи.
Я присоединюсь к вашей беседе:
Возможны два случая:
1. Просто встретили секцию данных без чисел (по спецификации) - очевидно, что здесь Error исползовать не надо (с современных позиций программирования - даже ошибочно), надо секцию пропустить.
2. В процессе парсинга произошла непредполагаемая ситуация - это Error. Но тут надо проинформировать пользователя "АУ, мы вообще правильно файл парсим, а то вдруг ты сейчас 100500 млн рублей контрагенту полатишь".
Т.е. в обоих случаях "молчаливый Error" не походит.
В первом - это не должен быть Error, во втором - он не должен быть молчаливым.
Многие десятилетия индустриального кода говорят, что чем раньше остановишься в случае ошибки, тем меньше кожанных мешков умрёт. В идеале останавливаться надо на этапе компиляции.
А в отношении ошибок есть два класса ошибок: ошибка в программе и ошибка в вводе. Ошибка в программе может быть остановлена одним образом: стоп.
Ошибка в вводе вообще не должна приводить к исключительной ситуации; а если исключительная ситуация возникла, то это уже ошибка в программе, которая не была готова обработать ошибку ввода.
В Rust это хорошо разделено: ошибка в программе (которую не поймали в компиляции) - это panic!
. Ошибка в вводе/выводе - это Result, который либо Ok, либо Err.
На практике в коде на Rust очень часто работают с Result через простой unwrap
, то есть,panic!
, в итоге. Теоретически так быть, конечно, не должно, но так есть. Я думаю, это потому что в Rust довольно синтаксически громоздко разбирать результаты цепочек вызовов функций, даже используя and_then
, а в системном програмировании такое постоянно нужно делать и люди идут по пути наименьшего сопротивления. "Компилируется же, значит работает" (c).
в WSH с момента его появления это по сути средство для обработки исключений. Там по умолчанию goto 0. Но это конечно не программирование, а скрипты для автоматизации рутины ;)
По пункту 2 - писал ли автор NLM под нетварь? Там многозадачность была такая многозадачность! Если этот условный DoEvents не делать, то сломается весь сервер.
Никогда не пользовался, но зато как по-хакерски:
static void *array[] = { &&foo, &&bar, &&hack };
goto *array[i];
или даже вот так, храним смещения между метками, а не сами метки:
static const int array[] = { &&foo - &&foo, &&bar - &&foo, &&hack - &&foo };
goto *(&&foo + array[i]);
Там это явно записано в самом goto. А с коболовским читаешь и думаешь, что перейдёт на path-1 всегда, а оно оказывается где-то изменено…
а оно оказывается где-то изменено…
Глобальные переменные в любом языке. Разименование ссылок в С.
1. В этом случае всё равно все доступы хотя бы можно нагрепать.
2. Я не представляю себе глубокого смысла такого написания, в котором бы для назначенного goto использовалась глобальная переменная. Это за пределами норм любых возможных стилей, я твёрдо убеждён. Напоминаю, что goto допустим только в пределах одной функции. В крайнем случае будет переменная уровня модуля, это ещё понятно.
> Разименование ссылок в С.
В C, вообще-то, ссылок нет, есть только указатели. Если вы про них… повторюсь, когда явный вызов по указателю — читатель кода ждёт, что этот указатель кем-то присваивается и может меняться по условию.
А когда вы видите вроде бы нормальный безусловный goto 1234, но оказывается, что его могут менять, и любой подобный переход надо проверять грепом по исходникам, а не меняет ли кто его… мягко говоря, бессмысленная затрата сил.
В этом случае всё равно все доступы хотя бы можно нагрепать.
любой подобный переход надо проверять грепом по исходникам
Нет принципиальной разницы. Можно изменить значение переменной, можно изменить указатель, можно изменить ещё и метку. А в некоторых С++ можно ещё и операторы переопределить ;) Кстати, про
#define true false
не забываем ;)
Есть. Только надо сравнивать BASIC не с C, а например с Java (хотя бы). Там возможности скрыто выстрелить всем в ногу резко сокращаются.
> #define true false
gcc -E к вашим услугам :) В случае проблем определённого уровня непонятности он неизбежен.
Глобальные переменные в любом языке.
Но не в любом языке каждая метка является скрытой глобальной переменной.
То, что написано на Си - это по сути индексный джамп. Вполне себе легальная вещь, может исполняться в ПЗУ. То, что на Коболе - это патчинг кода самим кодом. Подобные трюки можно было видеть на ассемблере во времена, когда кодовый сегмент не могли/не хотели защищать от модификации. Но иметь такую штуку на ЯВУ - это жить на вулкане.
Этим можно хардкодить клеточные автоматы. Целесообразно, если логика сложная, специфичная для разных состояний, при этом в таблицу не влезает, плюс нужно чтоб было быстро. В каком-нибудь NginX (моё непрофессиональное предположение)?
On Error Resume Next
живёт и здравствует в PHP под названием @
DoEvents
вносил намного меньше недетерминизма, чем многопоточность. Все заявленные аргументы против DoEvents
в ещё большей степени применимы против многопоточности.Согласен. DoEvents это аналог старого доброго Sleep(0), который давал возможность ОС консистентно разгрести очередь сообщений, пока кто-то умный блокировал UI-йный тред.
DoEvents ни капли не аналог Sleep(0).
Sleep(0) не делает ничего с очередью сообщений, он просто провоцирует планировщик переключить контекст на выполнение какой-нибудь другой задачи из тех, кто имеет на этой «шанс» в данный момент.
DoEvents это это аналог PeekMessage—>TranslateMessage—>DispatchMessage.
Если говорить о VB6/VBA, то помимо прокачки очереди оконны сообщений и вызова соответствющих им обработчиков (оконных процедур), которые транслируют оконное сообщение в срабатывание какого-нибудь ООП-событий, DoEvents может ряд других интересных и потенциально очень тяжеловесных вещей (именно по этому я всегда критикую использование DoEvents в цикле).
На самом деле, не многие догадываются, что 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.
А этот вызов может оказаться «чёрной дырой» при неблагоприятных условиях.
В Delphi в VCL аналог DoEvents кстати тоже есть. Это Application.ProcessMessages. Надо просто уметь им пользоваться. Если как у автора вызывать его из какого-то расчетного цикла (для обновления прогресса или какого-то списка на форме) , то все контролы на форме или сама форма должны быть предварительно задизэйблены, чтобы нажатия на них не срабатывали.
то все контролы на формеКроме весьма полезной в таком случае кнопки «Отмена», чтобы слишком длинную операцию можно было остановить штатно.
Кроме того, добавлю. Если мне не изменяет память, этот подход родом из времен Delphi 1 / Windows 3.1, когда на персоналках процессор обычно был только один, многозадачность была невытесняющей, и современных развитых средств межпроцессной синхронизации не было ни в системе ни в подавляющем большинстве языков программирования.
— — — —
PS Прошу прощения что добавляю сюда — технические)) проблемы.
JerleShannara
к ветке:
https://habr.com/ru/company/alfa/blog/587536/#comment_23672762
у меня вопрос возник:
у вас нет оперативной памяти, вообще нет(никакой, даже Cache as ram нельзя). А call ...Как поступаете с указателем на стек? И тем что по этому указателю идёт запись значений? Или от того что памяти нет — нечего плохого не происходит?
Прошу прощения — после второго прочтения, я всё таки понял, что ответ в той ветке — есть.
А зачем дизэйблить контролы? Пользователь что-то нажмёт, это что-то отработает, а потом продолжится вывод нашего длинного списка. Если этому "что-то" не нужны наши числа, которые мы генерируем, то не вижу никакой проблемы
Как минимум, надо либо задизейблить ту кнопку, нажатие на которую и привело к выводу длинного списка, либо как-то разумно обработать повторное нажатие.
Действия пользователя могут привести к удалению элемента интерфейса, на который должен выводиться этот длинный список. Достаточно, например, закрыть окно, тяпнув системную кнопку закрытия окна..
Решений несколько.
Можно заблокировать только те элементы управления, которые могут привести к уничтожению структур данных, использующихся при создании и выводе списка. Но иногда это слишком сложно, и проще заблокировать все окно.
Можно ничего не блокировать, а просто ксждый раз проверять, а не прибил ли кто-то исходные данные для построения списка или элемент интерфейса для его вывода. Но это тоже ненужное усложнение и перерасход ресурсов, способный неприемлемо увеличить в некоторых случаях время формирования списка. И в таких случаях тоже может оказаться проще полностью заблокировать окно.
Если есть кнопка "Начать расчет" и её нажмут еще раз, то мы снова окажемся в методе расчета, "как бы рекурсивно", но на самом деле с точки зрения стека вызовов это будет не совсем прямая рекурсия :) Ну и другие выше написали, что если закрыть окно или сделать еще что-то нехорошее, то когда код вернется из обработчика обратно в код нашего расчета, то в лучшем случае мы получим Access Violation при обращении к уже закрытому и освобожденному окну или какому-то контролу на нём.
Совершенно разные вещи.
DoEvents - фактически вызов обработчика очереди сообщений приложения. Там много чего, в том числе и отдача слайсов в систему. Длительные глухие циклы не только подвешивают приложение, но могут всю систему притормозить (такая уж многозадачность в винде). Аналогично притормозить систему можно и в отдельном треде, сделав там длинный цикл баз вызовов функция синхронизации (WaitObject/WaitForMultiplyObjectcts или хотя бы Sleep).
но могут всю систему притормозить (такая уж многозадачность в винде).
В Windows 9x разве что. Хорошая многозадачность в винде, не надо наговаривать. По крайней мере на уровне ядра всё сделано безупречно.
Вот пользовательская подсистема может грешить. Один из примеров грешка: буфер обмена. Если какое-то приложение захватит буфер обмена и не будет отпускать, другие приложения, которые тоже захотят получить доступ к буферу обмена, встанут. Но те, которые не попытаются, не встанут уж точно.
Не согласны — предоставьте PoC-код подвешивания системы длительным глухим циклом.
OOM Killer в Linux настолько дерьмовый, что ждать его срабатывания можно буквально часами, а разработчики ядра не видят никакой проблемы в том, что странички, ответственные за обработку пользовательского ввода, тоже свопятся, так что пользователь полностью теряет контроль над ОС и не может даже прибить глючную программу.
На Windows с OOM не лучше - глухое подвисание UI (до нескольких часов) и последующий BSoD. Лучше бы уж приложения убивались.
А что мешает oom-killer настроить? Довольно просто выдать всем процессам определённого пользователя повышенный приоритет в очереди на завршение при нехватке памяти. Cgroups тоже существуют
Код не покажу т.к. лет пять уже с виндой дел не имею. Но до этого 25+ лет под виндой. 3.11Б 98, 2000, 7 (32 бит). Может в новых 64 бит, на многоядерных процессорах с гипертрейдингом что-то и стало лучше, но еще в 7 на стареньком двухядерном целероне без гипертрейднга, поток (особенно с повышенным приоритетом), в котором не было ничего кроме сложных математических вычислений, без функций синхронизации, очень сильно тормозил систему. Попытка вызова диспетчера приложений затягивалась на 10-15 секунд. Попытка снятия задачи еще на столько же.
Радикально помогал вызов синхронизирующей функции. Хотя бы казалось бы бесполезный Sleep(1) в теле цикла потока. И все сразу начинало дышать.
Можете сами попробовать. Только для чистоты эксперимента без фреймворков, голый WinAPI.
Ну так многозадачность в винде реализована. Просто надо знать и учитывать.
Можете сами попробовать. Только для чистоты эксперимента без фреймворков, голый WinAPI.
Я и пишу на голом WinAPI на C/C++ без всяких фреймворков.
То, что вы описываете, для меня выглядит как нонсенс. Не наблюдал я такого в десятках и сотнях случаях, когда подобные циклы возникали.
Sleep(1) «помог бы» (по вашим словам), но Sleep(1) сделает так, что то, что мега-фукнция вычислила бы за секунду, она будет вычислять за полторы минуты.
Единственное вразумительное объяснение, которое я могу дать вашим наблюдением вот такое: у вас на машине, где вы это наблюдали, была катастрофическая ситуация с системой охлаждения. Высохшая/неправильно нанесённая термопаста, например, или что-то в таком духе.
Как только вы запускали поток с бесконечным циклом, он, в норме не страшный для процессора, вызывал его критический разогрев до точки, где начинал действовать throttling для предотвращения перегрева. Естественно, что throttling вызывал деградацию всей системы.
В норме того, что вы описываете, не происходит.
особенно с повышенным приоритетом
Вот в этом и проблема. Потокам, которые проводят долгие и тяжёлые вычисления, надо выдавать не повышенный, а пониженный приоритет. Тогда и никаких тормозов заметно не будет.
Выдав же такому потоку повышенный приоритет, вы явно говорите системе, чтобы все ресурсы были направлены в первую очередь на вычисления в этом потоке. Так откуда тогда удивление что остальные части системы тормозят?
Радикально помогал вызов синхронизирующей функции. Хотя бы казалось бы бесполезный Sleep(1) в теле цикла потока. И все сразу начинало дышать.
Во-первых, Sleep(1) будет ждать не 1 миллисекунду, как вы, наверное, ожидаете, а заранее совершенно неизвестный интервал времени, зависящий от частоты таймера, которую задаёт ОС из своих личных соображений.
Во-вторых, правильный способ отдать исполнение другим потокам изнутри вычислительно тяжёлого цикла, это не Sleep() а SwitchToThread(). Его преимущество в том, что он а) позволяет системе не тормозить б) если система больше ничем, кроме вашей программы не занята, то исполнение к вашему циклу вернётся на порядки быстрее, чем после Sleep()
On Error Resume Next живёт и здравствует в PHP под названием @
А разве это не аналог
// @doSomethingDangerous()
try { doSomethingDangerous() } catch(err) {}
? т.е. действует ровно на ту конструкцию к которой задан, а не на весь код ниже.
UPD: Память меня подвела. Это не аналог try-catch, это silence-operator. Возможность опустить вывод "diagnostic error". С обычными настоящими ошибками никакой связи.
В PHP есть особый вид боли 2 вида ошибок: исключения и ошибки (это легаси). Первые можно поймать с помощью try-catch, а вторые приглушить с помощью @ и получить данные о приглушённых ошибках с помощью специальных функций.
Эм, не согласен. В PHP @ значит не показывать ошибку, но не значит "пропустить строку в случае ошибки".
Пример @notexistFunction() остановит программу с "пустым экраном", без показа ошибки.
В VB6 on resume next перейдет к следующей строке в этой ситуации.
неверно. @ с Fatal не справляется.
попробуйте например @file_put_contents(1, []); - второй параметр должен быть строка но ошибка не будет выведена.
или например чтение несуществующего ключа в массиве
$a = []; $b = @$a['val'];
аналог современного $b = $a['val'] ?? null;
но, конечно, все равно эту конструкция лучше не исопльзовать.
Эм, не согласен. В PHP @ значит не показывать ошибку, но не значит "пропустить строку в случае ошибки".
Если уж на то пошло, то и в VB конструкция «On Error Resume Next» не означает «пропустить строку в случае ошибки».
Сильно изменилось. Уже давно практически все можно через Exception отловить и обработать, без отображения предупреждений
https://www.php.net/manual/en/function.error-reporting.php
похоже, далее hello world дело у вас не пошло )
Так не используйте функции для работы с файлами, используйте объекты
За 10 лет уже очень много изменилось. И текущий стандарт это делать : display_errors = off, log_errors = on. Т.е. ошибки не выводить, а логировать в файл.
Ну и да, в случае с открытием файла: если проверять всё до того как делать fopen, то и вероятность ошибки близится к нулю :) Т.е. is_readable и тп.
Не соглашусь, всё же довольно удобно в процессе разработки включить вывод ошибок прямо в браузер, чтобы не лазить постоянно в error лог (если и браузер, и лог открыты в фулскрин, нет второго монитора и/или используемая ОС - Windows, где нет tail с режимом отслеживания изменений).
Кстати, разве в Python иначе? Там тоже ошибка приводит к выводу её в основной поток вывода, если я ничего не путаю. И в JS тоже. Единственная разница - там программа всё-таки падает на том месте, на котором произошла ошибка.
-
3 – это ж предтеча указателя на функцию. При разумном использовании создаст примерно те же проблемы... Разумеется, с тех пор языки развились и использовать неразумно стало труднее :-)
2 – так и не понял, какое отношение имеет к потокам. Но ситуации, когда внутри одного цикла обработки событий запускается другой (например, DoModal или обработку таскания мышкой через цикл с GetMessage) тоже недолюбливаю.
1 – ну, автор, видимо, достаточно молод и не застал времена, когда 100 строк на бейсике – это было дофига, и хоть какая-то альтернатива "упасть на первой же ошибке" была праздником...
Альтернатива упасть это скорее on error goto <строка>. Вот это уже осмысленная обработка (там ещё давался номер ошибки и номер исходной строки в переменных — так было, например, в GW-BASIC).
А полное замалчивание это таки несерьёзно.
> 3 – это ж предтеча указателя на функцию. При разумном использовании создаст примерно те же проблемы…
Это ужасная предтеча именно потому, что по чтению кода 1) нельзя предположить, что точку перехода вообще может кто-то менять (фича редкая и о ней обычно не думают), 2) сложно вычислить, чем и как она меняетcя.
Когда есть указатель на функцию или переменная точки назначения в «назначенном GOTO» (так это звалось в Fortran), то сразу видно, кто этим управляет и как. Тут — такого нет, фича контринтуитивна.
(там ещё давался номер ошибки и номер исходной строки в переменных — так было, например, в GW-BASIC)
VB6 сохранял эту возможность в полном объёме.
Был даже какой-то «предрелизный» плагин, добавлявший номера всем строкам: если ошибка происходит в непронумерованной строке, то в
Erl
будет 0.Вы из другой эпохи)
То есть сейчас я с вами во всём соглашусь, но 33 года назад был другого мнения, слаще репки ничего не пробовал. А ещё в ту эпоху подход был совершенно другим. Программу можно было держать в голове и продумывать все пути выполнения. Т.е. никаких "нельзя предположить", ты знаешь чуть ли не наизусть все 100 строк программы). Это сейчас мы стали ленивые и умные, и эффективности кода предпочитаем его понятность.
33 года назад я в школе баловался тем же Бейсиком на Агатах. Я не знаю, о какой вы эпохе говорите, но, похоже, она примерно совпадает.
> Т.е. никаких «нельзя предположить», ты знаешь чуть ли не наизусть все 100 строк программы).
У меня впечатление, что вы спутали контекст. Эта часть обсуждения про ALTER GOTO, которое в COBOL и в котором могли быть и миллионы строк программы. Точнее, в одном модуле вряд ли держали бы больше нескольких тысяч, но всё равно это сильно больше, чем охватывается со знанием наизусть.
С использованием ножа можно сделать салат, а можно и палец себе отрезать. С другой стороны особо одаренный программист не имея Error Resume Next, может каждую команду и в try/catch без обработки ошибок обернуть, еще и функцию удобную для этого сделать.
Другими словами, мы не можем точно сказать, куда приведёт нас
GO TO
, если только не знаем полную историю выполнения приложения
Да это же прямо описание реактивного программирования! Control flow скрыт в графе зависимостей observable/computed переменных, удачной отладки!
Э-э-э, а зачем вообще отлаживать граф observable/computed переменных?
Фактически, при этом у нас нет возможности без отладчика узнать, при каких условиях выполняется тот или иной кусок кода (и выполняется ли он вообще хоть когда-то), потому что он выполнится по событию, которое генерируется неявно (в момент применения какого-нибудь LINQ-выражения) и передаётся в обработчик, который устанавливается динамически в момент получения объекта, причём объект может быть не только вашим, а вообще любым, который поддерживает нужный интерфейс.
Это просто блюдо спагетти, в котором за кодом не видно алгоритмов, и разбираться в такой динамической каше — натуральный ад, такой, что проще реверсить C++ бинарник в IDA, чем разбираться в C# коде, где «реактивные программисты» навертели этой долбанной динамики с композицией и фабриками фабрик.
Если у вас LINQ-выражения вызывают события — почему вы обвиняете в этом реактивное программирование?
Но и это ещё не самое плохое. Хуже всего то, что когда программа написана в реактивном стиле, то даже и вызов событий спрятан. Вот вы выполняете присваивание. Обычное присваивание, записывая значение в поле объекта. Как, глядя на эту строчку, можно хотя бы догадаться, что тут у нас оказывается будет последовательно вызвано целых четыре разных события? Особенно если это поведение добавляется динамически при определённых условиях.
В реактивных программах значительная часть информации, нужной для понимания кода, скрыта от программиста. Программист больше не видит, какие подсистемы будут затронуты при выполнении той или иной строчки кода, и потому понимание алгоритмов и поиск ошибок в такой программе — это ад.
Если вам надо знать кто будет обрабатывать событие для понимания программы — значит, вам не следовало использовать событие. Если вам надо знать всех кто зависит от реактивной переменной — возможно, реактивная переменная тоже лишняя.
Однако, обычно я сталкиваюсь с совсем другими ситуациями, когда для понимания кода не требуется знать ничего из перечисленного вами.
Если вам надо знать кто будет обрабатывать событие для понимания программы — значит, вам не следовало использовать событие.
Вот только 'надо знать' программисту поддержки, который 'на эксплуатации произошло нечто, что нам не нравится' расследует. А использовал — совсем другой человек лет n назад.
Уточнение: если программисту поддержки надо знать всех кто зависит от реактивной переменной — возможно, реактивная переменная тоже лишняя или использована неправильно.
А ему не надо? Потому что достаточно часто идет разбирательство 'что тут у нас вообще в системе происходило, что получилось так, как получилось?' Причем код разбирающийся видит первый раз в жизни. И воспроизведение ситуации в режиме отладки — не обязательно доступно.
Задача "что тут у нас вообще в системе происходило" должна решаться логами, а не запретом механизма который упрощает код и убирает глупые ошибки.
ProcessMessages из Delphi это не аналог ли DoEvents?
Да, мне по описанию очень напомнило.
Использовалось ровно для этого - провернуть основной поток UI сообщений.
Это именно оно (внутри оно просто вызывает WinAPI вызовы PeekMessage-TranslateMessage-DispatchMessage)
Это то же самое и это не костыль. Использование этого метода за частую костыль - да. Но сама суть метода - это принцип работы окон в Windows. Обработка сообщений.
Автор немного слукавил и ни чего не сказал, когда демонстрировал пример с добавлением элементов в Listbox с последующим вызовом DoEvents(). Ведь при "современных" подходах мы также можем выполнять действия при заполнении цикла. Ведь это проблема лишь того, что не заблокировали кнопку, по которой происходит заполнение списка.
В ассемблере есть старый добрый RET
, который извлекает из стека верхнее значение и передаёт управление соответствующей строке кода, вот только в стек можно писать что-угодно и даже забыть это что-то оттуда достать, получив неопределённое поведение. Некоторые очень злые программисты используют этот манёвр в качестве неявного GOTO
.
Link register?
mov ebp, RetFromCoolProc
jmp MyCoolProc
RetFromCoolProc:
MyCoolProc:
…
jmp ebp
А для более сложных случаев есть romcc.
Конечно, это не относится именно к VB, но вряд ли автору бы понравилось, если бы его кофеварка, стиральная машина или, не дай бог, легковая машина выпадала бы в кору из-за ошибки в каком-нибудь скринсейвере. А как-то однажды ракета Ariane V выпала в кору и затем на землю из-за неиспользуемого датчика. Так что resume next имеет своё применение. Хоть это и не лучший возможный вариант, но и не худший.
А когда кофеварка забудет выключить нагревательный элемент из-за resume next, посчитав, что процесс нормально прошел, как оно понравится автору?
Как она может забыть из-за resume next? Он не влияет на порядок операторов. Забыть можно только из-за отсутствия 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»).
Записали нолик — всё, ящик завис до передёрга по питанию снаружи (хм! на лаптопе без сменной батареи я боюсь пробовать). И всё честно — документировано, что ручку трогать нельзя, несмотря на то, что явно выставлена.
Я разве говорил, что не надо проверять коды возврата?
Весь смысл resume next состоит как раз в том, чтобы сохранить последовательность выполнения операторов. А не в экономии на проверках, тем более критически важных. Каждое средство надо применять с умом
А правильной метафорой кода в данном случае будет такая:
включить нагреватель;
показать мультик;
выключить нагреватель;
и падение на мультике.
Весь смысл resume next в 21м веке состоит как раз в экономии на критически важных проверках.
Те, кто на проверках не экономят, выбирают другие техники.
Прямо — не говорили. Косвенно — по факту, говорили. Сначала, давайте уточним: в статье говорится как про «ужасную фичу» не resume next само по себе, а про on error resume next. А это уже крайне важное уточнение.
В этом режиме таки последняя ошибка фиксируется, но не обрабатывается.
А вот тут уже открывается то, что одна необработанная ошибка может вызывать каскад следующих. И даже в случае, если вы после блока, обработанного в этом режиме, решили проверить ошибку, вы максимум заметите признак, что что-то случилось, но что стало причиной — исходная ошибка — давно перетёрто более поздними наведёнными ошибками.
Это то, что в моём аналоге было внутри get_active() — в результате он выдаёт некорректный результат, который дальше рушит всё.
А далее я привёл пример, как именно оно может рушить.
> Весь смысл resume next состоит как раз в том, чтобы сохранить последовательность выполнения операторов.
И опять таки, resume next из обработчика или on error resume next перед блоком целевого кода?
Если первое — не вопрос, обработчик знает, что делает.
Если второе — см. выше про невозможность узнать исходную ошибку и про наведённые последствия. Эту последовательность выполнения уже нельзя сохранять, раз ошибка, её надо прервать и перейти на обработку ошибки.
> и падение на мультике.
Почему будет падение на мультике, и почему вы считаете, что эта метафора правильнее?
Сначала, давайте уточним: в статье говорится как про «ужасную фичу» не resume next само по себе, а про on error resume next. А это уже крайне важное уточнение.
В статье говорится про on error resume next как про средство обработки исключений. Если автор не может представить себе работу с ошибочными ситуациями иначе чем через тотальный механизм исключений и не понимает, зачем этот механизм может быть нужно отключать, то это исключительно его проблема, о чём я и пишу.
А вот тут уже открывается то, что одна необработанная ошибка может вызывать каскад следующих. И даже в случае, если вы после блока, обработанного в этом режиме, решили проверить ошибку, вы максимум заметите признак, что что-то случилось, но что стало причиной — исходная ошибка — давно перетёрто более поздними наведёнными ошибками.
Если мы говорим про embedded, то там вообще неправильно строить логику работы на внутренних состояниях программы. Неясно, что происходит - проверь железо.
Почему будет падение на мультике, и почему вы считаете, что эта метафора правильнее?
Потому что это типичная проблема вообще. Затаскивание в отказоустойчивый код фрагментов, писавшихся из других побуждений или с другой квалификацией, и падение всей системы от незначимых для логики управления ошибок. А особенно явно это проявляется при реализации в управляющем коде элементов GUI, где кода много, возможностей для возникновения исключений полно, а никакой фактической ценности это не несёт.
Когда моя тоёта показывает загрузочный мультик на приборной панели, при том что тот же экран показывает цифровой спидометр, сообщения о поломке техники и т.д. - я на это смотрю с содроганием.
Я прогрепал исходную статью на слово «исключение» в любом падеже… его там нет совсем. Оно появляется только в комментариях. То же самое про exception. Я не знаю, зачем вы высасываете из пальца то, чего явно нет, и обвиняете в этом автора, но конструктивности обсуждению это не добавит.
В статье говорится про обработку ошибок. А вот она может делаться разными методами.
> Если мы говорим про embedded, то там вообще неправильно строить логику работы на внутренних состояниях программы.
Это было бы хорошо, если бы огромное количество железа не требовало своей логикой работы знания со стороны драйвера, в каком состоянии что находится. Например, есть write-only регистры управления, состояние которых прочесть нельзя — можно только писать и/или посылать общий ресет, если оборудование не отвечает как следует. Или, например, движитель позиционирования детали на станке — задача блока управления разогнать и потом плавно затормозить, и станок не рапортует «ой, движусь уже очень быстро, пора тормозить». Кажется, вы совершенно не представляете себе проблемы этой области.
> Неясно, что происходит — проверь железо.
Это если есть чем и как проверять. Разумеется, какие-то защиты всегда есть, но обычно только против самых крайних проблем (типа фатального сгорания).
> Потому что это типичная проблема вообще. Затаскивание в отказоустойчивый код фрагментов, писавшихся из других побуждений или с другой квалификацией, и падение всей системы от незначимых для логики управления ошибок.
Не вижу никакой связи с обсуждаемым. Вы пошли куда-то растекаться по древу, не объяснив, куда бежите и зачем.
> Когда моя тоёта показывает загрузочный мультик на приборной панели, при том что тот же экран показывает цифровой спидометр, сообщения о поломке техники и т.д. — я на это смотрю с содроганием.
Если это при включении (поворот ключа зажигания или аналог), это, конечно, сомнительно, но критической проблемы не видно.
Я прогрепал исходную статью на слово «исключение» в любом падеже… его там нет совсем. Оно появляется только в комментариях. То же самое про exception. Я не знаю, зачем вы высасываете из пальца то, чего явно нет, и обвиняете в этом автора, но конструктивности обсуждению это не добавит.
В статье говорится про обработку ошибок. А вот она может делаться разными методами.
В статье говорится про "вредный" оператор on error resume next, который сам по себе является частью механизма обработки исключений, не более и не менее. Не надо натягивать сову на глобус и представлять дело таким образом, как будто бы речь идёт об обработке ошибок вообще. Если бы автор написал, что критические ошибки нуждаются в обработке, вряд ли бы кто-то стал спорить с таким утверждением. Но оно тривиально.
Это было бы хорошо, если бы огромное количество железа не требовало своей логикой работы знания со стороны драйвера, в каком состоянии что находится. Например, есть write-only регистры управления, состояние которых прочесть нельзя — можно только писать и/или посылать общий ресет, если оборудование не отвечает как следует. Или, например, движитель позиционирования детали на станке — задача блока управления разогнать и потом плавно затормозить, и станок не рапортует «ой, движусь уже очень быстро, пора тормозить». Кажется, вы совершенно не представляете себе проблемы этой области.
Мне кажется, это вы себе совершенно не представляете проблемы этой области. Управление не может успешно функционировать без обратной связи. Если у вас станок не имеет данных о фактическом положении детали (реальном фактическом положении, а не модели положения, созданной на основании своих усилий по её перемещению), то такой станок не сможет работать.
И write-only регистры здесь не причём. Мы управляем физическим обьектом, а не содержимым регистров.
Управление не может успешно функционировать без обратной связи. Если у вас станок не имеет данных о фактическом положении детали (реальном фактическом положении, а не модели положения, созданной на основании своих усилий по её перемещению), то такой станок не сможет работать.
На самом деле может, и даже работает. Датчик-то денег стоит, а процессорное время на расчёт модели — бесплатное до определённого предела.
Мне всё больше кажется, это вы как раз стали представлять именно таким образом.
> Управление не может успешно функционировать без обратной связи. Если у вас станок не имеет данных о фактическом положении детали (реальном фактическом положении. Если у вас станок не имеет данных о фактическом положении детали (реальном фактическом положении, а не модели положения, созданной на основании своих усилий по её перемещению), то такой станок не сможет работать.
Сможет. Например, если реализация перемещения сделана шаговым двигателем, мы знаем по количеству пройденных фаз, на сколько мы переместились от исходной точки. При этом управляя скоростью смены фаз можно регулировать скорость перемещения, о чём я и говорил в прошлом комментарии.
Один раз за рабочую смену (если не происходит нештатной проблемы) производится операция настройки, при которой суппорт уезжает в известную позицию (замыкается контакт или что-то подобное), и дальше используется уже известная позиция согласно данным, хранящимся в оперативной памяти блока управления.
Это вполне типовой метод, и не только в станке. В компьютере он работал, например, в приводе дискет — всегда, и в приводе жёстких дисков — где-то до объёмов в 200MB. Если контроллер хочет переустановить позицию (рестарт или подозрение на некорректность позиции), он крутит позицию к нулю, пока не срабатывает датчик нулевой позиции, а дальше хранит в памяти состояние, до которого он «докрутил» управление. Впрочем, в «Агатах» крутили на 80 DD дорожек назад безусловно, не проверяя датчик, что приводило к постепенному разбалтыванию механизма :) но пока он был правильно состроен — работало.
А чтобы датчик постоянно проверял позицию… никто так не делает.
И это не единственный метод (хотя один из самых простых и при этом работающих).
> Мне кажется, это вы себе совершенно не представляете проблемы этой области.
См. выше. Банальности, но вы их не знаете.
> И write-only регистры здесь не причём. Мы управляем физическим обьектом, а не содержимым регистров.
Верно. Но разница между, например, заклином в безвыходном состоянии на уровне значений линий управления, и физическим объектом, тут только в уровне материальных последствий.
Когда я начал читать про шаговый двигатель, то как раз хотел в ответ написать, что таким образом было реализовано печально известное управление дисководом в Apple II... и тут дочитал до того, что Вы сами пишете про Агат. А раз Вы знаете про Агат, то и постоянный звук "трррррррр" должен быть Вам хорошо знаком. А это как раз издержки принятого хренового решения.
При этом дисковод Агата - это далеко не станок, там всего 80 позиций для 40 дорожек.
> И write-only регистры здесь не причём. Мы управляем физическим обьектом, а не содержимым регистров.
Верно. Но разница между, например, заклином в безвыходном состоянии на уровне значений линий управления, и физическим объектом, тут только в уровне материальных последствий.
Разница в том, что значение регистра само по себе не представляет интереса.
Хреновое решение было, да, но оно в отсутствии датчика нулевой дорожки, а не в отсутствии мифического датчика, который бы всегда мерял положение головки и выдавал что-то вроде «дорожка 17½».
> Разница в том, что значение регистра само по себе не представляет интереса.
Представляет, когда оно даёт любой эффектор для чего-то ещё.
Даже если просто на шину один будет выводить 1, а другой 0 (вместо Z-состояний), можно сжечь выходные каскады.
А пример с вечным Reset пожёстче :)
В микроконтроллерах часто делают один write-only регистр на выход и другой read-only регистр для чтения того, что фактически получилось. Именно по указанной выше причине. Интенция не всегда бывает равна результату.
а не в отсутствии мифического датчика, который бы всегда мерял положение головки и выдавал что-то вроде «дорожка 17½».
Однако, фактически именно так в современных дисках и происходит. Номер дорожки читается из сервоинформации, а не подсчитывается по числу шагов.
Потому я и проводил аналогию с древними дисками, а не современными. Вы не найдёте такую сервоинформацию на станке, или она будет слишком ненадёжной.
> Интенция не всегда бывает равна результату.
Это всё-таки особые случаи (я даже с ходу пример не вспомню).
Ну, это скорее не про ветхозаветный resume next, а про нормальную обработку ошибок (неважно, на try-catch, кодах ошибок или монаде Maybe/Either).
"Ветхозаветный" resume next - это как раз про обработку ошибок в кодах. В vb6, как минимум, on error сбрасывал статус объекта Err. При возникновении ошибки у объекта устанавливались атрибуты Code и Description. Обработка знания о возникновении и о коде ошибки - в руках пишущего. Может не так структурировано, как try-cath-fin, но вполне функционально. А отстрел обработки ошибок без вывода сообщений или с невнятным сообщением - достаточно часто встречаемое решение даже в настоящее время и даже с использованием современных языков программирования и даже с использованием современных конструкций языка.
Емнип он появился задолго до VB6 (на восьмибитных компах), и, насколько я помню, объекта Err тогда ещё не было (хотя не поручусь).
ON ERROR RESUME NEXT
появился в QuickBASIC (на 16-битных компах); тогда ERR
был целочисленным (без поля Description), но принципиальной разницы не было.Заглянул в туториал gwbasic ( по старой памяти, сталкивался в школе). Там данная конструкция делится на две: on error goto n и, отдельным оператором, resume next. При возникновении ошибки устанавливаются переменные err и erl, которые содержат код ошибки и строку, в которой возникла ошибка. Оператор resume next (насколько я понял) возвращает исполнение на строку, следующую за строкой возникновения ошибки (erl+1). Недостаток данной конструкции (имхо) - плохая читаемость и структурированность (для улучшения которой обработчики ошибок выносились на номера строк, заведомо большие тех, на которых располагался код основной программы). В остальном - вполне функциональная конструкция, корректность использования которой опять таки возложена на разработчика.
ON ERROR RESUME NEXT
и парой ON ERROR GOTO 1000
/ 1000 RESUME NEXT
есть одна существенная разница: RESUME NEXT
сбрасывает ERR
, так что проанализировать его в точке возникновения ошибки, как после ON ERROR RESUME NEXT
, не будет возможным. Выносить из основной программы приходится не только строчку RESUME NEXT
, но и обработчик целиком.Почитайте у Джоэла статью "Труп прибитый гвоздями стене"
К сожалению, похоже копирасты добились выпиливания всего цикла статей. Есть только в цифровом виде за 190 р на озоне :(
https://www.ozon.ru/product/dzhoel-o-programmirovanii-141734878/?ectx=0&sh=0sVjyyOV
А как вам вычисляемый GOTO в фортране ? Нет, если его использовать в стиле Switch-Case, то вроде норм. А если метки раскиданы по программе....
On Error Resume Next (классический VB 6)
Я могу ошибаться но это не дефолтное поведение, которое явно завдавалось в коде, примерно также как try-catch. Поэтому не совсем понимаю проблему-с таким же успехом можно ругать например пустые try-catch.
в Lotusscript есть всё это, добро пожаловать динозавры ))))
Выскажусь в защиту On Error Resume Next (хоть и с болью вспоминаю VB6). Это просто инструмент, который можно грамотно пользовать, а можно и в колено себе выстрелить... Вы почему то показываете странный (и безусловно опасный) механизм использования - задать эту директиву для процедуры. Обычно так не делалось. Эта конструкция использовалась явно в любом месте кода для того, чтоб следующая строка (или блок) не навернулась. К примеру имеем какую-то переменную String, ожидаем, что там число. Вот перед приведением к числу (что-то вроде CLng или как там его) и давали On Error Resume Next. Но только на тот кусок кода, где это нужно - в моем примере после приведения возвращаем нормальное поведение (On Error GoTo кудаТоТам), после чего спокойно проверяем значение и решаем, что делать дальше.
Если процедура написана в соответствии с принципами, и делает что-то ровно одно — то она как правило небольшая.
>для того, чтоб следующая строка (или блок) не навернулась
Я вам больше скажу — в документации написано, что ее следует использовать при доступе к объектам. Вот прямо так, конкретно. Но если документацию не читать — то конечно, будет опасно.
В принципе я это и имел ввиду - это просто инструмент, его можно использовать правильно (и тогда это отнюдь не "ужасная фича"б как указано в статье), или не правильно (а это может относиться к многим инструментам во всех языках).
следует использовать при доступе к объектам
Но поправьте меня, если ошибаюсь - вроде как не было написано НЕ использовать в других случаях... В любом случае основной мой посыл был - использовать с умом.
Если процедура написана в соответствии с принципами
Ох, видели бы Вы те авгиевы конюшни из легаси... Уж сколько лет стараюсь забыть об этом - а вздрагиваю до сих пор :)
Так я с вами и не спорю, а скорее подтверждаю.
>как не было написано НЕ использовать в других случаях
Нет, не было. Но все равно документацию стоит читать, и нормальное использование выглядит примерно так:
ON ERROR RESUME NEXT
некая строка, обычно обращение к объекту
IF… проверка наличия ошибки и обработка
И в таком виде, ровно как в примере из документации, это не вызывает ровно никаких проблем в применении. Более того, обработчик ограничен процедурой, и если она не смогла обработать ошибку внутри, происходит выход и обработка на уровне процедуры верхнего уровня — т.е. если длинную простыню не писать (а это само по себе плохо) — то такая обработка ошибок как раз локализована.
И да, я видел конюшни. У меня был проект по переписыванию 120 тыс строк VBA. Это то еще развлечение, скажу я вам.
Вообще-то On Error Resume Next тоже при применении с умом был очень эффективным и избавлял программиста от обработки тонн исключений, включая и тех что являются частью нормальной работы программы
Приведите примеры пожалуйста. Потому что исключения при нормальной работе программы это ненормально.
Типичный пример использования 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 ведут себя адекватно.
Какой подход больше всего обрадовал бы пользователя? Обеспечивал бы сохранность данные? А какой взбесил бы больше всего?
4) Дать задание адекватному программисту переписать контролы
Это не опция.
Во-первых, контрол писали не вы. А другая фирма из другой страны, очень даже может быть. Контрол вам дали в форме .OCX-файла.
Во-вторых, у контрола может быть своё понятие о минимальном размере. Если вы его нарушаете, контрол генерирует ошибку. Если даже разработчик исправит это персонально ради вас, другие программные продукты от других авторов, которые использовали этот контрол, поломаются, потому что они наоборот могли закладываться на то, что при таких-то условиях контрол должен сгенерировать ошибку (и они этого ждут).
Это нужно будет менять CLSID-контрола, потому что в идеальном случае CLSID должен быть неким хешем идентичности класса и его поведения.
В третьих, может быть вообще и переписывать нечего и нет никого неадекватного. Контрол не может принять новый размер, потому что при попытке это сделать не хватило ресурсов. Вот он и честно сгенерировал ошибку. Что предложите в этом случае? Не генерировать ошибку, если HeapReAlloc сбойнул, а делать вид, что всё окей?
То что вы сейчас предлагаете выглядит как попытка спрятать баг.
Если вы используете контрол и не соблюдаете контракты на его параметры - исправьте вызовы, если при соблюдении контрактов контрол ведет себя некорректно - нужно чинить контрол, или заменять его. Отправлять в прод сбойный кусок кода плохо в любом случае.
Ваш пример с шириной и высотой окна немного удивил, мне кажется что в такой ситуации делают отдельную функцию, которая возвращает положительное значение или 0 (если язык не имеет встроенной функциональности для подобного, есть ощущение что математическая функция возвращающая наибольшее из двух значений с переданным в один из аргументов 0 должна справиться с задачей, и кажется такая функция есть в VBA), и везде где нужно такое поведение делают ее вызов.
Использовать On Error Resume Next в такой ситуации это как раз игнорировать что на самом деле произошло, а возможно там уже и все окно куда-то делось, и ошибка в самой попытке присваивания, а не в отридцательном результате.
Если вы используете контрол и не соблюдаете контракты на его параметры — исправьте вызовы, если при соблюдении контрактов контрол ведет себя некорректно — нужно чинить контрол,
Такие контракты могут просто отсутствовать, именно так и было для большинства контролов во времена «золотого времени» 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)
{
...
}
Перемасштабирование не удалось потому, что не хватило памяти. Что более умное можно сделать, кроме как игнорировать эту ошибку?
Мне кажется что в случае если кончилась память надо явно об этом сказать и упасть (если изначальных требований к обработке этой ситуации не было), чтобы на этапе тестирования эта ошибка (если она возникает) была обнаружена и было принято решение что делать (увеличивать память на машинах где функционирует система/централизованно отслеживать потребление памяти и вовремя делать что-то чтобы ситуация не возникала и т.п., в любом случае вопрос нехватки памяти не выглядит как вопрос который должен решаться разработчиком занимающимся UI).
Т.е. да, мне кажется что программа должна давать правильные ответы когда находится в корректных условиях, если условия стали некорректными нужно не пытаться хоть как-то что-то сделать, а максимально быстро и максимально явно сообщить о проблемах и прекратить работу. Это в общем случае, понятно что требования заказчика могут быть весьма специфичны, но речь же в статье об опасности конкретной фичи впринципе, о том что вцелом она может пораждать плохо выстроенный код, и даже в каком-то смысле к нему подталкивает, а не о причудах требований конкретных заказчиков.
Это недостижимая утопия. Те же ОС тестируют в хвост и в гриву, а критические уязвимости и ошибки всё равно появляются.
И поэтому вы предлагаете в каком-то смысле прятать возникающие ошибки, чтобы их на этапе тестирования было еще труднее найти? Не понял мотивацию.
Описываемый мной подход позволяет дать юзеру программу, которая, пусть и содержит косяк, позволяет в случае его активизации программе работать хоть как-то. Не терять данные, не прекращать работу программы. Ну не может один из контрол дюжины подвинуться, лучше не будем его двигать, чем обрадуем пользователя инста-вылетом программы.
И всетаки мне кажется предпочтительнее явный вылет, который будет обнаружен на этапе тестирования, кмк в прод не должен ехать код который может иметь подобное поведение
но если автору контрола вздумается, что минимальным размером контрола должно быть 40×80, а не 0×0, ваша страховка внезапно потеряет силу.
Т.е. вы отправляете в рабочую среду код, который не понятно как работает, а чтобы тестировщики ничего не нашли прикрываете это On Error Resume Next, получается так?
Как понять «в самой попытке присвоения»? Присвоение такому свойству значения это вызов обычной процедуры вида
я имел ввиду что у вас что-то вроде NPE - вы пытаетесь вызвать присваивание для поля, когда у вас вместо объекта null (или вызвать метод у не существующего в момент исполнения объекта)
Приведите примеры пожалуйста
Попытаться распарсить строку, введённую пользователем, в число, а при ошибке парсинга (вполне ожидаемой) присвоить дефолтное значение.
Эм, тут тогда условия if then или проверку ввода выполнять, а не On Error resume next делать.
Проверку ввода делает собственно парсер. Только у него дизайн такой, что у него возвращаемое значение -- это распарсенное число, а не код ошибки или монада Either
, потому что в VB "парсер" -- это просто приведение типов в Variant. А если распарсить и привести не выходит -- бросается исключение. Предлагаете перед парсером самому парсить, чтобы уберечь его от ошибок?
On Error Resume Next
как раз и позволяет поставить If Err <> 0 Then ...
на следующей строке, а не улетать в хэндлер ошибки. Фактически, это получается аналог if (Int.TryParse(...)) { ... }
в шарпе.
Выглядит как недостаток языка, а не как достоинство On Error Resume Next
Я и не утверждаю, что это достоинство — это скорее workaround для случаев, когда "дружественный" интерфейс прозрачного приведения типов, рассчитанный на happy path, вдруг становится недружественным при отходе от happy path.
Эта ситуация и в "правильных" языках с исключениями встречается — например, сеттеры свойств как правило кидают исключение при невалидных данных, и если нужно сделать "присвой либо входное значение, либо дефолтное" на множестве свойств объекта (скажем, конфиг читаемый из файла), а полноценного валидирующего конструктора/билдера не завезли, то каждую индивидуальную попытку присвоения проперти придётся обернуть в try..catch
.
Мне описанный сценарий напоминает использование Option/Maybe: проверяем исходные данные, на каком-то участке программы говорим, что скорость и ненарушение потока исполнения важнее раннего выхода, в конце участка делаем "разбраковку" выходных данных.
Ненавидимый многими оператор
goto
позволяет быстро и удобно выбраться из глубоко вложенной структуры
Что-то не понял как это. Что значит "из глубоко вложенной структуры"?
Из любой функции, какой бы длинной она не была, выйти можно через return. Если вложенных функций много - нужно выходить из каждой. А если выйти через goto, то это путь в stackoverflow, и я не про вебсайт. В общем с этой фразой я не согласен.
Все остальные примеры - точно такие же. Практически любую команду можно использовать настолько неправильно, что это нанесет вред и процессу и возможно системе. Так зачем выбирать какие-то отдельные и рассказывать как их использовать неправильно? Или данные примеры прямо являются таки общепринятыми и популярными? Сомневаюсь.
Тот же On Error Resume Next
скорее всего изначально был задуман для использования во время отладки и написания ПО, когда еще не готов весь функционал, и нужно посмотреть как работает дальнейшая часть программы, игнорируя ранние ошибки, а не для использования в продакшене. Ну или если программист юзает в продакшене, то понятно что все риски он берет на себя.
Что-то не понял как это. Что значит "из глубоко вложенной структуры"?
Из вложенных областей видимости, которые образуются стандартными языковыми конструкциями, - циклами, условиями. Допустим, у нас есть трехмерный неупорядоченный массив с данными и нам надо найти ячейку по какому-то там условию. Соотвественно, есть три вложенных цикла, внутри проверка, и возникает вопрос, - как нам прекратить дальнейшие итерации, когда мынашли требуемое. Стандартное средство прерывания, - break, - дает выход на один уровень вверх. Чтобы выйти из нескольких, то приходится вводить дополнительные флаги выхода, выносить эти вычисления в лямбды или отдельные функции (и использовать в них уже return). А у некоторых чешутся руки поставить метку на верхнем уровне вложенности и сделать goto. В embedded C это может прокатить, но если подобное заметят коллеги на ревью в коде на каких-нибудь плюсах, то можно потом и не отмыться.
Что-то не понял как это. Что значит "из глубоко вложенной структуры"?
Вложенные циклы, например. На Java есть операторы break/continue с меткой, на C это нужно делать через goto:
outer:
for(...) {
for(...){
break outer;
}
}
for(...) {
for(...) {
goto exit_outer;
}
}
exit_outer:
...
Это минимальные примеры, уровней вложенности может быть намного больше 2х.
Ну и goto гораздо более гибкий, чем метки циклов. Он может передавать управление почти в любое место. Иногда это полезно, например при реализации итераторов в языках, в которых нет yield-подобной конструкции.
Мало кто об этом говорит, но в 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;
}
Вложенные циклы, например. На Java есть операторы break/continue с меткой, на C это нужно делать через goto:
Так на джава есть, например, garbage collector, а если на С вы делаете какое-то действие вроде аллокации памяти под данные, а потом при выходе из цикла это чистите, то goto это прямые утечки памяти.
Тот же 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
Т.е. это просто способ вставить (или забыть вставить) обработку ошибок в том же месте процедуры, а не отдельным блоком где-то далеко.
Вообще-то выглядит откровенно некорректно, потому что в результате err и erl оказываются хранителями _последней_ случившейся ошибки, которая может быть ни о чём, потому что индуцирована кривыми данными и прочими нарушениями — а не первой, которая это всё вызвала. Не должно такое продолжаться, ошибка должна быть или обработана снаружи, или замолчена каждая индивидуально, с явным обходом с необходимыми действиями (типа подставить умолчание).
По сравнению с этим — неудивительно, что try-catch начал побеждать на многие годы (несмотря на многословность).
Что значит "из глубоко вложенной структуры"?
Из глубоко вложенного цикла, например. В языке может не быть конструкции "break label".
Из любой функции, …, выйти можно через return.
Но в языках без RAII и сборки мусора нужно будет перед return освободить все ранее выделенные ресурсы. Если не использовать goto, то придётся копировать код освобождения перед каждым return и помнить исправлять все эти места, когда набор выделяемых ресурсов меняется.
Практически любую команду можно использовать настолько неправильно, что это нанесет вред и процессу и возможно системе.
А можно ограничить себя в "плохом" использовании goto: не покидать границы функции, прыгать только вперёд, делать это только в исключительных ситуациях.
В Си и по указателю можно выйти в неведомые дали, особенно имея талант.
А в VB иначе работало все же, там интерпретатор позволял выходить так из любой (или почти любой) задницы "вполне законно".
Я вас умоляю. Читайте вот этот коммент до просветления:
https://habr.com/ru/post/582566/#comment_23578554
Ну а в том комментарии кстати кажется упущен еще 1 момент для полноты, а именно опция сбора итоговых ехешников не только в байткод (который был медленнее + на раз-два декомпилировался без защиты), но и в натив со своими особенностями.
Плохо читали — ничего там не упущено. Оно скрыто под спойлером «Немного подробностей о том, как происходит компиляция в Native-код», внутри которого есть свой под-спойлер «Интересный вопрос касательно IL на входе бэкенда C2».
Ну и поскольку она при работе интерпретирует байткод (который так вот круто сгенерирован IDE еще в процессе написания), то это все же интерпретатор.
В Википедии приводится вот такое определения термина «интерпретация» в контексте ЯП:
Интерпрета́ция — построчный анализ, обработка и выполнение исходного кода программы или запроса, в отличие от компиляции, где весь текст программы, перед запуском анализируется и транслируется в машинный или байт-код без её выполнения[4][5][6]
Источниками такого определения назван не Вася Пупкин, а литература, написанная авторами, заслуживающими уважения:
- Першиков В. И., Савинков В. М. Толковый словарь по информатике / Рецензенты: канд. физ.-мат. наук А. С. Марков и д-р физ.-мат. наук И. В. Поттосин. — М.: Финансы и статистика, 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 (Великобритания).
Так что в соответствтии с общеприянтым определением это не интерпретация, а компиляция и исполнение байт-кода виртуальной машины. Видимо многим просто нелегко принять факт, что презираемый очень многими инструмент с репутацией «для чайников в программирование» имел крутейший и непревзойдённый механизм патчинга скомпилированного байт-кода по живому. «Ну не может же такого быть, что оно вот так легко на лету перекомпилирует и перекраивает скомпилированную процедуру, в данный момент исполнявшуюся — значит там под капотом банальная интерпретация» — так видимо рассуждало большинство.
"нтерпретация байт-кода" для режима P-CODE, потому что в таком режиме библиотека машины все равно интерпретирует его по ходу выполнения
Тогда скомпилированные Java-программы работают путём интерпретации Java-байкода, дотнетовские программы работают путём интерпретации MSIL-байткода. Да и процессоры интерпретируют машинный код.
отому что в таком режиме библиотека машины все равно интерпретирует его по ходу выполнения в отличие от режима компиляции в натив,
Можно было бы говорить «в отличие от режима компиляции в Native», если бы суть обработки P-кодных инструкций заключалась бы в том, что виртуальная машина P-код переписывала бы в Native-код (генерируя для каждой пи-кодной инструкции дюжину машинных), а потом отдавала бы сгенерированный Native-код на выполнению процессору.
Но она же не так делает. В том большом комменте описано, как она это делает.
Коротко и упрощённо говоря: для каждой P-кодной инструкции есть свой хендлер, опкод P-кодной инструкции является индексом в таблице указателей на соответствующие обработчики.
Приведённое выше академическое определение понятия «интерпретор» настаивает на том, что входными данными для него является исходный текст программы, а обработка происходит построчно.
В данном случае нет ни построчности, ни исходного текста в роли входной последовательности для виртуальной машины.
В теории можно было бы вообще создать архитектуру процессора, которая бы машинно умела исполнять VB-шный P-код. Ну какая же это интерпретация?
Я смотрю, пока я писал, вы перекроили свой коммент.
или например нативные вызовы 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-исключения для работы своего механизма ошибок).
Банальные утечки памяти или незакрытые handle. Один из сценариев когда goto мог создать проблемы несмотря на безопасную среду.
Ну так и файл, открытый родными средствами средствами VB, не будет закрыт при выходе из процедеры. И оконо, показанное из процедуры, тоже не будет закрыто.
А если обернуть в класс хоть получение хендла через WinAPI, хоть открытие файла родными средствами — тогда экземпляр класса будет правильным образом уничтожен (если других ссылок нет).
В общем, деланье чего-то через WinAPI тут приведено зря, потому что в связанных с этим проблемах оно ничем не отличается от октрытия файлов родными средствами.
Ну и к этому относится и использование "хаков" вроде прямых вызовов управляющих функций вирт. машины чтобы что-то там оптимизировать, все это один и тот же сценарий проблем с goto по сути.
Что за управляющие вызов виртуальной машины? Имплементации P-кодных инструкций не экспортируются — вызвать их нельзя. Да и функциями эти имплементации не являются.
Из управляющих можно назвать разве что ProcCallEngline/ProcMethEngline — эдакие гейты между native-кодом и P-кодом — позволяют вызывать P-код из native-среды.
Но не представляю, каким образом их использование совместно с goto может нести какие-то проблемы.
Вы немного ошибаетесь, уважаемый. AV, по большей части - это попытка обращения к объект уничтоженному или не созданному. Обращение к несуществующему объекту не портит память, а значит работоспособность программы не под угрозой.
Т.е. обратившись к объекту и отловив эту ошибку мы можем смело продолжать работать, естественно, понимая все последствия выполнения текущего блока кода.
Я ни в коем случае не защищаю того человека, однако нужно всё же понимать всю суть прежде чем делать подобные выводы.
Обращение к несуществующему объекту не портит память, а значит работоспособность программы не под угрозой.
Обращение к несуществующему объекту, как запись, так и чтение, с достаточно большой вероятностью совершенно безобразно портит память. После этого невозможно гарантировать абсолютно ничего. Если мыслить радикально, то программа, обратившаяся к несуществующему объекту, должна быть завершена немедленно.
Пример:
Допустим, есть указатель *a на элемент связного списка A. Мы удаляем A и создаём какую-то другую структуру данных, например таблицу на основе сбалансированого дерева В. Внезапно оказывается, что какая-то часть B, например указатель на первый элемент left_child некоторого узла, размещён по указателю *a.
Предположим, что мы пишем в *a что-то. На самом деле это "что-то" уходит в left_child, и дерево незаметно портится - прочитать уже его нельзя. Данные потеряны.
Предположим, что мы читаем из *a, ожидая увидеть, очевидно, элемент списка A. На самом деле данные считывается указатель на узел дерева, который бессмысленно интерпретировать как элемент списка. Данные искажены.
А дальше проблемы нарастают. Мы можем случайно испортить указатель на функцию. Мы можем считать не тот указатель на деструктор. Мы можем попортить структуры, например, менеджера памяти процесса, и что произойдёт дальше, предсказать трудно.
И только часть таких проблем будет отловлена AV. Возможно, AV посыплются чуть позже, когда данные уже сильно повреждены, и возможно были записаны в файл или в БД в повреждённом виде.
В вашем примере АВ не возникло и успешно записались данные по ложному указателю. Но вы также считаете, что в программе всё ок. Разве нет?
АВ не позволит записать данные по ложному указателю молча, а следовательно не испортит память.
АВ - это отловленная ошибка, которая может сказать гарантирует, что память ещё цела. Память будет испорчена лишь когда исключения не произойдет.
Но вы также считаете, что в программе всё ок. Разве нет?
Я не считаю, что в программе всё ок. Видимость, что всё ок - да (особенно если не проверять целостность структур).
АВ - это отловленная ошибка, которая может сказать гарантирует, что память ещё цела. Память будет испорчена лишь когда исключения не произойдет.
AV не гарантирует, что ошибок не было. Они могли пройти без AV. Напротив, AV гарантирует, что программа написана достаточно криво, чтобы допускать ошибки доступа к памяти. А значит, при первом AV логично предположить, что это не первая ошибка программы, а лишь первая отловленная, и память, возможно, уже повреждена. Я много раз встречался с такой ситуацией, когда ошибки доступа повреждают не те данные, с которыми сейчас работает код, а другие, и AV возникают отложено и совсем в других местах.
Вот уж скорее стоило бы про убогость современных написать наверно, типа 3 наиболее идиотских модных языка)
ИМХО интересная идея. Напишите!
При этом я не за goto, т.к. в середине 1980х мне пришлось разбирать ассемблерный листинг (бумажный) в 10 000 строк для нестандартной графической станции ЕС ЭВМ.
Согласен:
и вполне себе обходиться без goto
без goto — "далеко не приговор".
PS Знаю и люблю ассемблеры PDP-11, Macintosh Classic, IBM PC AT, OS 360/370, Pentium 4, но без излишних goto.
Ну, я бы так бы сказал — goto это инструмент, так же как и язык в целом. И если вам для вашей задачи удобно будет его применить — то такое решение вполне возможно будет верным. Скажем, если вам нужно реализовать FSM, то реализовать стрелки графа как goto — вполне себе решение (утрирую, но думаю смысл понятен). Особенно если это решение будет сгенерировано.
Согласен. Но когда код исправляют много раз, вводя goto, то код становится спутанным клубком.
На это коллеги мне ответили, что на слово «спагетти» например, не очень хорошая ассоциация, по этой самой причине. Но — если вы посмотрите, как спагетти выглядят в магазине, то вы увидите, что в упаковке они идеально ровные, а клубком становятся только когда их сварят. А еще бывает скажем паста фетуччини «птичьи гнезда», где уже в упаковке все спутано в один большой клубок.
Я это к тому, что любое блюдо надо уметь готовить, а то переваришь :)
Есть такой закон — базовые принципы топологии (см. выше).
По-моему, оно и то ближе к обсуждаемой теме.
На строке M в проге стоит метка, на строке N — переход на эту метку. Где здесь неопределённость? — Всё однозначно определено.
Да, и безо всяких топологических проблем, если их явно не вводить.
Правила типа: переход назад только на начало тела цикла, иначе только вперёд — и ваши зубы становятся белыми и пушистыми ™.
Скажем, если вам нужно реализовать 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 в фортране или алголе, который может указывать почти в любое место в программе.
Так я не настаиваю на конкретно таком решении. Наверное декорируют — потому что с переходами связаны какие-то действия, то есть код тут не goto в буквальном смысле.
Я имею в виду, что если у вас goto логично получается из какой-то модели, например FSM (и в идеале не строится руками, а генерируется из другого представления) — то у этого кода есть вполне определенная понятная структура. Ну т.е. тут goto как раз не «может указывать почти в любое место в программе».
Дийкстра, насколько я помню, критиковал goto за сложность понимания и построения выводов о коде, и предлагал взамен структурное программирование, где у куска кода есть один вход и один выход, а куски эти — последовательность, if, цикл. В принципе, вполне можно себе представить и более сложные варианты блоков, при условии что они будут как-то компоноваться в единое целое.
VM/SP - это в те времена уже S/370 или S/390. В остальном все верно, десятки и сотни тысяч строк кода IBM - читается очень легко и быстро (подозреваю, что это легкость достигается за счет того, что исходный код IBM OS VM/SP написан на PL/S) и во-многом благодаря развитым макросам. Даже название IBM ассемблера было заменено на HLASM (High Level ASM).
Ненавидимый многими оператор
goto
позволяет быстро и удобно выбраться из глубоко вложенной структуры, если пользоваться им с умом.
Лучше сначала код с умом писать. Глубоко вложенная структура = высокая сложность кода. Автор предлагает приправить его ещё и лапшой goto
. Как решать: вынести эту структуру в отдельную функцию (или класс), сильно сократив уровень вложенности, и заменить goto
на return
, имплементировав тем самым early return pattern
Не везде было возможно/удобно/эффективно. Компиляторы C/C++, например, массово доросли до поддержки правильного инлайнинга в этом случае только в 2000-х, и в inline сложно переносить много параметров. Лямбды без замыканий, которые позволили писать такие вложенные функции, в C++ появились только в 11-м стандарте. В C их до сих пор нет (расширение GCC не считаем). В других языках — по обстановке, но в большинстве вроде тоже нет эффективной реализации.
> приправить его ещё и лапшой goto
_Лапши_ как раз нет, это у вас полемическое преувеличение. Выход по goto в позицию сразу за концом цикла читается беспроблемно.
Во многих стандартах кодирования с C допускается переход по goto только вперёд. Это избавляет от «лапши», одновременно допуская подобные приёмы.
Конечно, лучше было бы как в Perl и Java — метка на операторе цикла и break/next на неё — но, увы, не везде завезли.
В защиту ALTER - это вполне стандартная фича однозадачного программирования, пришедшая из ассемблера (где хождение по адресам - вообще базовая функция ИМХО). В умелых руках позволяет ОЧЕНЬ сильно экономить объёмы и производительность. Просто не стоит смешивать методы из разных систем в одну кучу.
Собственно, ассемблерный листинг любого if/else/switch и есть "ненавистный goto", только реализованный компилятором.
Абсолютно верное возражение! В стародавние времена мы экономили и размер программы и время выполнения кода. Люди тогда знали и ассемблер и кобол, соответственно, идеи оптимизации плавно перекочевывали из ассемблера в COBOL/PLI/FORTRAN/LISP и т.д. Экономия памяти была очень значительной, при этом код также ускорялся, а единственным минусом была потеря реентерабельности.
И до сих пор огромное количество железа программируется без ОС и нет вообще никакой проблемы динамически менять себе вектора перехода, адреса выхода из прерывания, состояние сохранённых регистров и пр. Просто в одних языках это возможно встроенными способами, в других - через гланды или ассемблерные вставки. А какие возможности открываются на P-SOC-ах!!!!111!!
Но на хабре опасно такое писать, минусят... )))))
Неужели Вы серьезно относитесь к аудитории Хабра? Нет, 10-15%% здесь профи, подстраиваться под остальных - так себе занятие. Я вот давно жду статью с условным названием: "ООП - враг серьезных программных проектов. Функциональное программирование - пусть не лучший, но выход из тупика ООП". Очень хочется изучить поведение хабровцев, как реакцию на эту статью.
Про ALTER я не понял, чем оно принципиально отличается от
let func = path1;
if (wsX == 1) func = path2
else func = path3;
func();
?
Гы, минуснувший явно не знает что такое метка директивы JMP с точки зрения адресного пространства, где находится и как её можно динамически менять. А есть же ещё и RET ))))
Просто оставлю это здесь, чтобы не повторяться https://habr.com/ru/company/alfa/blog/587536/comments/#comment_23672460
В вашем примере явно видно, что func это указатель, надо только прочитать код.
С alter goto каждый goto может оказаться изменённым. При том, что это используется в менее чем 1% кода, читателю следует опасаться каждый раз, где оно того не стоит. Подобное притупляет бдительность и в результате пропускается реальная опасность.
Если бы те goto, которые можно менять, требовали явной пометки alterable, жалоб было бы в разы меньше.
Очень напоминает ситуацию с [[fallthrough]] в C++. Сколько (десятков) лет прошло до его появления?
Исправлять, да, надо. В новых языках таки это лечат…
Как я помню - VB нет (типа того) возможности проверить существование переменной. И единственный способ - это сделать ф-ию isExist(переменная)
on error resume next
//проводим операции над переменной и возвращаем true
//или падаем от ее отсутствия, но из за on error - не падаем а переходим ниже
//return false
А как переход по goto позволит не пропустить еще какую-то проблему? Он просто позволит обработать ошибку в другом куске кода, а что мешает её обработать сразу, без скачков, и в дальнейшем отключить игнорирование ошибок? Во многих случаях последовательный код будет нагляднее.
On Error Resume Next
происходит вторая ошибка, то управление переходит к следующей строке — как и после первой. Таким образом, ошибка в обработчике ошибок рискует остаться незамеченной.Когда после
On Error GoTo ...
и до Resume
или выхода из процедуры происходит вторая ошибка, то VB вызывает обработчик выше по стеку, а если такого нет — то остановку программы. В более старых бейсиках ошибка в обработчике ошибок приводила к безусловной остановке программы.Все так, но положа руку на сердце, признаем, что количество кода между "On Error Resume Next" и возвратом к обработке ошибок "On Error GoTo err_label" или отказом от неё "On Error GoTo 0" определяется только опытностью разработчика. Опытный ограничится парой стандартных строк проверки, в которых ошибку ожидать глупо, неопытный просто не воспользуется ни одним из вариантов. Нужно ли за это гнобить "On Error Resume Next"?
Option Explicit
справляется с обнаружением необъявленных переменных гораздо лучше.Как ни странно, в коллекциях VBA нет встроенной проверки наличия элемента по ключу (раньше по крайне мере так было), и народ выкручивается обработкой ошибок. Может это припомнил @shornikov?
У большинства людей ножи дома тупые, незамеченные. Это ничего не говорит о том, что нож - отстой. Нет, их используют и ухаживают за ними не так, как это надо-бы. Но для большинства это норма. Если многие из людей верят в свои возможности при например покраски чего либо, получают пии этом сходительный результат, сами обмазаны краской, вокруг себя мноие вещи попортили - это же проблема не в краске и кисточке. Как проф. Преображенский сказал -разруха в головах.
Так и с языком. С какими инструментами большинство тех, кто программирует и превращает код в говнокод - не важно. Это уже 'талант' этих программистов.
И в говнокоде таких программистов всё, не только goto используется неправильно, но и циклы, и объявление переменных, и постройка функции или классов. Goto- это одна из тысячи вещей, которыми плохие программисты не умеют пользоваться.
А от себя ещё добавлю. Чем больше в лес, тем больше дров. В последнее время сталкиваешся с говнокодом, говноархитектурой, говноинтерфейсами, говнополцессами итд. всё больше и больше. Как с ножами в моём первом примере.
On Error Resume Next
Не полный аналог, но что-то похожее в Java:
try{
...
} catch (Exception e){
}
Несколько раз нехило так подгорало, когда убивал день, а то и больше из-за того что исключение проглатывалось и программа вела себя очень странно, но в логах все чисто и никаких вылетов нет. К счастью, в современном коде такое нечасто встречается, сейчас обычно либо IDE на такое ругается, либо если ее предупреждения проигнорировать, то реквест в репозиторий не принимается из-за анализаторов вроде сонаркуба.
On Error Resume Next
— оператор превращения языка в JavaScript.
Почему не в «Си или C++ в стиле Microsoft»?
При вызове WinAPI вы обязаны проверять GetLastError cами.
При вызове COM-методов вы обязаны проверять HRESULT сами.
errno
тоже нужно проверять самому.Как это элегантно сформулировать?
«В Си в стиле WinAPI или POSIX или подавляющего большинства паттернов обработки ошибок в Си или С++ в стилке Microsoft»?
Что характерно, ядро Windows написано на Си, но там очень много где (если IRQL позволяет) используется расширение try...except для обуздания SEH.
Кто говорил о кросс-процессности?
Речь шла совершенно о другом.
Недавно тут была статья «Как понять, чо вы пишите не на C++, а на Си с классами».
Так вот, если вы пореверсите продукты MS, написанные на C++, да хоть тот же обсуждаемый здесь VB6, то откроете для себя, что внутри MS для своих же продуктов С++ используется как «Си с классами».
Они не используют STL или сишный механизм исключений внутри своих продуктов. Не на уровне API ОС, не между разными DLL-библиотеками, а внутри одной программы/библиотеки они это обходят стороной.
Вспомнилась древняя многостраничная тема на 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
"On Error Resume Next" живее всех живых в shell скриптах.
On error resume next.
Ничего особо ужасного. Всего лишь первая реализация полезной возможности для крайне редко возникающей потребности - необходимости продолжения работы программы в любых условиях вне зависимости от возникающих ошибок, когда остановка программы по ошибке - однозначно плохо, а при продолжении работы может что-то полезное и выгореть, а если вдруг что-то пойдет не так, то будет не хуже, чем при остановке выпоьнения.
С другой стороны, в современных языках программирования есть более удобные средства для реализации подобной живучести программы, а потому помянем добрым словом и отправим в морг.
DoEvents.
Подобные вещи есть и в Delphi, и даже в QT. Тоже ничего страшного. Полезно для предотвращения ненужных усложнений в коде ради прорисовки интерфейса при длительных операциях (например, той же длительной отрисовки других частей интерфейса. Надо только понимать, как оно работает, блокировать элементы интерфейса, использование которых может вызвать конфликт, и таки не использовать без нужды.
Динамический оператор перехода в Коболе.
А вот это уже странно. Интересно, чего пытались этим вообще добиться? Все, что дает этот оператор, реализуется другими средствами более очевидно и понятно. Разве что там есть что-то специфичное вроде ограничения на длину перехода или количество операторов в программе, и этот трюк банально позволяет иногда как-то выкрутиться.
Костылём здесь может являться сама стековая реализация исключений, но это совершенно допустимый код. Факт его наличия не говорит, что объект Err потом не исследуется и не осуществляется соответствующая диагностика. Как бы вы себе видели это иначе (без использования maybe монады)? Сделать rethrow исключения, которое уже возникло, а потом его отдельно ловить или просто проверить Err после выполнения функции? А вообще "silencing" часто помогает централизовать обработку ошибок, чтобы достичь хорошего SoC и не тащить в математичекую функцию логгеры, диагностику и т.п.
Во-первых, никаких 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 { })). Что суть тоже самое
Совершенно нормальный подход для FSM или акторных моделей обработки.
На lvl 3 у вас уже скорее всего какие-нибудь реактивные стриминговые асинхронные модели, которые забудут про BackgroundWorker вообще и изредка будут делать DoEvents, только это будет скорее WPF и увидим мы какие-то замысловатые Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })). Что суть тоже самое
Современный аналог DoEvents — это await Task.Yield();
, а никак не то что вы написали.
Совершенно нормальный подход для FSM или акторных моделей обработки.
Нет, это ненормальный подход, поскольку он не позволяет создавать несколько экземпляров конечного автомата или актора.
Современный аналог 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 в том числе для потокобезопасной разработки? И ваш вопрос превращается в "не позволяет создавать несколько экземпляров функции".
Начнём с того, что речь шла о языке 59 года рождения
А закончим тем, что сейчас уже 2021й год, и у нас доступны не только упомянутые вами "computed goto", "branch table", "jump tables", но и паттерн State из ООП, асинхронные сопрограммы, паттерн-матчинг и даже акторные фреймворки. Каждый из этих вариантов лучше так называемого "совершенно нормального подхода для FSM" родом из 1959 года.
Я же говорю, вы своим авторитетом растоптали всех разработчиков и исследователей, короткий список из которых я привёл. Ребята из Google или Tarantool заламывают руки, что в далёком 1959 они не догадались использовать "паттерн State из ООП". Команда наиболее актуальной VM для Erlang, языка известного тем, что он само воплощение actor model, уволилась всем составом. При такой диспозиции кто я такой чтобы с вами спорить, конечно я капитулирую.
Разработчикам из 1959 года простительно не знать про ООП и паттерн State. Но мы-то в 2021м году живём, и вы именно в 2021м году назвали тот древний ужас "совершенно нормальным подходом".
Это вы, а вовсе не я, пытаетесь перечеркнуть и растоптать 62 года развития индустрии.
Извините, что не разжевал всё до мельчайших деталей. Понимаю, поиск это сложно: - Python. Актуальный master, разжёвано донельзя, утверждают, что "computed gotos" быстрее на 15-20% - - Dalvik VM. Актуальный master. Также всё расписано. - Tarantool. Актуальный вики содержащий в том числе графики сравнения. Был представлен на Google Summer of Code 2021.
Вот такой вот 1959. Ой, простите за сарказм. Это всё 2021. Там, кстати, ссылки, на них можно кликать. Если вдруг вы не только поиск не умеете.
Я думаю вам не составит труда предоставить какие-то бенчмарки со своей стороны, коль скоро вы столь безапелляционно заявляете о доминировании, прости меня бог Computer Science, паттерна State.
Разработчики 59 года ООП придумали и реализовали, чтобы вы сейчас могли рассуждать о развитии индустрии. А разработчики 21 года, чаще всего, не понимают, что программируют не только винду/PC.
Тем не менее, в 21м году для любой современной платформы существуют языки программирования, в которых можно использовать что-то кроме конструкции ALTER… TO PROCEED TO для выбора перехода.
Да не требует она мощного процессора. Китайцы делают u8 за 30 центов, примерно аналог attiny2313. Мигай не хочу. И это дешевле, чем транзистор с обвязкой.
Вот если чтобы помигать нужно андроид/винду накатить сначала, тогда да, тогда всё хуже.
Любая конструкция (даже HTML) в конце концов станет машинным кодом, который прямое воплощение имеет в ассемблере. Поэтому вопрос не в том, имеет ли язык конструкцию, а в в том, позволит ли компилатор или ОС добраться до нужных ниточек. Выше я писал как такое делается, меня заминусили, поэтому дам ссылку на точно такой же комент, но ещё пока не заминусованый https://habr.com/ru/company/alfa/blog/587536/comments/#comment_23672460
Что-то комментарий по вашей ссылке не открывается.
Нет, вопрос именно в конструкции. Она просто не нужна в ЯВУ.
Надо открывать в новой вкладке. 21 век на дворе, понимать надо.
Не знаю что вам там не нужно, мне в опытах над андроидом пригождалось. А потом они закрыли дырку. Но над 4.4 ещё можно отводить душу долгими зимними вечерами.
Нет, дело не в новой вкладке, а в разнице между форматом ссылок на комментарий между новой и старой версиями UI хабра. У меня вот ссылка тоже правильно не открывается если не удалить из неё блок "/comments
".
Зато вот так должно работать:
[Тыц](#comment_23672460)
Результат:
Тыц.
Если она пригождалась вам — так расскажите зачем, а то одни общие слова у вас.
Что же до того комментария на который вы сослались — то там опять рассказывается про аналог computed goto, а не про ALTER… TO PROCEED TO
Буду груб. Про DoEvents автор абсолютно не вкурил в суть. Если он рукожопил с DoEvents, то и без DoEvents у него получалось бы такое же рукожопие.
Суть в том, что существуют длинные операции. И эта длительность - часть взаимодействия с пользователем. Которая состоит в том, что пользователь должен ждать.
А вот как именно пользователь должен ждать, - тут могут быть варианты.
Самый простой из правильных вариантов - заблокировать пользователю ввод. А какой ввод у оконных программ? Это любые значимые действия с окнами. Закрыть, потыкать в кнопки, вот это всё... Это - в чистом виде модальное ожидание.
А где ещё у нас такое ожидание встречается? Баа, да в модальных диалогах же! Пока диалог не закрыт, нельзя ковыряться в других окнах программы. При этом, что интересно, цикл прокачки сообщений в программе не заблокирован. {Точнее, мы в одном месте цикла вошли в некую функцию, и там запустили новый цикл прокачки. Но не просто запустили... а} аккуратно задисаблили окна, кроме модального диалога. А в обработчиках, завершающих диалог, раздисаблили обратно. (Ещё точнее говоря, модальный диалог не обязан делать такие отступления в цикле, как указано в {}, - можно и немодальное с точки зрения системы окно сделать модальным с точки зрения пользователя, только логику придётся размазать по обработчикам).
Ну и чем это отличается от безоконных модальных вычислений? Да ничем, кроме наличия модального окна.
Ну и варианты:
Тупой и плохой: вообще отключить прокачку сообщений, пусть всё замёрзнет. Модальность при этом, естественно, сохранится. Но интерфейс испоганится.
Запаристый: вынести длинную операцию в какой-либо фоновый обработчик (хоть в главном потоке, хоть в отдельном), и на время работы блокировать окно. Шило на мыло, плюс мы добавили логику ожидания и обработку завершения операции.
Продвинутый запаристый: не просто заблокировать окно, но и показать модальный диалог прогресса/прерывания. Нужно будет научиться прерывать операцию. В случае с фоновой работой это дополнительные заморочки в логике при стандартном диалоге, с DoEvents - дополнительные заморочки с диалогом при простой логике.
Супер-продвинутый: научить программу совмещать фоновую работу с деятельностью пользователя. Но это уже немодальные вычисления.
А, да!
Тупой, плохой и запаристый: вынести длинную операцию в отдельный поток, а в главном сидеть и ждать, пусть интерфейс замёрзнет. Зато с многопоточностью!
Тупой, плохой, запаристый и неправильный: вынести длинную операцию в поток - и забыть блокировать окно. Пусть пользователь устроит погром.
По своему опыту - включая опыт консультирования коллег - могу сказать, что непонимание сути в этом вопросе гарантированно приводит в лучшем случае к колхозу, в худшем - к багам. Но виноват, конечно же, микрософт и DoEvents. Кривые кирпичи подсунули.
Хотя ваш развернутый ответ мне больше нравится. В нём причины, обоснование и возможные варианты. Это больше, чем просто расктириковать какой-нибудь command.
Т.е. Yield()
или Sleep(0)
, не могут привести к тому что событие от пользователя не будут обработаны другим потоком? Я давно не писал на MFC/OWL/TV/Swing но насколько я помню в части библиотек только один поток мог обрабатывать события от UI и длинный код в нем вешал приложение даже для вытесняющей многозадачности.
Теоретически, можно написать программу, в которой будет несколько UI-потоков.
В конце концов, просто запуск нескольких экземпляров программы - это запуск нескольких UI-потоков, в разных процессах. Ну, сделаем в одном процессе... Всё, что нам понадобится, это вручную следить за изоляцией данных и взаимодействии бизнес-логики.
Но взаимодействие окон (или, более общо, обработчиков сообщений) через границы потоков становится сложнее. У нас появляются недетерминированные задержки, усложняется протокол взаимодействия... не хочу сейчас закапываться в подробности. Если вкратце и грубо, то система синхронный SendMessage превращает в PostMessage туда и оттуда.
Запутывается граф владения: кто какие окна может добавить/пристрелить в какой момент времени. Пока UI-поток один, то понятно, что всё делается из него (ну или рабочий поток посылает сообщение, а обработчик в UI-потоке находит подходящий момент).
Возникают шикарные возможности для дедлоков на механизмах отправки сообщений.
Когда в однопоточном приложении мы делаем SendMessage в окно, и тамошний обработчик начинает молотить вложенный цикл прокачки, мы в целом не замерзаем.
Когда же в многопоточном, - мы в текущем потоке сделали SendMessage и замёрзли до тех пор, пока обработчик окна, принадлежащий другому потоку, не вернёт квитанцию. А из того обработчика вполне могут послать SendMessage уже какому-нибудь окну нашего потока. А мы замёрзли и не качаем.
Я говорю про винду, где всё сделано на SendMessage / PostMessage, - включая почти все изменения свойств окон. Текст там установить, флажок disabled поднять-сбросить, и т.д. Но в общем виде это справедливо для любых оконных систем. (Просто в потроха того же гнома я не лазал, а в потроха винды - вовсю, потому и говорю про винду).
Не, конечно, существуют приложения, когда многопоточность интерфейса прямо нужна-нужна. Но это редкость. Стараются или однопоточный интерфейс сделать, или многопроцессный с серьёзной изоляцией окон. Или исхитриться, чтобы интерфейс был многослойным: окно браузера, транслирующий события в изолированные рендереры страниц со своими интерфейсными потоками (тоже по одной штуке на страницу).
Ну, дедлоки-то решаются полным отказом от SendMessage, не так это и трудно когда в языке есть сопрограммы.
Полный отказ от SendMessage - это полный отказ от виндоуза.
А как сопрограммы тут помогут?
Если вы использовали SendMessage для вызова к своему коду — то его можно заменить на два PostMessage. Если вы вызывали SendMessage для отправки сообщения стандартному компоненту винды — его можно заменить на два PostMessage и SendMessage посередине (последний будет обращаться гарантированно к своему потоку, а потому никогда не приведёт к взаимоблокировке).
Только что нашёл в winapi функцию SendMessageCallback. Кажется, всё уже реализовано до нас. А сопрограммы помогут не запутаться в колбеках: код с ними останется таким же какой и был, только вместо SendMessage будет стоять какой-нибудь await SendMessageAsync
.
del - продублировалось
Спасибо. Я не прогер, но теперь я понял почему блокируется диалоговое окно при всплытие окошка выбора файла например.
Но когда я вспоминаю об этих запылённых старых реликтах, то осознаю, что некоторые старые идеи настолько плохи, что лучше всего было бы сжечь их навечно.
Хе-хе.
1 — Норм для одноразовых тулз, когда данные заранее известны и достоверны, а единственный пользователь — ты сам;
2 — А если у вас дохлый микроконтроллер вообще без ОС?;
3 — Повсеместно используется при переключении real<>protected mode x86 и прочем низкоуровневом.
старые идеи настолько плохи, что лучше всего было бы сжечь их навечно
Сжечь идею? - Оригинально...
UPD: хотя, да, идеи же на бумаге часто излагают, тем более раньше. Не слыхал, чтоб так переводили напрямую.
Когда читал статью, возникло ощущение, что не с проста были внедрены эти функции. Это же подтвердилось после прочтения всех комментариев и напросился вывод, что автор оригинала просто хайпанул. Ибо: 1) не даром эти функции существовали(ют) и 2) зависит как их применять.
Ну, справедливости ради стоит отметить, что computed goto крайне полезен при реализации интерпретаторов - просто берешь номер опкода и делаешь jump к нужной инструкции, это быстрее, чем switch/case, емнип. Вроде бы в том же питоне можно fallback сделать на реализацию с чистым свитчем, но она медленнее на пару-тройку десятков процентов. Опять же, если я правильно это помню, я давно в исходники питона не заглядывал.
В VB хотя бы явно надо указать On Error Resume Next, а в MS SQL вообще по умолчанию в процедурах часть ошибок игнорируются, и если не включить XACT_ABORT ON можно не хило удивится почему транзакция завершилась без ошибок, а данные не корректны.
Думал недавно про исключения как похожий "динамический" аналог GOTO. Пример в статье можно написать на исключениях как-то так:
def path2():
try:
f()
catch SELECT_PATH:
print("path 2")
def path3():
try:
f()
catch SELECT_PATH:
print("path 3")
def f():
raise SELECT_PATH()
if WS_X = 2:
path2()
elif WS-X = 3:
path3()
Только исключения немного по другому подходят к динамичности GOTO: место, где окажется поток выполнения определяется стеком вызовов, а не значением метки. Думаю, если писать в Continuation Passing Style, то можно из исключений сделать хаос не хуже GOTO.
Но в .NET уже есть простые в использовании инструменты, например, компонент
BackgroundWorker
Складывается ощущение, что статья была написана лет 5+ назад.
В условиях, когда процессоры имели 1 ядро параллелизма быть не может, что вполне оправдывает существование DoEvents(). Пока не появились 2х-ядерные и более процессоры не имело особого смысла создавать поток (а потом синхронизироваться с потоком UI, что по сей день в Forms актуально). Потоки (System.Threading) в .Net существуют с 1 версии, BackgroundWorker со второй (15 лет уже).
Три ужасные фичи программирования из прошлого