Здравствуйте, 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 прошу прощения за уход от темы.