Исключения
От: faa  
Дата: 14.05.13 10:22
Оценка:
Доброго времени суток!

В настоящий момент я разрабатываю библиотеку классов предоставляющую API для логирования сообщений в таблицу БД MS SQL Server.
Изучив тему обработки исключений в C# саомостоятельно по книге Герберта Шилдта: "С# 4.0 Полное руководство" и засучив рукава начал применять полученные знания на своей рабочей задачи, но позже наткнулся на раздел MSDN: http://msdn.microsoft.com/ru-ru/library/ms229014%28v=vs.100%29.aspx и понял, что всё сделаное мной противоречит практически всем основным рекомендация там приведённым. Вот кратко основные из них:

1. Создавайте и генерируйте пользовательские исключения, если у Вас возникает ошибка, которую можно обработать программным путем иным способом, чем существующие исключения. В противном случае генерируйте одно из существующих исключений.
2. Не создавайте и не генерируйте новых исключений, просто чтобы иметь свои собственные исключения.
3. Генерируйте наиболее специфичные (самые отдаленные от базовых) исключения, которые подходят в конкретной ситуации. Например, если метод принимает null-аргумент (Nothing в Visual Basic), он должен генерировать исключение System.ArgumentNullException вместо базового типа System.ArgumentException.
4. Особые исключения, создаваемые кодом нижнего уровня, рекомендуется заключать в оболочку более подходящего исключения, если исключение нижнего уровня не имеет смысла в контексте операции высокого уровня.
5. Не перехватывайте исключение System.Exception или System.SystemException в своем коде, только если не планируется его повторный вызов. Избегайте перехватов исключений System.Exception или System.SystemException, за исключением обработчиков исключений высокого уровня.

Теперь фрагмент из моего проекта:


 public Category GetCategory(string CategoryName)
        {
            try
            {
                if( !DataBase.ValidateArgument(CategoryName) )
                    throw new JournalProvaiderException("Ошибка получения категории журнала. " +
                                                        "Не верный формат имени категории журнала.", null);
                
                //Если экземпляр запрошенной категории журнала уже создан,
                //возвращаем ссылку на него 
                if (_CatInstances.ContainsKey(CategoryName))
                    return _CatInstances[CategoryName];

                if (!IsConnected)
                    throw new JournalProvaiderException("Ошибка получения категории журнала. " +
                                                        "Не установлено подключение к серверу журналов.", null);
                
                //Проверка существоваония категории журнала в БД сервера журналов
                if (!IsJournalExist(CategoryName))
                    throw new JournalProvaiderException("Катгория журнала с именем: <" + CategoryName + 
                                                        "> не существует на сервере журналов.", null);
                
                Category _Category = null;

                //Выбор блока обработки категории журнала
                switch (CategoryName)
                {                
                    case ("Информационные сообщения"): 
                
                        //Входные параметры хранимой процедуры
                        SQLParametr[] InputParamList = new SQLParametr[1];
                        InputParamList[0].Name = "@CatName";
                        InputParamList[0].Value = CategoryName;

                        //Возвращаемое хранимой процедурой значение
                        SqlParameter ReturnValue;

                        //Получить описание категории журнала из БД
                        using (SqlDataReader Reader = DataBase.ExecuteSQL(_SQLConnection, "sp_GetCategories",
                                                                          InputParamList, out ReturnValue, false))
                        {
                            if (!Reader.HasRows)
                            {
                                Reader.Close();
                                throw new JournalProvaiderException("Не удалось получить описание категории журнала <" + CategoryName +
                                                                    "> от сервера журналов! (sp_GetCategories return " +
                                                                    ReturnValue.Value.ToString() + " ).", null);
                            }
                            //Получить аттрибуты записи Категори журнала
                            if (Reader.Read())
                                _Category = new Category(
                                                            Reader.GetSqlString(0).Value,
                                                            Reader.GetSqlString(1).Value,
                                                            Reader.GetSqlString(2).Value,
                                                            Reader.GetSqlBoolean(3).Value,
                                                            Reader.GetSqlInt32(4).Value,
                                                            Reader.GetSqlInt32(5).Value
                                                         );
                            else
                                throw new JournalProvaiderException("Сбой при получении аттрибутов записи " +
                                                                    "категории журнала <" + CategoryName + ">.", null);
                            Reader.Close();
                        }
                    break;
             
                    default:                
                        throw new JournalProvaiderException("Запрошенная категория журнала: <" + CategoryName + 
                                                            "> не поддерживается бибилотекой провайдера.", null);                  
                }
                
                //Добавляем ссылку на категорию журнала в коллекцию 
                _CatInstances.Add(CategoryName, _Category);
                return _Category;            
            }
            catch(SystemException e)
            {
                throw new JournalProvaiderException("Ошибка получения данных категории журнала.", e);
            }
        }


Я размышлял так:
1. Так API библиотеки абстрагирует пользователя библиотеки от БД вводя в свою очередь собственную абстракцию "Сервер жураналов" то
информационные сообщения должны описывать ситуацию в контексте сущности "Сервер журналов", являясь оболочкой для исключений СУБД.
Поэтому создал собственный класс исключения:


    //Определяет исключительную ситуацию
    //в работе провайдера журналов
    public class JournalProvaiderException: Exception
    {
        public JournalProvaiderException() : base() {}
        public JournalProvaiderException(string message) : base(message) { }
        public JournalProvaiderException(string message, Exception innerException) : base(message, innerException) { }
        public JournalProvaiderException(SerializationInfo info, StreamingContext context) : base(info, context) { }
        public override string ToString()
        {
            return "JournalProvaiderException: < " + base.Message + " >\n" +
                    "Source: < " + base.Source + " >\n" +
                    "TargetSite: < " + base.TargetSite + " >\n" +
                    "StackTrace: < " + base.StackTrace + " >\n" + "\n" +
                    //Внутреннее исключение
                    "InnerException: \n<\n" + base.InnerException + "\n>";
        }
    }


2. Все исключения возбуждаемые внути моей библиотеки перехватываются на уровне SystemException и "заворачиваются" в мой класс JournalProvaiderException, чтобы в клиентском коде достаточно было ставить только один "фирменый" перехватчик:



try
{
   //Клиентский код 
}
catch (JournalProvaiderException e)
{
    ...
}



Внутри библиотеки я начал ставить несколько блоков перехвата, так:

            
            try
            {

            }
            catch (SqlNullValueException e)
            {
            }
            catch (OutOfMemoryException e)
            {
            }
            catch (InvalidOperationException e)
            {
            }
            ...


но бысро понял, что такой код выглядит громоздким, плохочитаемыми, да и знать сколько именно исключений перехватывть сложно.
И тут меня осенило Я посчитал, что гораздо правильней и читабельнее использовать полиморфизм и перехватывать исключения базового класса, и генерировать своё исключение завернув в него перехваченное. Так и поступил:



            try
            {

            }
            catch (SystemExceptione e)
            {
               throw new JournalProvaiderException("Ошибка получения данных из журнала.", e);
            }


Объясните пожалуйста почему поступать так плохо?
Почему Microsoft даёт рекомендации так не делать (см. выше П.5) ?
Что значит рекомендация: " Создавайте и генерируйте пользовательские исключения, если у Вас возникает ошибка, которую можно обработать программным путем иным способом, чем существующие исключения. В противном случае генерируйте одно из существующих исключений." ?

Помогите разобраться . Заранее благодарен!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.