gifts2017

Неблагодарное это дело – выдавать сообщения об ошибках

Опубликовал Илларион Пак (pakill) в раздел Программирование - Практика программирования

Методика формирования и выдачи сообщений об ошибках.
Описывается способ работы над ошибками в данных, прилагается программный код. Приводятся примеры.

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

Это хороший тест, проверьте себя. Прагматы выбирают первый вариант, зануды (не в обиду) -  второй, а гении … - вообще игнорируют Smile.

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

Ниже приводится простая процедура для преодоления этих трудностей. В комментариях к ней приводится пример использования.

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

 

// Процедура обрабатывает текст ошибки
//
// Назначение:
//
//     1. Иерархическая обработка ошибок.
//        На каждом уровне иерархии ошибку можно дополнить уточняющими
//        сведениями о месте ее возникновения.
//        Примерамии иерархии являются: вложенный вызовов процедур,
//        вложенные циклы, вложенные условия "Если Тогда Иначе" и т. д.
//
//     2. Гибкость выбора места вывода ошибок.
//        Вопрос: где выводить ошибку, в процедуре, где она возникла,
//        или в вызывающей процедуре решается гибко.
//        Если внешнюю процедуру ошибка интересует, то ошибка будет передана ей,
//        иначе вывод ошибки будет сделан во внутренней процедуре.
//
//     3. Накопление списка ошибок и выдачи их единым результатом,
//
//
//
// Параметры
//
//     ТекстНакопительОшибок - либо Неопределено, либо строка. Входной и выходной параметр.
//                             Текст, содержащий список ошибок
//     ТекстОшибки           - строка, входной параметр.
//                             Текст ошибки или собранный ранее накопитель ошибок
//     КоординатыОшибки      - любой тип, входной параметр.
//                             Приводится к строковому типу. Текст информирующий пользователя
//                             о месте возникновения ошибки. Следует указать место в
//                             именительном падеже, например "Строка 1" или ЭтотОбъект.Ссылка
//
//
// Описание
//
// 1. Если ТекстНакопительОшибок - имеет тип "Строка", то ТекстОшибки и КоординатыОшибки
//    будут добавлены в ТекстНакопительОшибок, при этом, вывода на экран не будет
//
// 2. В противном случае ТекстОшибки и КоординатыОшибки будут выведены на экран
//
// Замечание 1. Повторные одинаковые ошибки накопитель отбрасывает
// Замечание 2. Если ТекстОшибки - не простая ошибка, а уже ранее собранный накопитель
//              ошибок, то КоординатыОшибки будут добавлены слева к уже существующим
//              координатам каждой из ошибок
//
//
//
// Пример.
//
//
//      Процедура ОбработатьСтроку(НомерСтроки, Ошибки = Неопределено)
//          ОшибкуОтдать(Ошибки, "Лимит превышен", "Строка " + НомерСтроки);
//      КонецПроцедуры
//
//
//      Процедура ВариантСУточнением(Ошибки = Неопределено)
//          Ош = "";
//          ОбработатьСтроку(1, Ош);
//          ОбработатьСтроку(2, Ош);
//          Если ЗначениеЗаполнено(Ош) Тогда
//              ОшибкуОтдать(Ошибки, Ош, "ТЧ Данные");  // отдаем ошибку нижнего уровня наверх,
//                                                      // уточняя своими координатами
//          КонецЕсли;
//      КонецПроцедуры
//
//
//      Процедура ВариантБезУточнения(Ошибки = Неопределено)
//          ОбработатьСтроку(1, Ошибки);             // Проносим ошибку без контроля
//          ОбработатьСтроку(2, Ошибки);             //
//      КонецПроцедуры
//
//
//      Процедура Испытание()
//          ВариантСУточнением();                    // выдаст:
//                                                   //     Лимит превышен. См. ТЧ Данные, Строка 1
//                                                   //     Лимит превышен. См. ТЧ Данные, Строка 2
//
//          Ошибки = "";
//          ВариантСУточнением(Ошибки);
//          Если Ошибки <> "" Тогда
//              ОшибкуОтдать(, Ошибки, "Испытание"); // выдаст:
//                                                   //     Лимит превышен. См. Испытание, ТЧ Данные, Строка 1
//                                                   //     Лимит превышен. См. Испытание, ТЧ Данные, Строка 2
//          КонецЕсли;
//
//
//
//          ВариантБезУточнения();                   // выдаст:
//                                                   //     Лимит превышен. См. Строка 1
//                                                   //     Лимит превышен. См. Строка 2
//
//          Ошибки = "";
//          ВариантБезУточнения(Ошибки);
//          Если Ошибки <> "" Тогда
//              ОшибкуОтдать(, Ошибки, "Испытание"); // выдаст:
//                                                   //     Лимит превышен. См. Испытание, Строка 1
//                                                   //     Лимит превышен. См. Испытание, Строка 2
//          КонецЕсли;
//      КонецПроцедуры
//
//
Процедура ОшибкуОтдать(ТекстНакопительОшибок = Неопределено, ТекстОшибки = "", КоординатыОшибки = Неопределено) Экспорт

    Если не
