Обнаруживайте ошибки времени выполнения как можно раньше
Причина
Избегайте «таинственных» сбоев. Избегайте ошибок, ведущих к (возможно, неопознанным) неверным результатам.
Пример
void increment1(int* p, int n) // плохо: подвержено ошибкам
{
for (int i = 0; i < n; ++i) ++p[i];
}
void use1(int m)
{
const int n = 10;
int a[n] = {};
// ...
increment1(a, m); // возможно, опечатка, возможно, предполагается m <= n
// но предположим, что m == 20
// ...
}
Здесь мы допустили небольшую ошибку в use1, которая приведёт к повреждению данных или сбою. Интерфейс в стиле (указатель, количество) не оставляет increment1() реалистичного способа защититься от ошибок выхода за пределы диапазона. Если бы мы могли проверять индексы на выход за границы, ошибка не была бы обнаружена до обращения к p[10]. Мы могли бы проверять раньше и улучшить код:
void increment2(span<int> p)
{
for (int& x : p) ++x;
}
void use2(int m)
{
const int n = 10;
int a[n] = {};
// ...
increment2({a, m}); // возможно, опечатка, возможно, предполагается m <= n
// ...
}
Теперь m <= n можно проверить в точке вызова (рано), а не позже. Если это была просто опечатка и мы имели в виду использовать n в качестве границы, код можно упростить ещё больше (исключив возможность ошибки):
void use3(int m)
{
const int n = 10;
int a[n] = {};
// ...
increment2(a); // количество элементов a не нужно повторять
// ...
}
Пример (плохой)
Не проверяйте одно и то же значение многократно. Не передавайте структурированные данные в виде строк:
Date read_date(istream& is); // читает дату из istream
Date extract_date(const string& s); // извлекает дату из строки
void user1(const string& date) // работает с датой
{
auto d = extract_date(date);
// ...
}
void user2()
{
Date d = read_date(cin);
// ...
user1(d.to_string());
// ...
}
Дата проверяется дважды (конструктором Date) и передаётся как строка символов (неструктурированные данные).
Пример
Избыточная проверка может быть затратной. Есть случаи, когда ранняя проверка неэффективна, потому что вы можете никогда не использовать значение или вам может понадобиться только часть значения, которую проще проверить, чем целое. Аналогично, не добавляйте проверки достоверности, меняющие асимптотическое поведение вашего интерфейса (например, не добавляйте проверку O(n) к интерфейсу со средней сложностью O(1)).
class Jet { // Физика говорит: e * e < x * x + y * y + z * z
float x;
float y;
float z;
float e;
public:
Jet(float x, float y, float z, float e)
:x(x), y(y), z(z), e(e)
{
// Следует ли проверять здесь, что значения физически осмысленны?
}
float m() const
{
// Следует ли обрабатывать вырожденный случай здесь?
return sqrt(x * x + y * y + z * z - e * e);
}
???
};
Физический закон для джета (e * e < x * x + y * y + z * z) не является инвариантом из-за возможности ошибок измерения.
???
Контроль
- Смотреть на указатели и массивы: выполнять проверку диапазона рано и не повторно
- Смотреть на преобразования: устранять или помечать сужающие преобразования
- Искать непроверенные значения, поступающие из входных данных
- Искать структурированные данные (объекты классов с инвариантами), преобразуемые в строки
- ???