Путь дятла-2: Как сгенерировать исключение в 1С? #90901


#0 by romix
Это продолжение темы, начатой в Как известно, хороший способ прекратить выполнение программы в случае какой-либо ошибки - сгенерировать программное исключение. Это позволило бы не строить "воронье гнездо" наподобие такого замечательного кода: Если вас все еще радует этот вариант кода проверки заполнения реквизитов, обратите внимание, что это проверка всего лишь трех реквизитов. Но что будет, если в документе (или в справочнике) их окажется, например, 33? Очевидно, что подобный "разлапистый" код может быть "свернут", но как бы сделать это поаккуратнее? В системном языке программирования, таком как C++ или Delphi, мы воспользовались бы исключением (RaiseException или просто Raise). В 1С это может выглядеть примерно так: Попытка Процедура ПроверитьРеквизит должна генерировать программное исключение и выдавать предупреждение, если реквизит не заполнен. Процедура ПроверитьРеквизит(ИмяРекв) Здесь мы генерируем исключение, выполняя деление на 0. Правда, хороший способ вызвать исключение? Другой способ вызвать исключение предлагает нам технология внешних компонент. methRaiseException: Образец внешней компоненты на языке Delphi с примерами ее использования (тестовая конфигурация для 1С:Предприятие версии 7.7) приведен в виде ссылки в конце статьи. Помимо простой проверки реквизита "на пустоту", иногда бывает полезно проверять реквизиты на соответствие маске. В мире Unix широко распространены регулярные выражения (RegExp), перекочевавшие оттуда практически во все уважающие себя языки (включая VBScript, которым можно воспользоваться из 1С). Следующая процедура выполнит проверку строки на соответствие шаблону: ///////////////////////////////////////////////////////////////////////////// Если строка не соответствует указанному шаблону, то процедура вызовет исключение (при помощи метода внешней компоненты RaiseException). Вот как ей можно воспользоваться, к примеру, в гипотетическом справочнике Контрагенты, где надо проверить на правильность поля Email, Телефон и Факс: Пользователь, который попытается ввести неподобающий электронный адрес электронной почты или телефон (например, без телефонного кода города/страны/сотового оператора), получит предупреждение об ошибке, и будет вынужден продолжить редактирование карточки контрагента, не имея возможности, случайно или по незнанию, нанести ей непоправимый ущерб. * * * Существует одна проблема: как мы будем отличать "наши" исключения от "не наших"? Для примера допустим в коде небольшую ошибочку: ///////////////////////////////////////////////////////////////////////////// Документ просто не будет сохраняться, не выдавая никаких сообщений. Так что если мы не просто проверяем реквизиты, а выполняем некий код, который может сбойнуть, то нам необходимо выявлять подобные ошибки и делать с ними что-то еще.        Исключение ОписаниеОшибки и ВызватьИсключение - это встроенные метод и оператор 1С соответственно. Немного неуклюжий способ (необходимо анализировать подстроки), но, тем не менее, он позволяет отличить исключения, сгенерированные "нашей" DLL, от всех остальных. В других языках мы использовали бы разные классы исключений. Чтобы сделать его более "уклюжим", можно поместить его в процедуру: При этом мы наглым образом обходим ограничение оператора ВызватьИсключение, который не может быть вызван вне блока Исключение ... КонецПопытки; В заключение хотелось бы отметить, что в различных источниках можно встретить советы наподобие следующих: "Не следует использовать исключения для обычных или ожидаемых ошибок или для обычного потока управления" (с) MSDN. "Используйте исключения только для исключительных ситуаций" (с) Керниган и Пайк. На это можно сказать только одно: "Если на клетке с буйволом написано "Дятел", то не верь глазам своим". Конечно же, эти замечания по стилю - не запрет на использование любых исключений вообще. В действительности, это требование, чтобы поток управления, в котором не происходит никаких ошибок (в нашем примере - пользователь ввел все данные верно) не выдавал никаких исключений. Керниган и Пайк приводят такой пример: "Так, при чтении файла рано или поздно будет достигнут его конец; это должно отрабатываться посредством возвращаемого значения, а не исключения". С этим очень трудно не согласиться. Пример с несушествующим файлом уже менее категоричен - вместо явного запрета мы видим слова "вряд ли стоит": "Вряд ли при неудачной попытке открыть файл, например, стоит возбуждать исключение.  Последние лучше оставить для действительно непредвиденных случаев вроде отсутствия свободного места на диске или ошибок арифметики с плавающей точкой". Файловых ошибок в действительности не так мало: это не только "нет файла", но и "нет права доступа", "диск только для чтения", "ошибка ввода-вывода", "нет места на диске", "ошибка сети"... Какие из них более непредвиденные, а какие менее - сказать трудно. Поэтому те же библиотеки Microsoft выдают исключение в случае всех возможных ошибок, в дополнение предоставляя функции наподобие FileExists. Эта функция позволяет разработчику проверить факт существования файла, поскольку "обычная", "ожидаемая" логика работы программы может предполагать как наличие, так и отсутствие файла (например, еще не поступила выгрузка из другой программы, и надо ее подождать). Использовать в этом случае исключение будет не совсем правильно, в особенности, если вы захотите вести протокол системных ошибок программы и, к примеру, автоматически посылать его себе по электронной почте. Пример внешней компоненты 1С с открытым исходником на Delphi, а также тестовая конфигурация, которая показывает, как этим можно воспользоваться, доступны для скачивания по адресу (96Кб).
#1 by romix
Поправка: вероятно, будет не очень правильно высвечивать сообщения или особенно модальные окна при всякой генерации исключения. Будет достаточно и такого кода:     except        on E: Exception do begin Здесь ErrorMsg - сообщение об ошибке, которое должно быть доступно при работе с внешней компонентой. Самое главное - CallAsFunc := E_FAIL;
#2 by 427
1С - это набор частных решений...
#3 by romix
Ну да, 1С - набор частных решений (что, на мой взгляд, - очень хорошо), но почему там нет нормальной генерации исключений, понять трудно. Наличие исключений означает более аккуратный код контроля всевозможных ошибок, и повышает вероятность, что всякие "занятые люди" будут таки контролировать ошибки вообще. А в чем тут может быть опасность, я вообще не могу въехать. Керниган и MSDN абсолютно правы, когда запрещают применение исключений вне ошибочных/нештатных ситуаций. Я так понимаю, что лог ошибок должен быть как можно меньшим (в идеале - пустым). Но проверок, которые никогда не сработают, должно быть, напротив, предельно (экстремально) много. Я очень удивился, когда понял, что практически полные проверки, какие только удается придумать, лишь слегка увеличивают объем кода.
#4 by romix
Файл примера был обновлен в соответствии с . На народ.ру какие-то глюки (не дает скачать файл), поэтому работающая ссылка - ? (в конце - вопросик).
#5 by orefkov
В 1С++ есть нормальная работа с исключениями. Можно програмно генерить исключение, указывая строку-описание и передавая дополнительно любой объект
#6 by romix
То есть, я правильно генерю исключения? А то в доке по ВК ничего про это толком не написано... А как это передавать объект - это только 1С++ или 1С-ный объект?
#7 by orefkov
В 1C++ можно передавать любой объект, как исключение. те там у объекта "ВыполняемыйМодуль" есть метод "ВызватьИсключение(Описание,Объект)", и "ПолучитьОбъектИсключения", который можно использовать в обработке исключения.
#8 by Сказочникс
(romix) А что тебе мешает в глобальном модуле сделать обработку реквизтов документа через метаданные, а из документа передовоать только контекст и париться не надо со всякими с+++ и т.д.
#9 by gg2
Я делал вызов исключений без внешней компоненты но не через 1/0 а через СоздатьОбъект, т.к. это позволяет отличить свои исключения от не своих, описание ошибки будет что то вроде такого. Не найден объект (<здесь то что было передано в создать объект>) Опасность использования исключений это во первых можно отработать не свои ислючения что не очень хорошо. Во вторых через попытку можно уйти за пределы выполняемой процедуры, это все равно что оператор перейти, только перейти не позволяет выбратся за процедуру, а попытка позволяет. В третьих есть глюк с транзакцией, например НачатьТранзакцию; ... ЗафиксироватьТранзакцию;  //<-- то здесь произойдет ошибка Как обойти эту ошибку я пока не нашел. P.S. Какой злодей занял мой ник
#10 by romix
В примере по ссылке в я так и делаю: глобальная функция у меня проверяет реквизиты документа или справочника через контекст и метаданные. А цель генерировать исключение - чтобы вызывающий это дело код стал короче и "прозрачнее" (причем, намного). А конечная цель (вы спросите: ну, короче, ну и что?) - повысить вероятность, что ошибка будет проконтролирована разработчиком вообще. Иначе может быть просто влом, и на практике мы встречаем массу проектов без тщательного контроля проблемных ситуаций. Это оборачивается большой потерей времени при попытке (например, удаленно) диагностировать у заказчика проблему, и недовольством заказчика 1С в целом. Исключение не совсем то же, что goto; в любых языках оно умеет выходить из множества вложенных процедур, и при этом корректно восстанавливает стек. С транзакцией - это вроде не ошибка, потому что реальная запись в базу происходит именно при ЗафиксироватьТранзакцию, а до этого все пишется в буфер в ОЗУ (кстати, эта особенность транзакции позволяет резко - на порядок - ускорить множество операций записи).
Тэги:
Ответить:
Комментарии доступны только авторизированным пользователям

В этой группе 1С