ЗначениеЗаполнено(ТекстОшибки) Тогда
        Если не
ЗначениеЗаполнено(ТекстНакопительОшибок) или не ЗначениеЗаполнено(КоординатыОшибки) Тогда
            Возврат;
        КонецЕсли;
    КонецЕсли;

   
РазделительСписка = "..|..";    // между ошибками
   
Сцепка = ":,:";                 // между текстом ошибки и координатами


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

   
ВместоПС = "{;}";                 // временно заменяет Символы.ПС



   
ТекстНак = СтрЗаменить(СокрЛП(ТекстНакопительОшибок), Символы.ПС, ВместоПС);
   
ТекстНак = СтрЗаменить(ТекстНак, РазделительСписка, Символы.ПС);

   
ТекстТек = СтрЗаменить(СокрЛП(ТекстОшибки), Символы.ПС, ВместоПС);
   
ТекстТек = СтрЗаменить(ТекстТек, РазделительСписка, Символы.ПС);

   
Коорд    = СокрЛП(КоординатыОшибки);


   
// Разложение параметра ТекстНакопительОшибок в массив строк
   
мвОшибки = Новый Массив;
    Для
к = 1 По СтрЧислоСтрок(ТекстНак) Цикл
       
мвОшибки.Добавить(СтрПолучитьСтроку(ТекстНак, к));
    КонецЦикла;


   
// Разложение параметра ТекстОшибки на строки и их добавление в массив строк
   
Для НомерСтроки = 1 По СтрЧислоСтрок(ТекстТек) Цикл

       
ТекстСтроки = СокрЛП(СтрПолучитьСтроку(ТекстТек, НомерСтроки));
       
КоордСтроки = "";


       
к = Найти(ТекстСтроки, Сцепка);
        Если
к > 0 Тогда
           
КоордСтроки = СокрЛП(Сред(ТекстСтроки, к + СтрДлина(Сцепка)));
           
ТекстСтроки = СокрЛП(Лев(ТекстСтроки, к-1));
        КонецЕсли;


        Если
Коорд <> "" Тогда
            Если
КоордСтроки = "" Тогда
               
КоордСтроки = Коорд;
            Иначе
               
КоордСтроки = Коорд + ", " + КоордСтроки;
            КонецЕсли;
        КонецЕсли;


        Если
КоордСтроки <> "" Тогда
           
ТекстСтроки = ТекстСтроки + Сцепка + КоордСтроки;
        КонецЕсли;


        Если
мвОшибки.Найти(ТекстСтроки) = Неопределено Тогда
           
мвОшибки.Добавить(ТекстСтроки);
           
ТекстНак = ТекстНак + ?(ТекстНак = "", "", Символы.ПС) + ТекстСтроки;
        КонецЕсли;

    КонецЦикла;




    Если
