Я занимаюсь разработкой программного обеспечения в течение последних трех лет, но совсем недавно я осознал, насколько я невежествен в отношении хороших практик. Это заставило меня начать читать книгу « Чистый код» , которая меняет мою жизнь к лучшему, но я изо всех сил пытаюсь понять некоторые из лучших подходов к написанию моих программ.
У меня есть программа на Python, в которой я ...
- используйте argparse
required=True
для применения двух аргументов, которые оба являются именами файлов. первое имя входного файла, второе имя выходного файла - есть функция,
readFromInputFile
которая сначала проверяет, было ли введено имя входного файла - есть функция,
writeToOutputFile
которая сначала проверяет, было ли введено имя выходного файла
Моя программа достаточно мала, поэтому я считаю, что проверка в # 2 и # 3 является избыточной и должна быть удалена, что освобождает обе функции от ненужного if
условия. Тем не менее, я также был убежден, что «двойная проверка в порядке» и может быть правильным решением в программе, где функции могут быть вызваны из другого места, где разбор аргументов не происходит.
(Кроме того, если чтение или запись завершается неудачно, try except
в каждой функции у меня есть соответствующее сообщение об ошибке.)
Мой вопрос: лучше ли избегать избыточной проверки состояния? Должна ли логика программы быть настолько надежной, чтобы проверки проводились только один раз? Есть ли хорошие примеры, иллюстрирующие это или обратное?
РЕДАКТИРОВАТЬ: Спасибо всем за ответы! Я чему-то научился у каждого. Увидев так много точек зрения, я лучше понимаю, как подойти к этой проблеме и найти решение на основе моих требований. Спасибо!
источник
Ответы:
То, что вы просите, называется «робастность», и нет правильного или неправильного ответа. Это зависит от размера и сложности программы, количества людей, работающих в ней, и важности обнаружения сбоев.
В небольших программах, которые вы пишете в одиночку и только для себя, надежность обычно гораздо меньше, чем когда вы собираетесь написать сложную программу, состоящую из нескольких компонентов, возможно, написанных группой. В таких системах существуют границы между компонентами в форме общедоступных API, и на каждой границе часто бывает полезно проверить входные параметры, даже если «логика программы должна быть настолько прочной, что эти проверки являются избыточными ». Это значительно упрощает обнаружение ошибок и помогает сократить время отладки.
В вашем случае вы должны решить для себя, какой жизненный цикл вы ожидаете для своей программы. Это программа, которую вы ожидаете использовать и обслуживать годами? Тогда добавление избыточной проверки, вероятно, будет лучше, поскольку не исключено, что ваш код будет подвергнут рефакторингу в будущем, а ваши функции
read
иwrite
функции могут быть использованы в другом контексте.Или это небольшая программа только для учебы или для развлечения? Тогда эти двойные проверки не будут необходимы.
В контексте «Чистого кода» можно было бы спросить, нарушает ли двойная проверка принцип СУХОГО. На самом деле, иногда это происходит, по крайней мере, в некоторой степени: проверка входных данных может быть интерпретирована как часть бизнес-логики программы, и наличие этого в двух местах может привести к обычным проблемам обслуживания, вызванным нарушением DRY. Устойчивость против DRY часто является компромиссом - устойчивость требует избыточности в коде, в то время как DRY пытается минимизировать избыточность. А с ростом сложности программы надежность становится все более важной, чем СУХОЙ в валидации.
Наконец, позвольте мне привести пример, что это значит в вашем случае. Предположим, что ваши требования изменились на что-то вроде
Значит ли это, что вам нужно изменить двойную проверку в двух местах? Вероятно, нет, такое требование приводит к одному изменению при вызове
argparse
, но никаких изменений вwriteToOutputFile
: эта функция все равно будет требовать имя файла. Так что в вашем случае я бы проголосовал за проверку входных данных дважды, риск возникновения проблем с обслуживанием из-за необходимости изменения двух мест ИМХО намного ниже, чем риск возникновения проблем с обслуживанием из-за замаскированных ошибок, вызванных слишком малым количеством проверок.источник
Избыточность не грех. Излишняя избыточность есть.
Если
readFromInputFile()
иwriteToOutputFile()
являются открытыми функциями (и в соответствии с соглашениями об именах Python они есть, поскольку их имена не начинаются с двух подчеркиваний), то функции могут когда-нибудь использоваться кем-то, кто вообще избегает argparse. Это означает, что когда они пропускают аргументы, они не видят ваше пользовательское сообщение об ошибке argparse.Если
readFromInputFile()
иwriteToOutputFile()
проверить сами параметры, вы снова получите возможность показать пользовательское сообщение об ошибке, объясняющее необходимость имен файлов.Если
readFromInputFile()
иwriteToOutputFile()
не проверять сами параметры, пользовательское сообщение об ошибке не отображается. Пользователь должен будет самостоятельно определить получающееся исключение.Все сводится к 3. Напишите некоторый код, который фактически использует эти функции, избегая argparse, и выдайте сообщение об ошибке. Представьте, что вы вообще не заглядывали в эти функции и просто доверяете их именам, чтобы обеспечить достаточное понимание для использования. Когда это все, что вы знаете, есть ли способ запутаться в исключении? Есть ли необходимость в настраиваемом сообщении об ошибке?
Отключить часть вашего мозга, которая помнит внутренности этих функций, сложно. Настолько, что некоторые рекомендуют писать код использования перед кодом, который будет использоваться. Таким образом, вы приходите к проблеме, уже зная, как все выглядит со стороны. Вам не нужно делать TDD, но если вы делаете TDD, вы уже придете снаружи.
источник
Степень, в которой вы делаете свои методы автономными и многократно используемыми, - это хорошо. Это означает, что методы должны прощать то, что они принимают, и они должны иметь четко определенные результаты (точные в том, что они возвращают). Это также означает, что они должны иметь возможность корректно обрабатывать все, что им передается, и не делать никаких предположений о характере входных данных, качестве, сроках и т. Д.
Если программист имеет привычку писать методы, которые делают предположения о том, что было передано, основываясь на таких идеях, как «если это сломано, нам есть о чем беспокоиться» или «параметр X не может иметь значение Y, потому что остальные код предотвращает это ", и вдруг у вас больше нет независимых, разъединенных компонентов. Ваши компоненты существенно зависят от более широкой системы. Это своего рода тонкая тесная связь и приводит к экспоненциальному увеличению общей стоимости владения по мере увеличения сложности системы.
Обратите внимание, что это может означать, что вы проверяете одну и ту же информацию более одного раза. Но это нормально. Каждый компонент отвечает за свою собственную валидацию по-своему . Это не является нарушением DRY, потому что проверки выполняются независимыми независимыми компонентами, и изменение проверки в одном не обязательно должно точно повторяться в другом. Здесь нет избыточности. X несет ответственность за проверку своих входных данных для собственных нужд и передачу их Y. Y несет собственную ответственность за проверку своих собственных входных данных для своих нужд .
источник
Предположим, у вас есть функция (в C)
И вы не можете найти документацию о пути. А потом вы смотрите на реализацию, и это говорит
Это не только проверяет входные данные для функции, но также сообщает пользователю функции, что путь не может быть NULL или пустой строкой.
источник
В общем, двойная проверка не всегда хорошо или плохо. В вашем конкретном случае всегда много аспектов вопроса, от которого зависит этот вопрос. В твоем случае:
argparse
модулем. Часто плохая идея использовать библиотеку, а потом делать свою работу самостоятельно. Зачем тогда использовать библиотеку?источник
Ваши двойные проверки, кажется, в местах, где они используются редко. Таким образом, эти проверки просто делают вашу программу более надежной:
Чек слишком много не повредит, слишком мало может.
Однако, если вы выполняете проверку внутри цикла, который часто повторяется, вам следует подумать об удалении избыточности, даже если сама проверка в большинстве случаев обходится не дорого по сравнению с тем, что следует после проверки.
источник
Возможно, вы могли бы изменить свою точку зрения:
Если что-то пойдет не так, каков будет результат? Будет ли это вредить вашему приложению / пользователю?
Конечно, вы всегда можете поспорить, являются ли более или менее чеки лучше или хуже, но это довольно схоластический вопрос. А так как вы имеете дело с программным обеспечением реального мира , это имеет реальные последствия.
Из контекста вы даете:
Я предполагаю , что вы делаете преобразование из A в B . Если A и B малы, а трансформация мала, каковы последствия?
1) Вы забыли указать, где читать: тогда результат ничего не значит . И время выполнения будет короче, чем ожидалось. Вы смотрите на результат - или лучше: ищите пропущенный результат, видите, что вы вызвали команду неправильно, начните все сначала и все снова хорошо
2) Вы забыли указать выходной файл. Это приводит к различным сценариям:
а) Ввод читается сразу. Затем начинается преобразование, и результат должен быть записан, но вместо этого вы получаете ошибку. В зависимости от времени, ваш пользователь должен ждать (в зависимости от массы данных, которые должны быть обработаны), это может раздражать.
б) ввод читается шаг за шагом. Затем процесс записи немедленно завершается, как в (1), и пользователь начинает заново.
Небрежная проверка может рассматриваться как нормально при некоторых обстоятельствах. Это полностью зависит от вашего варианта использования и ваших намерений.
Дополнительно: Вы должны избегать паранойи и не делать слишком много двойных проверок.
источник
Я бы сказал, что тесты не являются лишними.
Пока имена файлов проверяются дважды, они проверяются для разных целей. В небольшой программе, в которой вы можете доверять параметрам, которые были проверены для функций, проверки в функциях могут считаться избыточными.
Более надежное решение будет иметь один или два средства проверки имени файла.
Я использую два правила для того, когда выполнять действия:
источник
Проверка избыточна. Однако для исправления этого необходимо удалить readFromInputFile и writeToOutputFile и заменить их readFromStream и writeToStream.
В тот момент, когда код получает поток файлов, вы знаете, что у вас есть действительный поток, подключенный к действительному файлу, или любой другой поток, к которому можно подключиться. Это позволяет избежать лишних проверок.
Вы могли бы тогда спросить, ну, вам все равно нужно где-то открыть поток. Да, но это происходит внутри метода разбора аргумента. У вас есть две проверки, одна для проверки того, что имя файла требуется, другая для проверки того, что файл, указанный в имени файла, является допустимым файлом в данном контексте (например, существует входной файл, выходной каталог доступен для записи). Это разные типы проверок, поэтому они не являются избыточными и происходят внутри метода анализа аргументов (периметра приложения), а не внутри основного приложения.
источник