C#. Кодо-ориентированные исключения

Исключения, ориетированные на коды ошибок

Доброго времени суток! В этой статье я хочу показать на практике, как в C# можно создать класс-исключение ориентированный на использование кодов ошибок. Сейчас я чуть подробнее расскажу что имею ввиду и зачем это нужно, а потом, приступим к делу.

Когда Вы пишите программу, Вам приходится думать не только о том что она должна делать, но и том, что как она должна себя вести в нестандартных ситуациях, при некорректном поведении пользователя и тому подобное. В частности, приходится сталкиваться с обработкой входных данных и информировать систему, если они некорректные. И как правило, выявляется целый ряд типов ошибок. В таком случае, программист может для каждого типа исключительных ситуаций написать отдельный класс (и такой подход оправдан в ряде случаев), но может пойти и другим путём, о котором я и расскажу в этой статье.

Вместо того, чтобы создавать на каждый исключительный случай отдельный класс, можно написать всего один, но расширить его кодом ошибки. Для примера, давайте представим, что нужно написать класс, описывающий человека. В таком классе будет информация о его имени, возрасте и росте. Но логично, предположить, что возраст и рост не могут быть отрицательными. По-этому в конструкторе, и свойствах (устанавливающих значения соответствующим полям) входные данные будут проверяться, и в случае обнаружения их некорректности, будут генериться исключения, но не разных типов, а одного и того же, с указанием кода ошибки. Посмотрите на пример ниже:

//Класс, для описания некого человека
class Person
{
    //Конструктор (принимает имя, возраст и рост)
    public Person(string aName, int anAge, int aHeight)
    {
        name = aName;

        //Если указан отрицательный возраст
        if (anAge < 0)
            throw new PersonException(PersonException.NEGATIVE_AGE); 

        age = anAge;

        //Если указан отрицательный рост
        if (aHeight < 0)
            throw new PersonException(PersonException.NEGATIVE_HEIGHT);

        height = aHeight;
    }

    //Возвращает, или устанавливает имя
    public string Name { get { return name; } set { name = value; } }
 
    //Возвращает, или устанавливает возраст
    public int Age 
    { 
        get { return age; } 
        set 
        {
            //Если указан отрицательный возраст
            if (value < 0)
                throw new PersonException(PersonException.NEGATIVE_AGE); 

            age = value; 
        } 
    }

    //Возвращает, или устанавливает рост
    public int Height 
    { 
        get { return height; } 
        set 
        {
            //Если указан отрицательный рост
            if (value < 0)
                throw new PersonException(PersonException.NEGATIVE_HEIGHT);

            height = value; 
        } 
    }
 
    private string name; //Имя
    private int age;     //Возраст 
    private int height;  //Рост
}

//Класс, для описания пользовательского типа ошибок
class PersonException : Exception //Используем наследование
{
    //Массив описаний ошибок
    private static string[] descriptions = new string[] 
    {
        "",
        "Указано отрицательное число в качестве возраста",
        "Указано отрицательное число в качестве роста"
    };

    //Константы-коды ошибок
    public const int NO_CODE = 0; //Не указан код ошибки 
    public const int NEGATIVE_AGE = 1; //Отрицательный возраст
    public const int NEGATIVE_HEIGHT = 2; //Отрицательный рост

    //Конструктор. Принимает код ошибки в качестве аргумента
    public PersonException(int anErrorCode) 
    {
        errorCode = anErrorCode;
    }

    //Возвращает текстовое описание ошибки
    public override string Message
    {
        get
        {
            //Если указан некорректный код ошибки, или вообще не указан
            if (errorCode < 1 || errorCode > descriptions.Length - 1)
                return descriptions[0];

           return descriptions[errorCode];
        }
    }

    //Возвращает код ошибки
    public int ErrorCode { get { return errorCode; } }

    //Код ошибки
    private int errorCode;
}

В примере описаны два класса, один («Person») отвечает за бизнес логику, а второй («PersonException») служебный — для представления ошибок.  В классе «PersonException» есть поле, представляющее код ошибки, значение которому, задается при создании объекта (используется класса «Person» в конструкторе и двух свойствах). Причем, код ошибки, является не только числовым кодом, но и индексом, для получения строкового описания ошибки из статического массива «descriptions», объявленного в классе. Это строковое описание возвращает пользователю класса, переопределенное, виртуальное свойство «Message». Но это свойство вернет описание только в том случае, если код ошибки указан корректно, т.е. не «взят с потолка» а является одним из допустимых. Набор допустимых кодов ошибок представлен открытыми константами класса.

Обратите внимание, чтобы не получить исключительную ситуацию в свойстве «Message», сначала проверяется корректность кода ошибки, и если он не выходит за пределы массива описаний, тогда возвращается описание по коду. В противном случае, или если код ошибки равен «0» возвращается пустая строка.

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

Конечно, такой подход может подойти не всегда, иногда нужно создавать отдельные классы для некоторых типов ошибок (если необходимо передавать дополнительную, специфическую информацию в блок обработки). Да и саму реализацию класса «PersonException» можно сделать более качественно, например ввести перечисление вместо отдельных констант-кодов ошибок. Но цель этой статьи, скорее дать Вам пищу для размышлений (и последующего развития) на конкретном примере, а не показать идеальный вариант.

Добавить комментарий