Проверяется ли избыточная проверка на соответствие лучшим практикам?


16

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

У меня есть программа на Python, в которой я ...

  1. используйте argparse required=Trueдля применения двух аргументов, которые оба являются именами файлов. первое имя входного файла, второе имя выходного файла
  2. есть функция, readFromInputFileкоторая сначала проверяет, было ли введено имя входного файла
  3. есть функция, writeToOutputFileкоторая сначала проверяет, было ли введено имя выходного файла

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

(Кроме того, если чтение или запись завершается неудачно, try exceptв каждой функции у меня есть соответствующее сообщение об ошибке.)

Мой вопрос: лучше ли избегать избыточной проверки состояния? Должна ли логика программы быть настолько надежной, чтобы проверки проводились только один раз? Есть ли хорошие примеры, иллюстрирующие это или обратное?

РЕДАКТИРОВАТЬ: Спасибо всем за ответы! Я чему-то научился у каждого. Увидев так много точек зрения, я лучше понимаю, как подойти к этой проблеме и найти решение на основе моих требований. Спасибо!


Вот обобщенная версия вашего вопроса: softwareengineering.stackexchange.com/questions/19549/… . Я бы не сказал, что это дубликат, поскольку он имеет гораздо больший фокус, но, возможно, это помогает.
Док Браун

Ответы:


15

То, что вы просите, называется «робастность», и нет правильного или неправильного ответа. Это зависит от размера и сложности программы, количества людей, работающих в ней, и важности обнаружения сбоев.

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

В вашем случае вы должны решить для себя, какой жизненный цикл вы ожидаете для своей программы. Это программа, которую вы ожидаете использовать и обслуживать годами? Тогда добавление избыточной проверки, вероятно, будет лучше, поскольку не исключено, что ваш код будет подвергнут рефакторингу в будущем, а ваши функции readи writeфункции могут быть использованы в другом контексте.

Или это небольшая программа только для учебы или для развлечения? Тогда эти двойные проверки не будут необходимы.

В контексте «Чистого кода» можно было бы спросить, нарушает ли двойная проверка принцип СУХОГО. На самом деле, иногда это происходит, по крайней мере, в некоторой степени: проверка входных данных может быть интерпретирована как часть бизнес-логики программы, и наличие этого в двух местах может привести к обычным проблемам обслуживания, вызванным нарушением DRY. Устойчивость против DRY часто является компромиссом - устойчивость требует избыточности в коде, в то время как DRY пытается минимизировать избыточность. А с ростом сложности программы надежность становится все более важной, чем СУХОЙ в валидации.

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

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

Значит ли это, что вам нужно изменить двойную проверку в двух местах? Вероятно, нет, такое требование приводит к одному изменению при вызове argparse, но никаких изменений в writeToOutputFile: эта функция все равно будет требовать имя файла. Так что в вашем случае я бы проголосовал за проверку входных данных дважды, риск возникновения проблем с обслуживанием из-за необходимости изменения двух мест ИМХО намного ниже, чем риск возникновения проблем с обслуживанием из-за замаскированных ошибок, вызванных слишком малым количеством проверок.


«... границы между компонентами в форме открытых API-интерфейсов ...» Я наблюдаю, что «классы выходят за границы», так сказать. Так что нужен класс; согласованный бизнес-класс. Из этого ОП я делаю вывод, что вездесущий принцип «это просто, так что класс не нужен» работает здесь. Может существовать простой класс, обертывающий «первичный объект», обеспечивающий соблюдение бизнес-правил, таких как «файл должен иметь имя», которые не только СУШИЛИ существующий код, но и сохранили его в будущем.
radarbob

@radarbob: то, что я написал, не ограничивается ООП или компонентами в форме классов. Это также относится к произвольным библиотекам с открытым API, объектно-ориентированным или нет.
Док Браун

5

Избыточность не грех. Излишняя избыточность есть.

  1. Если readFromInputFile()и writeToOutputFile()являются открытыми функциями (и в соответствии с соглашениями об именах Python они есть, поскольку их имена не начинаются с двух подчеркиваний), то функции могут когда-нибудь использоваться кем-то, кто вообще избегает argparse. Это означает, что когда они пропускают аргументы, они не видят ваше пользовательское сообщение об ошибке argparse.

  2. Если readFromInputFile()и writeToOutputFile()проверить сами параметры, вы снова получите возможность показать пользовательское сообщение об ошибке, объясняющее необходимость имен файлов.

  3. Если readFromInputFile()и writeToOutputFile()не проверять сами параметры, пользовательское сообщение об ошибке не отображается. Пользователь должен будет самостоятельно определить получающееся исключение.

