Любопытная тема ожила. Актуальность не утрачена, несмотря на старт в 2009 г.
GeoVit писал(а):
ПРИМЕР ВОПИЮЩИЙ!!!
"Правильные" слова:
Madzi писал(а):
Появились исключения не от хорошей жизни, а от упавшего уровня программистов разработчиков, которые вместо грамотного построения программы "ловят" ошибки на выходе.
И ярчайшее подтверждение:
Madzi писал(а):
Пример 1:
try {
a = x / y;
} catch (Exception e) {
System.out.writeln("Деление на нуль.");
}
Пример 2:
IF x = 0
THEN Out.String("Делитель(??????)равен нулю"); Out.Ln;
ELSE a = x / y;
END;
АПЛОДИРУЮ СТОЯ!!!
В примере, видимо, есть опечатка: проверять необходимо "y" (делитель). Но возможно и нет, я не отслеживал весь контекст истоков первоначального сообщения, взято отсюда:
viewtopic.php?f=6&t=1454&sid=1ac07916d41e14df369b44ef0ba0b688#p27247по контексту в начале теме, видимо, речь о том, что проблемы в данном примере выявляются именно при "безисключительном" способе. Плюс события там следующие:
Alexey_Donskoy писал(а):
Давайте уж сравнивать сравнимые вещи. Например:
Вариант 1: try a:=x/y except a:=inf end;
Вариант 2: if abs(y)>=eps then a:=x/y else a:=inf;
Или же:
Вариант 1: try a:=x/y except {значение а не изменяется} end;
Вариант 2: if abs(y)>=eps then a:=x/y;
По-моему, так эти варианты ничего по существу не доказывают. Их использование зависит от задачи. Удобство не сильно отличается. Затраты, в общем, тоже не сильно.
Madzi писал(а):
Примеры показывают, что во втором случае программист заранее предусмотрел "исключительный" вариант. Практика показывает, что первый вариант возникает когда автору некогда/лень разбираться с алгоритмом, который иногда сбоит...
GeoVit писал(а):
Алексей, не увиливайте!
А если Ваши примеры напишут вот так:
Вариант 1: try a:=x/y except a:=inf end;
Вариант 2: if abs(x)>=eps then a:=x/y else a:=inf;
Или же:
Вариант 1: try a:=x/y except {значение а не изменяется} end;
Вариант 2: if abs(x)>=eps then a:=x/y;
Какой вариант будет безопаснее в эксплуатации, а?
Alexey_Donskoy писал(а):
GeoVit писал(а):
Вариант 1: try a:=x/y except {значение а не изменяется} end;
Вариант 2: if abs(x)>=eps then a:=x/y;
Какой вариант будет безопаснее в эксплуатации, а?
То, о чём Вы говорите, называется скрытой ошибкой, которая закрывается "мягкой подушкой" исключения, но в ран-тайме. Такая практика, к сожалению, имеет место, но я бы назвал её категорически недопустимой! Потому что никто заранее не скажет, какие побочные эффекты может иметь сокрытие такой опечатки.
Поэтому второй вариант предпочтительней. Он навернётся сразу.
В общем, в целом здесь основные недостатки механизма исключений. И один из факторов деградации IT-отрасли.
Однако, такова позиция в меньшей степени применима к таким языкам как С++, где широкий пласт "низкоуровневых" исключений аля "Segmentation Fault" не отлавливается блоками try/catch. Сигналы SEGFAULT и т.п. заполучить можно для технических нужд (фиксация проблем и т.д., с помощью "неструктурной обработки исключений"), а MS-компиляторы смогут и через try, но это не стандарт и не кроссплатформенно и не поощряется. Прежде всего, декларируется политика аварийного останова процесса, т.к. выявлено "неопределенное поведение" и в общем случае некорректное состояние программы. Как и политика "assert"-ов в Обероне (если я правильно понимаю не используемый мной инструмент). Пусть это явление будет "паникой" (поскольку здесь выше есть дискуссия о терминах, и я воспользуюсь тем, что вошло в обиход мейнстрима благодаря Go, Rust для отличения "классического" механизма исключений). Проверку многих предусловий в С++ игнорировать проблематично на фоне иных популярных языков с исключениями.
Отсюда, исключения в аля С++ не отменяют потребности (если такова имеется) для реализации мониторов процессов (в дополнение к прикладным, между которыми организуется некий IPC и реализуется политика перезагрузок. Возможна ограниченная runtime-среда и внутри одного процесса и т.п.).
И, согласно популярным представлениям, альтернатива механизму исключений для анализа постусловий или результатов операций есть применение явных проверок кодов возврата или глобальных флагов. Задача не простая, и эта тема форума также косвенно возникла из-за методов её решения. Здесь были и "профильные" темы аля (со ссылками там на смежные):
viewtopic.php?f=27&t=3175Вспоминаются и "семантические редакторы", когда-то активно обсуждаемые на форуме, как проект PureBuilder, где имеется конструкция "progress" и др.:
https://sites.google.com/site/purebuilder/В языке Go ввели кортеж в качестве результата функции, видимо, не в последнюю очередь, чтобы коды ошибок явно наглядно торчали как lvalue.
В общем, допускаем, что обработка кодов результата операций алгоритмически разрешима в том или ином виде. Однако, у "классической" техники "коды ошибок" есть существенный недостаток на фоне механизма исключений -- исключения игнорировать невозможно. Ни компилятор, ни runtime-среда не могут дать гарантии, что все необходимые проверки кодов возврата выполнены. Если есть возможность где-то что-то упустить, то такая возможность обязательно реализуется.
Ещё одна альтернатива -- результат операций упаковывать в контейнер, к примеру, в виде алгебраических типов данных, и компилятор в этом случае вынуждает их оттуда "доставать". Такой подход применяют в Rust:
https://doc.rust-lang.org/book/second-e ... dling.htmlс использованием универсальных комбинаторов аля unwrap, expect, или есть возможность организовать средства под конкретную задачу. Но главная фишка там среди универсальных механизмов -- реализация парадигмы досрочного выхода и "проталкивания" результатов "наверх" -- см. оператор "?". Здесь подробнее:
https://github.com/glaebhoerl/rfcs/blob ... andling.mdгде оценивается задумка внедрения в т.ч. и конструкции try..catch, и других сопутствующих расширений. Фактически, в Rust реализуются монады, но с использованием "синтаксического сахара" (как аля внедрение do-нотации в функциональных языках). Причём в таком случае политика "checked exceptions" (которая провалилась в Java, например) здесь реализуется естественным образом в соответствие с системой типов языка.
Есть ещё один момент на фоне обычных исключений. После обработки исключения в секции catch (except) возможно продолжение кода, где м.б. использованы объекты, состояние которых изменялось в секции try. Т.е. обработка исключительной ситуации должна учитывать возможность доступа к состоянию, модификация которого была прервана. В случае же монадического подхода продвинуться по монаде без анализа результатов предшествующих операций невозможно (там, где эти результаты востребованы), в т.ч. секция "catch" в Rust также всегда возвращает результат. Это конечно же не алгоритмические гарантии, а всего лишь палочка у компилятора, с помощью которой он может лишний раз стукнуть по башке разработчику. Что полезно.
Таким образом, в Rust реализуется политика, близкая к С++. Имеются "паники", которые могут возникнуть где угодно (к тому же они принуждают не лениться и не забывать об инвариантах с проверками). В т.ч. ими вынуждены пользоваться в тех случаях, когда невозможно явно сообщать свой результат (например, в реализации инфиксных операторов, "конструкторов" данных и пр.). И "псевдоисключения" для анализа результатов операций.
Реализация "монад" в Rust пока хромает. К примеру:
enum MyError {}
fn f() -> Result<i32, MyError> { Result::Ok(1) }
fn main() { f(); }
Здесь при вызове f() игнорируется результат, и компилятор лишь выдает предупреждение, а не ошибку. Таковы языковые особенности. Дело поправимое в общем случае, и я не акцентирую на конкретике, а в целом представляю здесь суть самого подхода.
Итак, механизм исключений не безгрешен. В то же время реализация альтернативного равномощного механизма далеко не проста. В качестве перспективного направления видится монадический подход, обкатанный в "функциональщине", и практика языка Rust со временем покажет приемлемость (или нет) в широкой "промышленной эксплуатации".