Re[10]: Exceptions в сеттерах
От: vansha Украина korneliuk.blogspot.com
Дата: 01.12.08 23:02
Оценка: 15 (1) +1
Здравствуйте, mazurkin, Вы писали:

M>Мне интересна мотивация — вот вы хотите чтобы ассерт работал только в

M>дебаге — а почему?

Разрешите вставить свои 5 копеек.
При разработке, у меня возникает необходимость (или скорее желание) использовать как Debug.Assert'ы, которые будут отключены в релизной версии, так и утверждения которые в ней останутся. А также и проверки предусловий, которые мы тоже оставляем в релизной версии.

Почему есть желание использовать Assert только в Debug версии? Потому что, в нем можна использовать ресурсоемкие вычисления для проверки. А главное, Assert, с моей точки зрения, действительно никогда не дожен стрелять. Чего не скажешь о предусловиях. Т.е Assert я использую для проверки собственной логики, а специальные классы предусловия (Precondition) и утверждения (Ensure) для проверки внешней по отношению к классу логики, за которую я (класс) отвечать не могу. Но я (опять же класс) могу предъявить контракт в виде предусловий и/или предположений только при соблюдении которых я соглашусь что-то делать.

Попробую на примере лучше объяснить свои мысли.

public class OrderRepository
{
    public IList<Order> GetClientOrders(Client client)
    {
        // Здесь элементарное предусловие. Метод не знает что ему делать с 
        // наловым клиентом. Вот и нечего такие данные передавать.
        Precondition.Argument.NotNull(client, "Client");
        
        // Здесь проверяется предусловие, что клиент уже был сохранен в БД. 
        // А иначе что искать?
        Precondition.Argument.Check(IsPersisted(client, "Client is saved in database"));

        return SomeORM.CreateQuery("LoadClientProducts ?").SetInt(client.Id).List<Order>();
    }

    public Order FindLastClientOrder(Client client)
    {
        // А здесь мне было лень писать предусловие. Я понадеялся, что они и так 
        // стрельнут в методе GetClientOrders. Наверное так делать не стоит.

        IList<Order> clientOrders = GetClientOrders(client);
        if (clientOrders.Count == 0)
        {
            return null;
        }

        Order result = clientOrders[clientOrders.Count - 1];
        
        // Здесь ассерт документирует предположение, что последний элемент 
        // в списке заказов клиента обязательно имеет максимальную дату заказа.
        // Также он указывает, что именно гарантирует правильный порядок сортировки
        // (sql запрос или объектно реляционный преобразователь)
        // Я хотел показать, что в предложении Assert можно не бояться использовать
        // ресурсоемкие операции. Ведь в релизной версии их не будет.
        // Сортировка делается один раз, на стороне сервера. Проверка может быть выполнена 
        // с помощью юнит тестов. Т.е. Assert здесь лишь способ документирования.
        Debug.Assert(
            new List<Order>(clientOrders).TrueForAll(x => x.Date < result.Date),
            "Last order in the list has the greatest date",
            "List is sorted in sql query");

        return result;
    }
}

class ProductRepository
{
    public Product GetById()
    {
        return null;
    }

    public Product FindByBarcode(string barcode)
    {
        // Таким образом проверяется предусловие. Методу нельзя передавать
        // null. Потому что он не знает что с этим null делать. Трактовать
        // его как пустую строку или использовать как значение NULL сервера БД?
        // Это не в компетенции данного метода. О чем мы и спешим сообщить миру 
        // посредством специального исключения.
        Precondition.Argument.NotNullOrEmpty(barcode);

        IList<Product> result = SomeORM
            .CreateQuery("LoadProductByBarcode ?")
            .SetString(0, barcode)
            .List<Order>();

        if (result.Count == 0)
        {
            return null;
        }

        // Эта проверка гарантирует, что запрос вернул только одно значение.
        // И аргументирует это предположение тем, что штрих-код товара - 
        // это естественный ключ в БД.
        // Идиологически не использую Assert, потому что структура БД мне 
        // не подвластна (по крайней мере с высоты этого кода, то что я мог 
        // создавать эту таблицу еще вчера, ничего не значит).
        // Поэтому здесь используется утверждение в стиле design by contract, в том смысле 
        // что эта проверка будет жить и в релизной версии.
        Ensure.That(
            result.Count == 1,
            "Only one item found",
            "Barcode is an unique key in database");

        return result[0];
    }

    public Product GetByBarcode(string barcode)
    {
        Precondition.Argument.NotNullOrEmpty(barcode);

        Product product = FindByBarcode(barcode);
        if (product == null)
        {
            // Такой вариант удобен когда клиент (класса) получил значение
            // штрих-кода из доверительного источника, и хочет по нему 
            // гарантировано найти продукт. И чего он меньше всего ожидает, 
            // так это null в результате. То есть это действительно исключительная 
            // ситуация.
            throw new ArgumentException("Product with the specified barcode is not found");
        }

        return product;
    }

    public void Save(Product product)
    {
        Precondition.Argument.NotNull(product, "product");
        CheckCanBeSaved(product);
        
        // Save.
    }

    private static void CheckCanBeSaved(Product product)
    {
        // Так как это приватный метод, то ни о каком предусловии здесь 
        // речь не идет. Здесь может быть проверена лишь логика класса
        // Я могу утверждать что здесь не null, потому что публичные методы 
        // обязаны сделать такую проверку.
        Debug.Assert(product != null, "Product not null", "Checked in public methods");

        // Check that product can be saved in db.
    }
}


Таким образом мы используем разные типы проверок в разных случаях.
Precondition для проверки предусловий методов. Отключать их нельзя. Ведь я не могу гарантировать что клиентский код будет использовать метод корректно. Тем более это актуально для быблиотечных функций.
Ensure — для утверждений в смысле Design by contract, тех которые целесообразно оставить и в релизной версии программы.
Assert — в традиционном смысле, для проверки собственной логики класса, которая не зависит от внешних источников.
Exception — для исключительных ситуаций.

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

P.S. Mike Chaliy прошу прощения за уход от темы.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.