Мне интересно узнать, какую технику вы используете для проверки внутреннего состояния объекта во время операции, которая из его собственной точки зрения может терпеть неудачу из-за плохого внутреннего состояния или инвариантного нарушения.
Мой основной упор делается на С++, поскольку в С# официальным и распространенным способом является исключение, а на С++ существует не один единственный способ сделать это (нормально, на самом деле не на С#, я это знаю).
Обратите внимание, что я не говорит о проверке параметров функции, но скорее как проверки целостности класса.
Например, предположим, что мы хотим, чтобы объект Printer
выполнял арифметическое задание печати Queue
. Пользователю Printer
эта операция может быть успешной только потому, что результат асинхронной очереди приходит в другое время. Таким образом, нет никакого соответствующего кода ошибки для передачи вызывающему абоненту.
Но для объекта Printer
эта операция может завершиться неудачно, если внутреннее состояние плохое, то есть инвариант класса сломан, что в основном означает: ошибка. Это условие не обязательно представляет интерес для пользователя объекта Printer
.
Лично я склонен смешивать три стиля проверки внутреннего состояния, и я не могу решить, какой из них лучше, если таковой имеется, только тот, который является самым худшим. Я хотел бы услышать ваши взгляды на них, а также поделиться своим опытом и мыслями по этому вопросу.
Первый стиль, который я использую - лучше сбой управляемым способом, чем поврежденные данные:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in both release and debug builds.
// Never proceed with the queuing in a bad state.
if(!IsValidState())
{
throw InvalidOperationException();
}
// Continue with queuing, parameter checking, etc.
// Internal state is guaranteed to be good.
}
Второй стиль, который я использую, - лучше сбой, неконтролируемый, чем поврежденные данные:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in debug builds only.
// Break into the debugger in debug builds.
// Always proceed with the queuing, also in a bad state.
DebugAssert(IsValidState());
// Continue with queuing, parameter checking, etc.
// Generally, behavior is now undefined, because of bad internal state.
// But, specifically, this often means an access violation when
// a NULL pointer is dereferenced, or something similar, and that crash will
// generate a dump file that can be used to find the error cause during
// testing before shipping the product.
}
Третий стиль, который я использую - лучше молча и защищать, чем поврежденные данные:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in both release and debug builds.
// Break into the debugger in debug builds.
// Never proceed with the queuing in a bad state.
// This object will likely never again succeed in queuing anything.
if(!IsValidState())
{
DebugBreak();
return;
}
// Continue with defenestration.
// Internal state is guaranteed to be good.
}
Мои комментарии к стилям:
- Я думаю, что предпочитаю второй стиль, когда сбой не скрыт, при условии, что нарушение прав действительно вызывает сбой.
- Если это не указатель NULL, связанный с инвариантом, я склоняюсь к первому стилю.
- Мне действительно не нравится третий стиль, так как он скроет множество ошибок, но я знаю людей, которые предпочитают его в производственном коде, потому что он создает иллюзию надежного программного обеспечения, которое не падает (функции просто перестанут функционировать, как в очереди на сломанном объекте
Printer
).
Вы предпочитаете какой-либо из них или у вас есть другие способы достижения этого?