Мне интересно услышать, какую технику (ы) вы используете для проверки внутреннего состояния объекта во время операции, которая, с его собственной точки зрения, может дать сбой только из-за плохого внутреннего состояния или инвариантного нарушения.
Мой основной упор делается на C ++, поскольку в C # официальный и распространенный способ - генерировать исключение, а в C ++ не существует только одного одного способа сделать это (ну, на самом деле, в C # тоже нет). Я это знаю).
Обратите внимание, что я не говорю о проверке параметров функции, а скорее о проверках целостности инварианта класса.
Например, допустим, мы хотим, чтобы объект 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
).
Предпочитаете ли вы какой-либо из них или у вас есть другие способы достичь этого?