P.7: Catch run-time errors early 尽早捕捉执行时错误

Reason(原因)

Avoid "mysterious" crashes. Avoid errors leading to (possibly unrecognized) wrong results.

避免“原因不明的”崩溃。避免导致(可能是没有被认识的)错误结果的问题。

Example(示例)

 

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 // ...}

Here we made a small error in ​​use1​​​ that will lead to corrupted data or a crash. The (pointer, count)-style interface leaves ​​increment1()​​​ with no realistic way of defending itself against out-of-range errors. If we could check subscripts for out of range access, then the error would not be discovered until ​​p[10]​​ was accessed. We could check earlier and improve the code:

这里我们在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}); // maybe typo, maybe m <= n is supposed // ...}

Now, ​​m <= n​​​ can be checked at the point of call (early) rather than later. If all we had was a typo so that we meant to use ​​n​​ as the bound, the code could be further simplified (eliminating the possibility of an error):

现在,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    // ...}

Example, bad(反面示例)

Don't repeatedly check the same value. Don't pass structured data as strings:

不要重复检查同一个值,不要将结构化数据作为字符串传递。

 

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()); // ...}

The date is validated twice (by the ​​Date​​ constructor) and passed as a character string (unstructured data).

date数据被两次检查(被Data类的构造函数)并且作为字符串传递(非构造化数据)

Example(示例)

Excess checking can be costly. There are cases where checking early is dumb because you may not ever need the value, or may only need part of the value that is more easily checked than the whole.  Similarly, don't add validity checks that change the asymptotic behavior of your interface (e.g., don't add a ​​O(n)​​​ check to an interface with an average complexity of ​​O(1)​​).

过度检查代价高昂。有些时候提早检查会显得很愚蠢:你可能永远都用不到那个值或者你可能只会用到数据的一部分,而这一部分又比全体更容易检查。类似地,也不要增加可能改变接口的时间复杂度特性的有效性检查(例如不要向一个时间复杂度为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); }
???};

The physical law for a jet (​​e * e < x * x + y * y + z * z​​) is not an invariant because of the possibility for measurement errors.

由于测量误差的存在,关于jet的物理定律(​​e * e < x * x + y * y + z * z​​)不是恒定成立的。

??? T.B.D

译者注:作者可能是想表达:示例代码的情况中是否在构造函数中增加检查就是一个令人纠结的问题。

Enforcement(实施建议)

  • Look at pointers and arrays: Do range-checking early and not repeatedly 找到指针和数组,尽早进行范围检查但不要重复检查
  • Look at conversions: Eliminate or mark narrowing conversions 找到类型检查,排除或确定进行窄化转换
  • Look for unchecked values coming from input 找到来此(外部)输入的没有经过检查的数据
  • Look for structured data (objects of classes with invariants) being converted into strings 找到被转换为字符串的结构化数据(包换约束条件的对象或类)
  • ??? T.B.D

 

觉得本文有帮助,欢迎点赞并分享给更多的朋友!

阅读更多更新文章,请关注微信公众号【面向对象思考】