Указывайте постусловия
Причина
Для выявления недоразумений относительно результата и возможного обнаружения ошибочных реализаций.
Пример (плохой)
Рассмотрим:
int area(int height, int width) { return height * width; } // плохо
Здесь мы (неосторожно) опустили спецификацию предусловий, поэтому неясно, что height и width должны быть положительными. Мы также опустили спецификацию постусловий, поэтому неочевидно, что алгоритм (height * width) неверен для площадей, превышающих наибольшее целое число. Может произойти переполнение. Рассмотрите использование:
int area(int height, int width)
{
auto res = height * width;
Ensures(res > 0);
return res;
}
Пример (плохой)
Рассмотрим знаменитую ошибку безопасности:
void f() // проблематично
{
char buffer[MAX];
// ...
memset(buffer, 0, sizeof(buffer));
}
Не было постусловия, указывающего, что буфер должен быть очищен, и оптимизатор устранил явно избыточный вызов memset():
void f() // лучше
{
char buffer[MAX];
// ...
memset(buffer, 0, sizeof(buffer));
Ensures(buffer[0] == 0);
}
Примечание
Постусловия часто неформально указываются в комментарии, описывающем назначение функции; Ensures() можно использовать для придания этому более систематического, видимого и проверяемого характера.
Примечание
Постусловия особенно важны, когда они относятся к чему-то, что непосредственно не отражается в возвращаемом результате, например, к состоянию используемой структуры данных.
Пример
Рассмотрим функцию, манипулирующую Record с использованием mutex для избежания гонок:
mutex m;
void manipulate(Record& r) // не делайте так
{
m.lock();
// ... нет m.unlock() ...
}
Здесь мы «забыли» указать, что mutex должен быть освобождён, поэтому мы не знаем, является ли невыполнение освобождения mutex ошибкой или задуманной возможностью. Указание постусловия прояснило бы ситуацию:
void manipulate(Record& r) // постусловие: m разблокирован при выходе
{
m.lock();
// ... нет m.unlock() ...
}
Ошибка теперь очевидна (но только для человека, читающего комментарии).
Ещё лучше использовать RAII для обеспечения постусловия («блокировка должна быть освобождена») в коде:
void manipulate(Record& r) // лучший вариант
{
lock_guard<mutex> _ {m};
// ...
}
Примечание
В идеале, постусловия указываются в интерфейсе/объявлении, чтобы пользователи могли легко их видеть. В интерфейсе могут быть указаны только постусловия, относящиеся к пользователям. Постусловия, относящиеся только к внутреннему состоянию, принадлежат определению/реализации.
Контроль
(Не применяется) Это философский руководящий принцип, нецелесообразный для прямой проверки в общем случае. Для многих цепочек инструментов существуют специализированные проверки предметной области (например, проверки удержания блокировок).