эффективность проверки предварительных условий if-throw и принцип DRY - PullRequest
1 голос
/ 15 февраля 2020

Многие ресурсы inte rnet требуют проверки предварительных условий в функциях API с помощью if (something_is_wrong) throw Exception{} вместо assert(!something_is_wrong), и я вижу в этом некоторые хорошие моменты. Тем не менее, я боюсь, что такое использование может привести к удвоению тех же проверок:

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);
  }
}

, вероятно, будет выполняться как

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

, если вызов не будет встроен и оптимизирован путем вырезания один из if с, я думаю. Кроме того, написание чека дважды (в foo() и в caller()) может нарушать правило DRY. Поэтому, возможно, мне следует go, чтобы

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

, чтобы избежать повторения этих проверок предварительных условий, обеспечивая небольшую производительность и большую ремонтопригодность в случае изменения контракта функции.

Однако я не всегда могу применить такие логи c. Представьте, что std::vector имеет только at(), но не operator[]:

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

Этот код приводит к дополнительным проверкам O (N)! Не слишком ли это много? Даже если он оптимизирован так же, как описано выше, как насчет ситуаций с косвенными вызовами или длинными функциями, которые, вероятно, не будут встроены?

Итак, должна ли моя программа быть написана в соответствии с правилами ниже?

  • , если функция API, вероятно, не будет встроенной или ожидается многократный вызов с проверками на сайте вызова (см. Пример vector), assert() ее предварительные условия ( внутри него);
  • функции броска try-catch вместо проверки их предварительных условий перед вызовом (последний, кажется, прерывается DRY).

Если нет, то почему?

1 Ответ

1 голос
/ 15 февраля 2020

Итак, есть две отдельные вещи, о которых вы говорите: DRY и производительность.

DRY касается обслуживания и структуры кода и не относится к коду, который вы не можете контролировать , Итак, если API является черным ящиком, и внутри него есть код, который вы не можете изменить, но вам нужно иметь его отдельно, тогда я бы не подумал, что это не DRY, чтобы повторить его в вашем коде. Y - это ты сам.

Но ты все равно можешь заботиться о производительности Если вы измеряете проблему с производительностью, то исправьте ее с помощью того, что имеет смысл - даже если это анти- DRY (или это нормально, если оно есть).

Но, если вы контролируете обе стороны (API и клиент), и вы действительно хотите чистое, неповторяющееся, производительное решение, тогда есть шаблон, похожий на этот псевдокод Я не знаю имени, но я думаю о нем как о "Proof Provision"

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

API fun c precondition_check возвращает функцию, которая фиксирует myNum в нем и не ' Не нужно проверять, соответствует ли оно предварительным условиям, потому что оно было создано только в том случае, если оно было создано.

...