Все сводится к 3. Напишите некоторый код, который фактически использует эти функции, избегая argparse, и выдайте сообщение об ошибке. Представьте, что вы вообще не заглядывали в эти функции и просто доверяете их именам, чтобы обеспечить достаточное понимание для использования. Когда это все, что вы знаете, есть ли способ запутаться в исключении? Есть ли необходимость в настраиваемом сообщении об ошибке?

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


4

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

Если программист имеет привычку писать методы, которые делают предположения о том, что было передано, основываясь на таких идеях, как «если это сломано, нам есть о чем беспокоиться» или «параметр X не может иметь значение Y, потому что остальные код предотвращает это ", и вдруг у вас больше нет независимых, разъединенных компонентов. Ваши компоненты существенно зависят от более широкой системы. Это своего рода тонкая тесная связь и приводит к экспоненциальному увеличению общей стоимости владения по мере увеличения сложности системы.

Обратите внимание, что это может означать, что вы проверяете одну и ту же информацию более одного раза. Но это нормально. Каждый компонент отвечает за свою собственную валидацию по-своему . Это не является нарушением DRY, потому что проверки выполняются независимыми независимыми компонентами, и изменение проверки в одном не обязательно должно точно повторяться в другом. Здесь нет избыточности. X несет ответственность за проверку своих входных данных для собственных нужд и передачу их Y. Y несет собственную ответственность за проверку своих собственных входных данных для своих нужд .


1

Предположим, у вас есть функция (в C)

void readInputFile (const char* path);

И вы не можете найти документацию о пути. А потом вы смотрите на реализацию, и это говорит

void readInputFile (const char* path)
{
    assert (path != NULL && strlen (path) > 0);

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


0

В общем, двойная проверка не всегда хорошо или плохо. В вашем конкретном случае всегда много аспектов вопроса, от которого зависит этот вопрос. В твоем случае:

  • Насколько велика программа? Чем оно меньше, тем более очевидно, что вызывающий абонент поступает правильно. Когда ваша программа становится больше, становится более важным точно указать, каковы предварительные условия и постусловия каждой подпрограммы.
  • аргументы уже проверены argparseмодулем. Часто плохая идея использовать библиотеку, а потом делать свою работу самостоятельно. Зачем тогда использовать библиотеку?
  • Насколько вероятно, что ваш метод будет повторно использован в контексте, когда вызывающая сторона не проверяет аргументы? Чем вероятнее, тем важнее проверить аргументы.
  • Что произойдет , если аргумент действительно пропадает? Если не найти входной файл, это может привести к полной остановке обработки. Это, вероятно, очевидный режим отказа, который легко исправить. Коварные ошибки - это те, в которых программа весело продолжает работать и дает неправильные результаты, не замечая вас .

0

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

Чек слишком много не повредит, слишком мало может.

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


А так как у вас уже есть, это не стоит усилий по удалению, если это не в цикле или что-то.
StarWeaver

0

Возможно, вы могли бы изменить свою точку зрения:

Если что-то пойдет не так, каков будет результат? Будет ли это вредить вашему приложению / пользователю?

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

Из контекста вы даете:

  • один входной файл A
  • один выходной файл B

Я предполагаю , что вы делаете преобразование из A в B . Если A и B малы, а трансформация мала, каковы последствия?

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

2) Вы забыли указать выходной файл. Это приводит к различным сценариям:

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

б) ввод читается шаг за шагом. Затем процесс записи немедленно завершается, как в (1), и пользователь начинает заново.

Небрежная проверка может рассматриваться как нормально при некоторых обстоятельствах. Это полностью зависит от вашего варианта использования и ваших намерений.

Дополнительно: Вы должны избегать паранойи и не делать слишком много двойных проверок.


0

Я бы сказал, что тесты не являются лишними.

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

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

Более надежное решение будет иметь один или два средства проверки имени файла.

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

Я использую два правила для того, когда выполнять действия:

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

0

Проверка избыточна. Однако для исправления этого необходимо удалить readFromInputFile и writeToOutputFile и заменить их readFromStream и writeToStream.

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

Вы могли бы тогда спросить, ну, вам все равно нужно где-то открыть поток. Да, но это происходит внутри метода разбора аргумента. У вас есть две проверки, одна для проверки того, что имя файла требуется, другая для проверки того, что файл, указанный в имени файла, является допустимым файлом в данном контексте (например, существует входной файл, выходной каталог доступен для записи). Это разные типы проверок, поэтому они не являются избыточными и происходят внутри метода анализа аргументов (периметра приложения), а не внутри основного приложения.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.