Вопрос стиля программирования о том, как кодировать функции - PullRequest
8 голосов
/ 19 апреля 2010

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

У меня есть функция, которая перечисляет хосты в зависимости от среды, и я хочу иметь возможность разделить среду на куски хостов. Вот пример использования:

listhosts -e testenv -s 2 1

Это позволит получить все хосты из «testenv», разделить его на две части и отобразить первую часть.

В моем коде у меня есть функция, которая передает его в список, и он возвращает список списков на основе ваших параметров для разделения. НО, прежде чем передать его список, я сначала проверяю параметры в своем MAIN во время процесса getops, поэтому в основном я проверяю, чтобы убедиться, что пользователь не передал никаких негативов, я удостоверяюсь, что пользователь не запрашивал разбиение на скажем, 4 части, но с просьбой отобразить часть 5 (которая будет недействительной) и т. д.

tl; dr: вы проверите правильность ввода пользователем потока вашего класса MAIN, или вы сами проверите проверку в своей функции, и либо вернете верный ответ в случае правильного ввода, либо вернуть NULL в случае неверного ввода?

Очевидно, что оба метода работают, мне просто интересно услышать от экспертов, какой подход лучше :) Спасибо за любые комментарии и предложения, которые у вас есть, ребята! К вашему сведению, мой пример написан на Python, но я все еще больше интересуюсь общим ответом на программирование, а не конкретным языком!

Ответы [ 6 ]

5 голосов
/ 19 апреля 2010

Хороший вопрос! Мой главный совет - подходить к проблеме систематически. Если вы разрабатываете функцию f, вот как я думаю о ее спецификации:

  1. Каким абсолютным требованиям должен соответствовать абонент f? Этими условиями являются f * предварительное условие .

  2. Что f делает для своего абонента? Когда возвращается f, каково возвращаемое значение и каково состояние машины? При каких обстоятельствах f создает исключение и какое исключение выдается? Ответы на все эти вопросы составляют f постусловие .

    Предварительное условие и постусловие вместе составляют f контракт с вызывающими абонентами. Только вызывающий абонент, удовлетворяющий предварительному условию, может положиться на постусловие.

  3. И, наконец, непосредственно касаясь вашего вопроса, что произойдет, если вызывающий f не выполнит предварительное условие? У вас есть два варианта:

    • Вы гарантируете остановку программы, надеетесь с информативным сообщением. Это проверенная ошибка времени выполнения.

    • Все идет. Может быть, есть ошибка, может быть, повреждена память, возможно f молча возвращает неправильный ответ. Это не проверено ошибка времени выполнения.

    Обратите внимание на некоторые элементы, которых нет в этом списке: вызов исключения или возврат кода ошибки. Если на это поведение можно положиться, оно становится частью f контракта.

Теперь я могу перефразировать ваш вопрос:

Что должна делать функция, если вызывающая сторона нарушает свой контракт?

В большинстве видов приложений функция должна останавливать программу с проверено ошибка во время выполнения. Если программа является частью приложения, которое должно быть надежным, либо приложение должно предоставить внешний механизм для перезапуска приложения, которое останавливается с проверенной ошибкой во время выполнения (обычно в коде Erlang), или, если перезапуск затруднен, все функции «Контракты должны быть очень разрешительными, чтобы« плохой вклад »по-прежнему соответствовал контракту, но обещал всегда выдвигать исключение.

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

Еще один аспект вашего вопроса

Какие контракты делают лучшие проекты?

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

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

Я думаю, что это именно правильный способ решения этой конкретной проблемы:

  1. Ваш контракт с пользователем заключается в том, что пользователь может сказать что-нибудь, и если пользователь отправит бессмысленный запрос, ваша программа не упадет & mdash; он выдаст разумное сообщение об ошибке, а затем продолжит.

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

  3. Таким образом, у вас есть третья функция, помимо второй, задача которой состоит в том, чтобы отличать смысл от бессмыслицы и действовать соответствующим образом - ваша функция обработки запросов получает «смысл», пользователю сообщается о «бессмыслице», и все контракты выполнены.

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

Да. Почти всегда это лучший дизайн. На самом деле, где-то есть шаблон дизайна с причудливым названием. Но если нет, опытные программисты видели это снова и снова. Происходит одно из двух:

  • синтаксический анализ / подтверждение / отклонение с сообщением об ошибке

  • синтаксический анализ / проверка / обработка

Этот вид дизайна имеет один тип данных (запрос) и четыре функции. Поскольку на этой неделе я пишу тонны кода на Haskell, я приведу пример на Haskell:

data Request -- type of a request
parse    :: UserInput -> Request            -- has a somewhat permissive precondition
validate :: Request -> Maybe ErrorMessage   -- has a very permissive precondition
process  :: Request -> Result               -- has a very restrictive precondition

Конечно, есть много других способов сделать это. Сбои могут быть обнаружены как на этапе анализа, так и на этапе проверки. «Действительный запрос» может фактически быть представлен другим типом, чем «неподтвержденный запрос». И так далее.

5 голосов
/ 19 апреля 2010

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

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

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

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

2 голосов
/ 19 апреля 2010

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

2 голосов
/ 19 апреля 2010

Часто имеет смысл проверять ввод в обоих местах.

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

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

2 голосов
/ 19 апреля 2010

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

1 голос
/ 19 апреля 2010

способ обработки ошибок зависит от языка программирования; однако при написании приложения командной строки командная строка действительно должна проверять, что ввод является разумным. Если ввод не является разумным, соответствующее поведение - напечатать сообщение «Использование» с объяснением требований, а также выйти с ненулевым кодом состояния, чтобы другие программы знали, что это не удалось (путем тестирования кода выхода) .

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

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

...