Обязательно ли проверять предварительные условия?

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

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

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

Какова лучшая практика здесь?

9 ответов

Решение

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

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

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

Я думаю, что это зависит от того, как организована команда: проверьте входные данные, которые поступают извне вашей команды.

  • Проверьте входные данные от конечных пользователей
  • Проверьте входные данные от программных компонентов, написанных другими командами
  • Доверяйте вкладам, полученным из вашего собственного компонента / из вашей собственной команды.

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

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

Угадай, все дело в законе Деметры, говори только с друзьями и так далее.

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

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

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

У меня есть привычка различать проверку и утверждение предварительных условий, в зависимости (как люди указывали в комментариях) от того, поступает ли вызов извне (непроверенное исключение, может произойти) или изнутри (утверждают, не должно происходить)).

В идеале, в производственной системе не должно быть штрафов за утверждения, и вы могли бы даже использовать механизм, подобный Design By Contract(TM), для статического выполнения утверждений.

Как и во всем, оцените ваши требования, чтобы найти лучшее решение для каждой ситуации.

Когда предварительные условия легче проверить ("указатель не равен нулю"), вы также можете делать это часто. Предварительные условия, которые трудно проверить ("указывает на действительную строку с нулевым символом в конце") или которые дороги во времени, памяти или других ресурсах, могут обрабатываться другим способом. Используйте принцип Парето и соберите низко висящие фрукты.

// C, C++:
void example(char const* s) {
  // precondition: s points to a valid null-terminated string
  assert(s); // tests that s is non-null, which is required for it to point to
  // a valid null-terminated string.  the real test is nearly impossible from
  // within this function
}

Гарантия предварительных условий является обязанностью вызывающего абонента. Из-за этого некоторые языки предлагают конструкцию "assert", которую при желании можно пропустить (например, определение NDEBUG для C/C++, переключатель командной строки для Python), чтобы можно было более тщательно тестировать предварительные условия в специальных сборках без влияния на конечную производительность. Однако, как использовать assert, может быть жаркой дискуссией - опять же, выясните ваши требования и будьте последовательны.

Это немного старый вопрос, но нет, предварительные условия не нужно проверять каждый раз. Это действительно зависит.

Например, что если у вас есть бинарный поиск по вектору. Предварительным условием является отсортированный вектор. Теперь, если вы каждый раз проверяете, отсортирован ли вектор, это занимает линейное время (для каждого вектора), поэтому оно неэффективно. Клиент должен знать о предварительном условии и обязательно выполнить его.

Я думаю, что лучшая практика - делать эти проверки, только если они однажды потерпят неудачу. Если поможет, когда вы делаете следующее.

отладка

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

Однако, если вы выставите общедоступный API, однажды кто-то не выполнит ваше предварительное условие. Чем дальше человек, обслуживающий вызывающий модуль от вас (в организационной структуре и в физическом местоположении), тем больше вероятность того, что это произойдет. И когда это происходит, четкое заявление о сбое предусловия со спецификацией, где это произошло, может сэкономить часы отладки. Закон Утечки Абстракций по-прежнему актуален...

контроль качества

Ошибка предварительного условия помогает QA отлаживать свои тесты. Если модульный тест для модуля приводит к сбою предусловия модуля, это означает, что тест неправильный, а не ваш код. (Или, что ваша предварительная проверка неверна, но это менее вероятно).

Если одним из способов выполнения QA является статический анализ, то выполняется проверка предварительных условий, если они имеют определенную запись (например, используются только эти проверки). assert_precondition макро), тоже поможет. В статическом анализе очень важно различать неверные ошибки ввода и исходного кода.

Документация

Если у вас мало времени для создания документации, вы можете сделать так, чтобы ваш код помогал тексту, который его сопровождает. Четкие и видимые проверки предварительных условий, которые воспринимаются отдельно от остальной части реализации, в некоторой степени "документируют" возможные входные данные. (Другой способ документировать ваш код - писать модульные тесты).

Лучше всего всегда проверять их.

Другие вопросы по тегам