ТипЗнч(ТекстНакопительОшибок) = Тип("Строка") Тогда

       
ТекстНак = СтрЗаменить(ТекстНак, Символы.ПС, РазделительСписка);
       
ТекстНак = СтрЗаменить(ТекстНак, ВместоПС, Символы.ПС);

       
ТекстНакопительОшибок = ТекстНак;

    Иначе

       
ТекстНак = "";
        Для каждого
ТекстСтроки Из мвОшибки Цикл
           
ТекстСтр = ТекстСтроки;
            Если
СтрДлина(ТекстСтр) > 100 Тогда
               
ТекстСтр = СтрЗаменить(ТекстСтр, Сцепка, ВместоПС + "См. ");
            Иначе
               
ТекстСтр = СтрЗаменить(ТекстСтр, Сцепка, ". См. ");
            КонецЕсли;
           
ТекстНак = ТекстНак + ?(ТекстНак = "", "", РазделительСписка) + ТекстСтр;
        КонецЦикла;

       
ТекстНак = СтрЗаменить(ТекстНак, РазделительСписка, Символы.ПС); // каждую ошибку с новой строки
       
ТекстНак = СтрЗаменить(ТекстНак, ВместоПС, Символы.ПС);          // обратная замена

       
Сообщение = Новый СообщениеПользователю;
       
Сообщение.Текст = ТекстНак;
       
Сообщение.Сообщить();

    КонецЕсли;


КонецПроцедуры

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Maxim Goncharov (maxx) 29.09.13 19:55
Идея хорошая, я бы еще добавил в объект СообщениюПользователя путь к данным объектов или мест где возникла ошибка, чтобы навигация работала по списку ошибок, т.е нажал по ошибке и перешел сразу к месту где ошибка
2. Аркадий Кучер (Abadonna) 30.09.13 04:34
(0)
// Возможно пользователь использовал Символы.ПС в своих нуждах, поэтому
// временно заменим все Символы.ПС на другую комбинацию символов.
// Потом вернем назад

ВместоПС = "{;}";

Рекомендую: (ALT+0134) (ALT+0135) - никогда простой юзверь такое на клавиатуре не наберет ;)
CratosX; rkravtsov; +2 Ответить
3. Яков Коган (Yashazz) 30.09.13 12:45
Да, уже витает в воздухе.
Я себе смастерил накопитель в виде дерева, которое отслеживает "погружение" в вызовы различных процедур да функций; а потом вывожу его в макет с расшифровкой, и показываю, где были какие сообщения. Красиво и удобно, но кода требует, конечно.
4. Юрий Щербаков (ufo58) 30.09.13 17:12
все бы ничего
и идея хороша, и посыл нормальный
но ... выводить "Сообщением", тем более в управляемых формах ... бррр
да и Дерево ... тяжеловато
подумай, может дать вариант формирования тип Текст ?
с последующим просмотром
или сохранением ...
5. Юрий Лазаренко (TitanLuchs) 02.10.13 14:42
Присоединюсь к предыдущему оратору. При возможности тоже стараюсь избегать использование "Сообщить" и "СообщениеПользователю" в УФ, потому что перекореживание формы в этот момент - это иногда зрелище не для слабонервных. Одно время в своей конфе открывал отдельное окно с сообщениями, на быстродействии это не сказывалось (потому что форма все равно перерисовывается при изменении размера), но юзеров не так бесило.
6. Илларион Пак (pakill) 03.10.13 09:38
Вообще-то я старался сделать легкую процедуру, не зависящую ни от конфигурации, ни от контекста.
Писать что-то тяжеловесное на тот случай, который может никогда и не произойти как-то не хотелось. Я лишь ставил цель, предоставить пользователю шанс самостоятельно разобраться и исправить ошибку в данных, если таковая возникнет.

Насчет вывода ошибки с помощью Сообщить():
А как ее еще выводить на стороне сервера?

Это можно сгладить лишь частично:

#Если Сервер Тогда
Сообщить(...);
#Иначе
Предупреждение(...);
#КонецЕсли

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

