Это, собственно, ответ на сообщение из
этойАвтор: Odi$$ey
Дата: 02.04.08
ветки.
_FR>Вот, кстати, отыскал одной С++-ной библиотеке (comutil.h из поставки 2008ой студии):
_FR>_FR>inline BSTR _bstr_t::Detach()
_FR>{
_FR> _COM_ASSERT(m_Data != NULL && m_Data->RefCount() == 1);
_FR> if (m_Data != NULL && m_Data->RefCount() == 1) {
_FR> BSTR b = m_Data->GetWString();
_FR> m_Data->GetWString() = NULL;
_FR> _Free();
_FR> return b;
_FR> }
_FR> else {
_FR> _com_issue_error(E_POINTER);
_FR> }
_FR>}
_FR>
_FR>Вот это красота. На ней моя позиция и основана: если условие зависит от "наружных" данных, то надо делать проверки и бросать исключения. Асерт тут опционален и требует отдельного обсуждения.
Никакая это не красота... И вот почему:
Программу можно рассматривать как КА с множеством состояний и условий переходов. Каждое состояние(функция) имеет пред и пост условия. Перейти из одного состояния в другое мы можем только когда будут выполнены его предусловия. То есть: нормальная работа программы заключается просто в вычислении цепочки переходов по входным данным на основании пред/пост условий. Ключевой момент: вычисления выполняются(причем явно) _до_ перехода, а не после. В качестве аналога можно рассмотреть лексический анализатор которому для перехода в новое состояние достаточно знать один символ. Значение этого символа в текущем состоянии даст следующее состоянии и т.д.
Пример:
void stateA(input i)
{
if(i == B)
stateB(i);
else if(i == C)
stateC(i);
else
stateD(i);
}
void stateB(input i)
{
assert(i == B);
if(i != B) // абсурд...
//...
}
По аналогии с лексером: в случае если все stateX сами проверяют свои параметры — лексер получится с откатами. он входит в одно состояние — обламывается, запускает процедуру отката, входит в следующее — обламывается и т.д. Обломы соответственно проявляются в виде исключений(кодов возврата) что приводит тому, что в КА появляются новые(искуственные) состояния. Если же мы выполняем все пред/пост условия, то никаких лишних движений выполнять не приходится.
Еще пример:
// pre: a > 0
void f1(int a);
// pre: a, b > 0
void f2(int a, int b)
{
assert(a > 0); // это обязано выполняться
assert(b > 0); // и это тоже, иначе - нарушение контракта со всеми вытекающими
if(b == 2)
f1(a); // Здесь не должно быть никаких проверок,
// т.к. предусловие для f1 уже выполнено
///...
}
Теперь вот об этом:
// pre: a > 0
void f1(int a)
{
assert(a > 0);
if(a <= 0) // все так же - абсурд..
throw invalid_argument();
///...
}
Вопрос: Почему проверка производится именно здесь ? Если клиент заинтересован в успешном выполнении, то но сам может проверить условие... Ответ, вероятно, будет такой: "чтобы не дублировать проверки при многократных вызовах(в различных местах)"
Но с другой стороны:
Как уже было сказано выше, это насильно создает новые состояния. В клинических случаях они сливаются в одно — в корневом try/catch:
try
{
return real_main(args);
}
catch(...)
{
message("oops");
return -1;
}
Тем самым замазывая/скрывая потенциальную ошибку.
Хорошо, клиент может перехватывать исключения(в отдельном состоянии):
// pre: a > 0
void f1(int a)
{
assert(a > 0);
if(a <= 0)
throw invalid_argument();
}
void f(int a)
{
try
{
f1(a); // на 'а' нет ограничений
}catch(invalid_argument)
{
stateR();
}
}
Но чем это лучше чем:
void f(int a)
{
if(a <= 0)
stateR();
}
?
Еще небольшое имхо по поводу 'catch(...)':
Если исключение возбуждается только для того что бы быть перехваченным в корневом обработчике, то(за редкими исключениями) имеет место такая трансформация:
было:
if(!condition())
throw exception();
стало:
assert(condition());
Иначе это "замазывание" потенциальной ошибки.
P.S. Такой поход c пред/пост условиями — собственно и есть subj. При чем тут assert'ы ? Они просто наблюдают за выполненим контракта и процессами перехода в тех местах где это не может сделать компилятор(статически). И если срабатывает assert, то код требует изменения. Точно так же как и в случае если "срабатывает"(compilation failed) компилятор. В идеале нарушения контракта надо детектировать на этапе компиляции. Превращение нарушений контракта в ошибки(исключения, etc.) выполнения недопустимы. Точнее, в работающей программе нарушений контракта не может быть в принципе.