Определение места ошибки в исходном файле

Относительно недавно я писал про Singleton и там была у меня не решенная проблема с тем, что не выводилось точное место возникновения ошибки. Анализ ошибки проходил как надо, информация дополнительная выводилась, но это было мало. Привычный шаблон работы, когда двойной клик по тексту ошибки приводил к навигации на место ошибки не работал. Это безмерно огорчало.

После плодотворного общения с программистами PostSharp, оказалось, что это была ошибка в последней сборке и новый релиз, начиная с 4.0.41, ее исправляет. Дальше я хочу отдельно обратить внимание на то, что надо делать и рассказать немного о том, как вообще можно получить эту информацию в C#: до версии 4.5, с версии 4.5 и старше.

PostSharp

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

Использование Архитектурного фреймворка из состава PostSharp рано или поздно приведет вас к тому, что надо будет уведомлять программистов об ошибках/недочетах. Лучше всего это делать с помощью специализированных классов, которые построены с учетом специфики работы PostSharp на этапе сборке проекта. К таким специализированным классам относятся:

  • MessageLocation – статический класс, который позволяет узнать место ошибки (строка, файл) на основе служебной информации о составной части класса (MethodInfo, ConstructorInfo и так далее).
  • Message – класс со статическими и динамическими методами, который помогает сформировать сообщение для отображения для конечного пользователя – программиста.

Общий шаблон работы с этими классами выглядит так:

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

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

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

Последняя строка использует статический метод Write(), класса Message. Метод принимает только что созданный экземпляр класса Message, для того, чтобы отобразить его на панели Error List.

errorline_01

Класс MessageLocation позволяет задать местоположение ошибки вручную с помощью метода Explicit(). Либо можно использовать свойство Unknown.

Метод Of() имеет большое количество перегрузок, чтобы можно было указать Sна любое место класса. Например, если передать просто указание на тип класса, то в сообщении об ошибке будет указание на объявление класса – строка с ключевым словом class. Лучше всего это проиллюстрирует следующий скриншот:

errorline_02

До версии .NET 4.0 включительно

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

Для этого используется конструкция try…catch. В блоке обработки ошибки необходимо с помощью класса StackTrace получить стек ошибки и далее с помощью вспомогательных методов извлечь все необходимые данные. Имена методов говорят сами за себя:

  • GetFileName()
  • GetMethodName()
  • GetFileLineNumber()

Общий блок кода выглядит так:

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

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

errorline_04

Начиная с .NET 4.5 и до настоящего времени

Начиная с версии .NET 4.5 разработчики языка значительно упростили жизнь разработчикам реализовав то же самое поведение с помощью «аспектов». Хотя конечно же сама реализация просто реализована в глубине фреймворка. Атрибуты просто говорят, какое значение над вставить в параметры метода, если не приходит явное значение. Однако требуется при объявлении указать значения по умолчанию. Вот эти атрибуты, их имена так же ясно описывают поведение:

  • [CallerFilePath]
  • [CallerMemberName]
  • [CallerLineNumber]

Применение атрибутов выглядит вот так:

В клиентском коде так же используется конструкция try…catch.

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

Результат работы кода выглядит как на скриншоте ниже:

errorline_05

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

Если вам пришла идея подписаться на событие UnhandledException класса AppDomain, то это не очень удачная идея. И вот почему:

errorline_06

По сообщению в консоли видно, что метод TraceMessage показывает место, откуда был вызван метод. А это как раз и есть UnhandledException. Так что пришлось бы использовать старый метод с использованием стека. Надо будет взять на один фрейм выше, для того, чтобы получить нужные данные. Это уже можно рассматривать как домашнее задание. =)

 

Статья в PDF

 

 

Hard’n’Heavy!

 

 

 

 

Оставить комментарий