Здравствуйте, 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 — для исключительных ситуаций.
Пример немного надуман, что бы лучше продемонстрировать различные варианты использования проверок.
vansha wrote:
> Почему есть желание использовать Assert только в Debug версии? Потому > что, в нем можна использовать ресурсоемкие вычисления для проверки. А > главное, Assert, с моей точки зрения, действительно никогда не дожен > стрелять. Чего не скажешь о предусловиях. Т.е Assert я использую для > проверки собственной логики, а специальные классы предусловия > (Precondition) и утверждения (Ensure) для проверки внешней по > отношению к классу логики, за которую я (класс) отвечать не могу. Но > я (опять же класс) могу предъявить контракт в виде предусловий и/или > предположений только при соблюдении которых я соглашусь что-то > делать.
Большое спасибо за развернутый ответ. Но я все равно полностью не
получил желаемого
Насчет ресурсоемких проверок я согласился с вами авансом — несколькими
постами ранее.
Но зачем вы отключаете проверку собственной логики в релизе? Я не
понимаю. Вы боитесь, что написали логику неправильно и клиент об этом
узнает или что?
Здравствуйте, Mike Chaliy, Вы писали: MC>А как технически реализована гарнтия того что клиент выполняет условия? И почему ассерты отрубаються в поставщике, а не в клиенте?
Не думаю, что тебе удастся получить детали. В прошлый раз обсуждение с ЮЖ темы контрактов кончилось ничем. Его познания теории так глубоки, что никакой практике они не соответствуют.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, mazurkin, Вы писали:
M>vansha wrote:
M>Но зачем вы отключаете проверку собственной логики в релизе? Я не M>понимаю. Вы боитесь, что написали логику неправильно и клиент об этом M>узнает или что?
Скорее наоборот . Я отключаю проверки в релизе не потому что боюсь показать клиенту, что логика приложения не верна. Наоборот, я не боюсь отключить проверки в релизе, потому что после тестирования, уверен в том, что логика верна.
Мое восприятие таково. Нарушение условия в Assert, указывает, что в локальном коде разработчиком допущен баг (не ошибка с точки зрения пользователя), неверное предположение. Такое нарушение исправляется сразу же в процессе кодирования или при тестировании.
Нарушения условий, которые мы проверяем с помощью нашей реализации предусловий (Precondition) и утверждений (Ensure), также указывают на баг в программе. Но уже не в локальном коде класса, а коде клиентов класса. Т.е. на границе классов и компонентов. Такие ошибки протестировать сложнее. Поэтому такие проверки мы оставляем и в релизе. Так как лучше уж получить исключение (пусть не ласковое к пользователю), чем продолжать исполнение программы в некорректных условиях, с неизвестными последствиями.
Приведу навскидку еще один пример из кода
public static string DomainUserName
{
get
{
Ensure.That(
!String.IsNullOrEmpty(Environment.UserDomainName),
"Domain or local computer name defined",
"It seems to be always defined, even for local user that is not in domain");
Я благодарен разработчику кода, за то, что он сделал для меня очевидным факт, что метод Environment.UserDomainName всегда вернет не пустую строку. И мне не нужно смотреть документацию. А включать ли эту проверку и в релизе? Необязательно, так как это проверенно в процессе тестирования. (Хотя в данном случае включена в релизе, така ка используется класc Ensure).
vansha wrote:
> Скорее наоборот . Я отключаю проверки в релизе не потому что боюсь > показать клиенту, что логика приложения не верна. Наоборот, я не > боюсь отключить проверки в релизе, потому что после тестирования, > уверен в том, что логика верна.
Все понятно, но все равно не понятно зачем же отключать ассерты
То есть ваш ход мыслей я конечно понял — тут ничего спорного нет, но не
понял на чем основывается ваша уверенность в том, что логика верна
полностью. Тут (обращаясь к вашим словам) видимо вопрос именно веры или
ответственности.
Вы уверены, что после тестирования ваш код идеально правилен, а я — нет.
Даже при тотальном покрытии кода юнит-тестами остается осадочек, что
где-то мы чего-то могли забыть — не учесть какую-то ситуацию, в которой
при валидных с нашей точки зрения исходных данных результат работы
алгоритма становится неверен.
Еще другими словами. Предлагаю рассмотреть ситуацию с отключением
ассертов в релизе в разрезе такого анализа:
-----
1.1 Выгода, получаемая при отключении ассертов в релизе
1.2 Недостатки, устраняемые при отключении ассертов в релизе
2.1 Выгода, теряемая при отключении ассертов в релизе
2.2 Недостатки, приобретаемые при отключении ассертов в релизе
-----
Пока сам могу обозначить такие пункты
1.1.1 (rank=0) Уменьшение размера кода (кого это сейчас волнует?)
1.1.2 (rank=low) Увеличение быстродействия при отключении медленных
проверок (медленных проверок обычно крайне мало)
2.1.1 (rank=med) Мы теряем возможность поймать и затем устранить наши
редкие труднообнаружимые внутренние ошибки, не найденные при тестировании.
2.2.1 (rank=high) Есть риск, что не обнаруженная в ассерте ошибка может
нанести большой ущерб — то есть как сбой данная ситуация может себя не
проявить, а проявить намного позже — например неверной цифрой в годовом
отчете компании.
Здравствуйте, Mike Chaliy, Вы писали:
MC>Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>>Одно слово 'иногда' порождает очень существенные различия: у первых контракт выполняется всегда, а у других — иногда. В этом месте второй лагерь не верит первому про 'всегда', а первый... отключает ассерты в релизе . Им они незачем — выполнение контракта они обеспечили.
ЮЖ>>Вот примерно так.
MC>Клево написано.
MC>А как технически реализована гарантия того что клиент выполняет условия?
В этом вся проблема. Здесь требуется 100% покрытие тестами всех ассертов, что малореально и приводит к удорожанию разработки. Очевидный вывод — уменьшить количество ассертов(с сохранением общих инвариантов) насколько это возможно. Действенный способ: перенос проверок с run-time в compile-time.
А positive_float имеет инвариант — 'содержит неотрицательное число'. Теперь вместо того чтобы писать тесты на каждый случай использования sqrt, нужны тесты проверяющие моменты создания объектов positive_float.
При разрастании масшабов сразу появляются границы подсистем на которых статический контроль не работает. Эти места — места получения данных извне(в широком смысле слова), здесь проверки должны быть и должны тестироваться. Все это масшабируется от функций до сервисов и др. более крупных единиц функциональности.
Если посмотреть под другим угом, то можно заметить что вынесение предусловия(в примере) в инвариант positive_float есть ни что иное как следование SRP. С этой стороны DbC поощеряет 'правильный' дизайн.
+ Административные меры.
MC>И почему ассерты отрубаються в поставщике, а не в клиенте?
На предусловия поставщика ассерты есть только в поставщике. Это предусловия для его использования. А вырубаются они везде в релизе.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Mike Chaliy, Вы писали: MC>>А как технически реализована гарнтия того что клиент выполняет условия? И почему ассерты отрубаються в поставщике, а не в клиенте? S>Не думаю, что тебе удастся получить детали.
Нахождение на RSDN у меня — далеко не перманентное состояние, поэтому и отвечаю я по мере возможностей.
Здравствуйте, mazurkin, Вы писали:
M>Все понятно, но все равно не понятно зачем же отключать ассерты
Ну вот. Скатываемся к дискуссии . Как мне кажется, у нас не совсем присутствует предмет спора .
Я полностью поддерживаю вашу точку зрения в том, что желательно как можно раньше получить исключение в случае если программа находится в некорректном состоянии. Поэтому необходим какой то механизм проверок, который бы работал и в релизе. Но я испытываю большие сомнения, что для этих целей следует использовать ассерт. Потому что он изначально предназначен для использования в debug версии.
Пример 1: Диалог который показывается пользователю при нарушении условия в ассерт. С кнопочкой [Пропустить].
Допустим есть у меня такой код:
Debug.Assert(obj != null, "obj is not null")
Conole.WriteLine(obj.ToString())
При obj == null, пользователь посмотрит непонятный диалог, нажмет пропустить, и продолжит формирование годового отчета.
Я осознаю, что это решается, путем переопределения TraceListener.
Пример 2: Понимание Assert коллегами. Как мне кажется, большинство разработчиков склонны воспринимать Assert, все-таки как средство отладки. Поэтому можно будет столкнуться с кодом на подобии этого:
Debug.Assert(
TestDatabaseUsed,
"Test database used",
"As we're in debug mode, we must use test database only.")
В этом случае assert как раз раз будет стрелять только в релизе, при использовании продакшн базы данных.
Опять же, конечно пример высосан из пальца. И проблема решается путем введения стандарта методов работы с ошибками.
Вопрос в том, нужно ли решать эти проблемы? Зачем для проверок, которые должны работать всегда, использовать механизм, который для этого не предназначен. Для себя, мы этот вопрос мы решили. Используем собственную реализацию механизмов проверки, которая предусматривает возбуждение честных исключений в случае если условие проверки нарушено. Сайд-эффектом такого подхода, является то, что мы можем использовать более гибкие и читабельные конструкции в коде. Пример использования я приводил несколькими постами выше.
При этом я продолжаю широко использовать Assert для целей отладки или документирования. Хотя наверное вру . Я так же как и Вы, считаю, что лучше перебдеть чем недобдеть, и большинство проверок я оставляю в релизе. Просто для этого использую не Assert, а наш собственный аналог. Например для процедуры формирования отчета:
Ensure.That(
IsValidForYearReport,
"Data for year report is valid",
"Checked in some else method")
И предпочитаю assert в других случаях:
Debug.Assert(
False,
"Unreacheble code",
"Эй! Ты же еще не реализовал этот метод! И никак не мог сюда попасть.")
Должен признать, что в одним из наших проектов на Delphi используются ассерты в релизной версии. Но не скажу что мне это нравится. Я не испытываю большого удовольствия когда вижу в логах ошибку EAssertionException. Да это лучше чем если б программа молча продолжила выполнение, но скорее всего, если assert стреляет, то там должен быть exception.
С другой стороны когда стрельнул ассерт в Html Help Compiler, мы с коллегой не могли не удержаться от язвительного "Ну ребята, что же вы, даже ассерты не отключили".
PS. А теперь попробую ответить на вопрос кратко . Ассерты в релизе отключаю потому что они мне там не нужны. Те проверки которые там нужны, включены явно. Ассерт остается для использования в тех целях, для которых он был предусмотрен.
Здравствуйте, Aikin, Вы писали:
A>Привет. А не подскажешь откуда взят такой грамонтый фреймворк предусловий/постусловий? Нахрапом гугл не дался
Ну, вряд ли это можно назвать фреймворком . Это на самом деле, простая реализация, которую мы (на работе) сделали для своих нужд. Постусловий как раз там и нету, как и инвариантов. То есть, это не есть реализация концепций Design by Contract (хотя навеяно конечно Мейером). Просто более удобный синтаксис проверки условий, чем простое использование exceptions или assert.
Относительно такого фреймворка для .Net, я интересовался только nContract. Но пока не вижу нужды использовать что-то боле сложное чем несколько простых классов.
Здравствуйте, mazurkin, Вы писали:
... M>Вы уверены, что после тестирования ваш код идеально правилен, а я — нет. M>Даже при тотальном покрытии кода юнит-тестами остается осадочек, что M>где-то мы чего-то могли забыть
Это проблема идентификации пред и (особенно) постусловий. Предлагаю рассмотреть контракт функции добавления числа в некий Set (множество уникальных чисел):
Предусловия:
Есть два варианта — 'разрешить' вставку значения, которое уже присутствует или запретить.
Каждый имеет право на жизнь (иногдя и оба) и выбор зависит от ситуации, но мы пока запретим, т.е. предусловие будет следующим:
/// \pre: Exist(v) == false
void Insert(int v)
Постусловия — что должно выполняться после(!) вставки:
post: Размер увеличивается на 1. 'Size() = oldSize + 1'
Достаточно этого ? Очевидно что нет, т.к. такая реализация:
void Set::Insert(int v)
{
_set.Insert(Size()); // Для простоты - _set это уже готовое мн-во
}
Удовлетворяет нашему постусловию, но выполняет совсем не то что нужно.
Добавляем еще одно постусловие:
post: добавленный элемент действительно присутствует во множетсве — 'Exist(v) == true'
Достаточно этого или нет ? Нет. т.к. так нет гарантии что вставка (ошибочная реализация) изменит значения существующих элементов.
Необходимо добавить еще одно постусловие: Все значения, которые находились во множестве до вставки, остаются в нем и после вставки.
Итого:
void Set::Insert(int v)
{
Assert(Exist(v) == false);
//...
Assert(Size() == oldSize + 1);
Assert(Exist(v) == true);
Assert(Все значения, которые находились во множестве до вставки, остаются в нем и после вставки);
}
Дальше. Что должно происходить в случае если предусловия выполняются, а реализация по каким-либо причинам, от нее не зависящих(памяти, например не хватило), не может обеспечить выполнение постусловий ? А должно произойти исключение(или другая форма 'return error'). vansha в соседнем сообщении использует для этого Ensure.
Замена ассертов в постусловиях в ф-ии Insert на проверки вида:
if(Size() != oldSize + 1)
throw Exception();
— абсурд, т.к. во первых это означает что реализация не реализует то что должна(это ее обязанности), во вторых, постусловия могут проверятся во внешнем коде в unit-тестах:
Set s;
//...
s.Insert(v);
Assert(s.Size() == oldSize + 1);
Assert(s.Exist(v) == true);
Assert(Все значения, которые находились во множестве до вставки, остаются в нем и после вставки);
Так делают, когда объем проверок большой или проверки сложные, в случаях тестирования реализаций интерфейсов и т.п.
Похожие аналогии можно провести и для предусловий.
Здравствуйте, Юрий Жмеренецкий, Вы писали:
... ЮЖ>Похожие аналогии можно провести и для предусловий.
+ "но в этом случае проверки — часть production кода"
Здравствуйте, vansha, Вы писали:
V>Ну, вряд ли это можно назвать фреймворком . Это на самом деле, простая реализация, которую мы (на работе) сделали для своих нужд.
Я так и подумал, когда инет отказался выдать инфу по такому синтаксису
Меня именно синтаксис задел. Уж очень он говорящий.
А ты не мог бы поделится либо самой dll-кой либо списком классов-методов которые вы используете?
V>Но пока не вижу нужды использовать что-то боле сложное чем несколько простых классов.
Возможно, но так как я в DbC не силен, то хотелось бы получить готовую реализацию пред/постусловий чисто чтобы "набрал 'Precondition' нажал 'точку'" и смотри какие контракты есть, выбирай наиболее близкий...
Здравствуйте, Юрий Жмеренецкий, Вы писали:
MC>>А как технически реализована гарантия того что клиент выполняет условия? ЮЖ>В этом вся проблема. Здесь требуется 100% покрытие тестами всех ассертов, что малореально и приводит к удорожанию разработки.
Хм, каких ассертов? Клиентских чтоли? Ок, я был не уверен говорити ли вы про границы компонентов. Судя по всему не говорите. Речь идет тока про код, клиенты которого гарантированно не поменяються. Я придерживаюсь мнения что такого не бывает. Относительго гибкие дизаны, подразумевают что компонент это черный ящик. Он должен гарантированно правильно работать со всеми своими клиентами. Если не может — сообщить об этом. Соотвевенно в релизе эти проверки должны остаться.
ЮЖ>Очевидный вывод
Да это очевидный, жаль что Майкры прикрыли работу в этом направлении, оставив в Шарпе 4.0 костыли... Это я про SpecSharp
ЮЖ>При разрастании масшабов сразу появляются границы подсистем на которых статический контроль не работает. Эти места — места получения данных извне(в широком смысле слова), здесь проверки должны быть и должны тестироваться. Все это масшабируется от функций до сервисов и др. более крупных единиц функциональности.
Угу, вопрос перемещаеться в плоскость что называть границами... Для меня например граница это все публичное или интернал. А соотвевеннло у меня все(после всех очевидных комилтай м проверок) проверки остаються в релизе.
Здравствуйте, Mike Chaliy, Вы писали:
... MC>Речь идет тока про код, клиенты которого гарантированно не поменяються. Я придерживаюсь мнения что такого не бывает.
Я на это смотрю более радикально — любой код меняется(произойдет ли это на самом деле — второстепенно).
MC>Относительго гибкие дизаны, подразумевают что компонент это черный ящик.
Реализация — несомненно черный ящик, но публичный контракт, наоборот — является(должен по крайней мере) четко определенным. От компонента(некой единицы функциональности) мне важно что он делает(контракт), а не как(реализация). Здесь кстати есть место для ошибок — протаскивание лишних деталей реализации в контракт.
MC>Он должен гарантированно правильно работать со всеми своими клиентами.
Сначала отвечу на это:
MC> вопрос перемещаеться в плоскость что называть границами...
В моем варианте: границы это места "обрастания" различных сырых данных инвариантами. Таких мест достаточно мало — user input, чтение из файлов, прием данных по сети, и т.п. Можно еще добавить отложенные инварианты и кривые api.
У всех эти "генераторов" данных есть один общий элемент — отсутствие(полное или частичное) постусловий, которые говорят о содержимом. Для некоторых это обусловленно их дизайном — в "gereric" text box можно вводить не только возраст(min-max) а произвольное число и даже текст. И только потом(в gui framework или явно) производится заточка под возможность ввода только валидного, например возраста. С другой стороны — их природой: нет гарантии что из файла мы прочитаем то же что и записали. Вероятность порчи данных можно снизить с помощью привелегий, административным путем, но все равно 100% гарантию мы не обеспечим. Некоторую гарантию можно дать для БД, но до тех пор, пока в таблицы не лезут руками.
Вот это для меня границы. Весть такой "ввод" проверяется явно(благо мест очень мало) и в дальнейшем данные(валидные) свободно гуляют в системе. В первом приближении это похоже на 'если объект существует, то он находится в валидном состоянии', positive_float помните?
В эту схему органически вписываются и данные для которых инварианты не известны или являются взаимоисключащими в различных сценариях. Полноценными "объектами"(это не обязательно объекты с точки зрения языка) они становятся в момент соответствующих проверок. Или не становятся — определяется, опять же, результатом проверок.
Вот эти проверки — никогда не удаляются из релиза. Удаляются только проверки(это уже ассерты) "внутри" кода, который использует проверенные данные. Эти ассерты просто наблюдают за состоянием программы при отладке/тестировании, помогая обноружать баги, допущенные при изменениях кода, причем эти баги вызваны нарушениями контрактов и должны быть исправлены, т.к. работу программы вне рамках контракта гарантировать никто не будет.
Unit-тесты — это по сути теже самые ассерты, только вынесеные в отдельное окружение. Почему-то для них вопроса 'включения в релиз' не стоит.
Теперь возвращаясь к: MC>Он должен гарантированно правильно работать со всеми своими клиентами.
У него есть контракт, пусть клиенты его соблюдают, тогда и он будет правильно работать.
Хотя это похоже на подход "от реализации" — компонент главный, а пользователи — второстепенны. Это не плохой, не хороший подход — он просто другой. При тотальном использовании Design by Contract пляшут от требований — начиная от высокоуровневых use cases(это тоже форма контрактов, причем образующая) и углубляясь по "самые помидоры".
Подход "от реализации" — это написание больших библиотек, фреймворков, вспомогательных сервисов и т.п. Основная сложность — частичная неизвестность сценариев использования, наличие некоторых предположений и обобщений. Это обычно приводит к тому, что внешний интерфейс(библиотеки/фреймворка/итп) или его части должны рассматриваться в качестве границы(см. выше) как клиентом, так и реализацией (например winapi функции проверяют свои парамеры на недопустимые значения).
Ну и никто не запрещает(после определения внешнего интерфейса) при реализации использовать DbC.
MC>Соотвевенно в релизе эти проверки должны остаться.
Только на границах.
Но если идти от реализации снизу вверх, и каждый модуль(или функцию) рассматривать как совершенно обособленную, то да — она имеет границу на входе и в точках использования других функций, а те в свою очередь... и т.д. рекурсивно.