7. Яков Коган (Yashazz) 03.10.13 12:57
(6) Вот я именно и заменил "Сообщить" своей процедурой. И УФ не корёжит, и на сервере работает, в протокол пишет.
8. Алексей (devel0per) 08.10.13 02:08
Хоть комментарий к процедуре и прекрасен, но из него не ясно чем плох отладчик, ведь
нам никто не запрещает им пользоваться на сервере.
Не лучше ли будет вместо:
Коорд    = СокрЛП(КоординатыОшибки);

и
Если Коорд <> "" Тогда

писать:
Если ПустаяСтрока(Коорд) Тогда

Вот не помню в клюшках функц. ПустаяСтрока была?
9. Walter (WalterMort) 08.10.13 13:49
Автор изобрел велосипед, вместо грамотной работы с исключениями.
10. Андрей Овсянкин (Evil Beaver) 08.10.13 13:56
(9) WalterMort, ну тогда уж приводите пример "грамотной" работы с исключениями в рамках поставленной задачи. Что вы понимаете под "грамотной" работой?
11. Елена Пименова (Bukaska) 08.10.13 13:56
(9) WalterMort, Ой да ладно.. ну что же вы все как эти... даже не знаешь как назвать... Я конечно понимаю что нужно реализовываться, знаю что и инициатива бывает наказуема. Но кто не работает, тот не ест)))
12. Walter (WalterMort) 08.10.13 14:55
(10) Обычная передача ошибок снизу наверх. Классов исключений в 1С нет, но описания ошибки для замысла автора достаточно:

Процедура Внешняя()
Попытка
Внутренняя();
Исключение
ВызватьИсключение "Невозможно выполнить внешняя: " + ОписаниеОшибки(); // наверх
КонецПопытки;
КонецПроцедуры

Процедура Внутренняя()
Попытка
а = б/0;
Исключение
ВызватьИсключение "Невозможно выполнить внутренняя"; // передаем наверх
КонецПопытки;
КонецПроцедуры


Результат: "Невозможно выполнить внешняя: Невозможно выполнить внутренняя"



Ну а исключение клиентского (самого высокого) уровня уже можно как угодно отработать. Сообщением, предупреждением, или записью в лог.
13. Андрей Овсянкин (Evil Beaver) 08.10.13 16:05
(12) WalterMort, вот поскольку классов исключений нет, то все, что могут передать исключения - это строки. Отсюда, шибко "грамотной" работы с исключениями все равно не будет.
14. Илларион Пак (pakill) 09.10.13 02:44
(8). Чем же плох отладчик? Чтож. Дайте пользователю отладчик. Пусть ищет и исправляет ошибки в данных (подчеркиваю в данных, а не в коде). Ну а я, как-то не готов давать пользователю отладчик.

Коорд = СокрЛП(..) // это чтобы вместо: "текст , текст" всегда было так: "текст, текст"
Если Коорд <> "" Тогда

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


(12). Насчет Попытка-Исключение.
А Вы попробуйте воспроизвести в точности тот пример из комментария с помощью попытки-исключения.
И сразу же выяснится, что вы будете заниматься разбором строк в каждой процедуре, где используете эту конструкцию. Кроме того, у Вас возникнут трудности при передаче списка ошибок. И чтобы облегчить жизнь придется написать свою процедуру, занимающуюся разбором строк и накоплением ошибок.
Но скорее всего, Вы просто используете ту самую процедуру ОшибкуОтдать(), приведенную выше.
Так что, попытка-исключение - это всего лишь, один из способов ее применения.


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


Заметьте, принципиальную разницу: сообщение может быть выдано не только на верхнем уровне, а на любом из уровней. В примерах как раз показан вывод из разных процедур. И это зависит от того, как вызвана процедура.
В Вашем случае - это наличие или отсутствие конструкции попытка-исключение в процедурах верхнего уровня.
Это значит, что "ВызватьИсключение" - это и есть Ваш способ вывода ошибки, т.е. Вы с этим уже заранее определились.
15. Антон Антонов (monkbest) 27.05.16 09:10