原因
避免“神秘的”崩溃,避免错误导致(可能无法识别的)错误结果。
例子
void increment1(int* p, int n) // bad: error-prone
{
for (int i = 0; i < n; ++i) ++p[i];
}
void use1(int m)
{
const int n = 10;
int a[n] = {};
// ...
increment1(a, m); // maybe typo, maybe m <= n is supposed
// but assume that m == 20
// ...
}
这里我们在use1
中犯了一个可能导致损坏数据或者崩溃的小错误。(pointer,count)风格的接口导致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}); // maybe typo, maybe m <= n is supposed
// ...
}
现在,m <= n
可以在指针调用前检查而不是之后。如果我们想用n
作为边界但是拼写错误,代码可以进一步简化(消除错误的可能性):
void use3(int m)
{
const int n = 10;
int a[n] = {};
// ...
increment2(a); // the number of elements of a need not be repeated
// ...
}
反例
不要重复检查相同的值。不要以字符串传递结构化数据传递:
Date read_date(istream& is); // read date from istream
Date extract_date(const string& s); // extract date from string
void user1(const string& date) // manipulate date
{
auto d = extract_date(date);
// ...
}
void user2()
{
Date d = read_date(cin);
// ...
user1(d.to_string());
// ...
}
日期被验证两次(通过Date
结构体)并且通过字符串传递(非结构化的数据)。
例子
过度检查可能代价高昂。在某些情况下,早期检查效率低下,因为您可能永远不需要该值,或者可能只需要比整体更容易检查的部分值。类似的,不要增加改变接口渐近行为的有效性检查(例如,不能给一个平均复杂度O(1)
的接口添加一个O(n)
的检查)。
class Jet { // Physics says: 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)
{
// Should I check here that the values are physically meaningful?
}
float m() const
{
// Should I handle the degenerate case here?
return sqrt(x * x + y * y + z * z - e * e);
}
???
};
由于可能的测量误差,jet的物理规律并不是不变的。
补充
- 查看指针和数组:尽早进行范围检查而且不要重复检查
- 查看转换:消除或者缩小窄化转换
- 查找来自输入的未经检查的值
- 查找结构化数据(对象或者不变的类)被转换为字符串
- ???