首页 > 解决方案 > if-throw precondition check effectiveness and the DRY principle

问题描述

A lot of internet resources insist on checking preconditions in API functions via if (something_is_wrong) throw Exception{} instead of assert(!something_is_wrong) and I see some good points in it. However, I'm afraid such usage may result in doubling the same checks:

void foo(int positive) {
  if (positive < 1) {
    throw std::invalid_argument("param1 must be positive");
  }
}
void caller() {
  int number = get_number_somehow();
  if (number >= 1) {
    foo(number);
  }
}

will probably execute like

int number = get_number_somehow();
if (number >= 1) {
  if (number < 1) {
    throw std::invalid_argument("param must be positive");
  }
}

unless the call will actually be inlined and optimized by cutting out one of the ifs, I guess. Besides, writing the check twice (in foo() and in caller()) might be violating the DRY rule. Therefore, maybe I should go for

void caller() {
  int number = get_number_somehow();
  try {
    foo(number);
  } catch (std::invalid_argument const&) {
    // handle or whatever
  }
}

to evade repeating myself with those precondition checks, providing a bit of performance and a lot of maintainability in case of a function contract change.

However, I can't always apply such logic. Imagine std::vector having only at() but not operator[]:

for (int i = 0; i < vector.size(); ++i) {
  bar(vector.at(i)); // each index is checked twice: by the loop and by at()
}

This code results in extra O(N) checks! Isn't it too much? Even if it is optimized out the same way as above, what about such situations with indirect calls or long functions, which probably won't be inlined?

So, should my program be written according to the rules below?

If not, why?

标签: c++exceptionassertdrypreconditions

解决方案


所以,你在谈论两个不同的事情:干燥和性能。

DRY 是关于代码维护和结构的,并不真正适用于你无法控制的代码。因此,如果 API 是一个黑盒,并且其中恰好有您无法更改的代码,但您需要单独拥有,那么我不会认为它不是 DRY 在您的代码。Y是你自己。

但是,您仍然可以关心性能。如果你衡量一个性能问题,然后用任何有意义的方法来修复它——即使它是抗 DRY 的(或者如果它是可以的)。

但是,如果你控制双方(API 和客户端)并且你真的想要一个纯粹的、无重复的、高性能的解决方案,那么就会有一个类似于这个伪代码的模式。我不知道名字,但我认为它是“提供证明”

let fn = precondition_check(myNum)
if fn != nil {
    // the existence of fn is proof that myNum meets preconditions
    fn()
}

API funcprecondition_check返回一个捕获其中的函数myNum并且不需要检查它是否满足先决条件,因为它只有在满足的情况下才被创建。


推荐阅读