Подтвердить что ты не робот

Должен ли я использовать нулевые параметры в частных/внутренних методах?

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

В публичных методах у меня есть нулевая проверка и такой бросок:

public int DoSomething(int number)
{
    if (number == null)
    {
        throw new ArgumentNullException(nameof(number));
    }
}

Но потом это заставило меня задуматься, на каком уровне я должен добавлять параметр null для проверки методов? Я также начинаю добавлять их к приватным методам? Должен ли я делать это только для общедоступных методов?

4b9b3361

Ответ 1

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

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

  • Нулевые чеки выражают предварительное условие. Если метод будет терпеть неудачу, когда одно из значений равно null, то с нулевой проверкой наверху это хороший способ продемонстрировать это случайному читателю без необходимости поиска там, где он был разыменован. Чтобы улучшить это, люди часто используют вспомогательные методы с именами типа Guard.AgainstNull, вместо того, чтобы каждый раз писать чек.

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

  • Тяжесть пропускания нулевого значения зависит от ситуации. Часто, если нуль попадает в метод, он будет разыменован несколькими строками позже, и вы получите NullReferenceException. Это действительно не намного менее понятно, чем бросать ArgumentNullException. С другой стороны, если эта ссылка передается примерно до того, как ее разыграют, или если выброс NRE оставит вещи в беспорядочном состоянии, то бросать ранние гораздо важнее.

  • Некоторые библиотеки, такие как .NET Code Contracts, позволяют получить степень статического анализа, что может добавить дополнительную выгоду для ваших проверок.

  • Если вы работаете над проектом с другими, могут существовать существующие стандарты команд или проектов, охватывающие это.

Ответ 2

Вот мои мнения:


Общие случаи

Вообще говоря, лучше проверить наличие каких-либо недопустимых входов, прежде чем обрабатывать их в методе для обоснования надежности - будь то методы private, protected, internal, protected internal, or public. Хотя для этого подхода есть некоторые затраты на производительность, в большинстве случаев это стоит делать, а не платить больше времени для отладки и исправления кодов позже.


Строго говоря, однако...

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

Общие методы

Теперь метод public более сложный. Это связано с тем, что, строго говоря, хотя только один модификатор доступа может определить, кто может использовать методы, он не может определить, кто будет использовать методы. Более того, он также не может определить, как будут использоваться методы (то есть, будут ли методы вызываться с недопустимыми входами в заданных областях или нет).


Конечный определяющий фактор

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

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

Здесь мы видим, что нет гарантии, что метод public всегда требует проверки на недопустимые входы. И если это верно для методов public, оно также должно быть верно и для методов protected, internal, protected internal, and private.


Выводы

Итак, в заключение, мы можем сказать пару вещей, которые помогут нам принимать решения:

  • Как правило, лучше иметь чеки для любых недопустимых входов для надежной причины проектирования при условии, что производительность не поставлена ​​на карту. Это верно для любых типов модификаторов доступа.
  • Проверка недействительных входов может быть пропущена, если при этом может быть значительно улучшено усиление производительности, при условии, что также может быть гарантировано, что область, в которой вызывается метод, всегда дает корректные входы методов. Обычно метод
  • private пропускает такую ​​проверку, но нет гарантии, что мы не сможем сделать это для метода public, а также
  • Люди, которые в конечном счете используют методы. Независимо от того, как модификаторы доступа могут намекать на использование методов, как методы фактически используются и называются, зависят от кодеров. Таким образом, мы можем говорить только об общей/хорошей практике, не ограничивая ее единственным способом сделать это.

Ответ 3

Если вы не разработчик библиотеки, не защищайтесь в своем коде

Вместо этого напишите единичные тесты

На самом деле, даже если вы разрабатываете библиотеку, бросать большую часть времени: BAD

1. Тестирование null на int никогда не должно выполняться в С#:

Появляется предупреждение CS4072, потому что оно всегда ложно.

2. Бросок исключения означает, что он исключительный: ненормальный и редкий.

Он никогда не должен поднимать производственный код. Тем более, что трассировка трассировки стека исключений может быть интенсивной задачей процессора. И вы никогда не будете уверены, где исключение будет поймано, если оно поймано и занесено в журнал или просто просто игнорируется (после убийства одного из ваших фоновых потоков), потому что вы не контролируете код пользователя. В С# (например, в java) нет "проверенного исключения" , что означает, что вы никогда не знаете - если он не задокументирован - какие исключения может дать данный метод. Кстати, такая документация должна храниться в синхронизации с кодом, который не всегда легко сделать (увеличить затраты на обслуживание).

3. Исключения увеличивают затраты на обслуживание.

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

4. Выбрасывание недопустимого ввода означает, что вы не контролируете ввод.

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

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

  • Какова будет его реакция?
  • Как он справится с этим?
  • Вам будет легко дать объяснение?

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

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

Создание общедоступного метода не означает, что он должен (только он может) быть вызван из-за пределов вашей библиотеки (Посмотрите на публикацию или публикацию из Мартин Фаулер). Используйте IOC, интерфейсы, фабрики и публикуйте только то, что нужно пользователю, делая все классы библиотеки доступными для модульного тестирования. (Или вы можете использовать механизм InternalsVisibleTo).

6. Бросание исключений без поясняющего сообщения высмеивает пользователя

Не нужно напоминать, какие чувства можно испытывать, когда инструмент сломан, не имея понятия о том, как его исправить. Да, я знаю. Вы попадаете в SO и задаете вопрос...

7. Недопустимый ввод означает, что он разбивает ваш код

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

8. Подумайте в терминах пользователя:

Вам нравится, когда библиотека, которую вы используете, исключает исключения для разбивания вашего лица? Например: "Эй, это неправда, вы должны были это знать!"

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

  • Яркая документация (в документе Xml и сводке архитектуры может помочь).
  • Опубликуйте XML-документ с библиотекой.
  • Очистить объяснение ошибки в исключении, если оно есть.
  • Дайте выбор:

Посмотрите на класс словаря, что вы предпочитаете? какой звонок, по вашему мнению, самый быстрый? Какой вызов может вызвать исключение?

        Dictionary<string, string> dictionary = new Dictionary<string, string>();
        string res;
        dictionary.TryGetValue("key", out res);

или

        var other = dictionary["key"];

9. Почему бы не использовать Контракты кода?

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

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

Ответ 4

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

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

Ответ 5

Хотя вы отметили language-agnostic, мне кажется, что он, вероятно, не существует общего ответа.

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

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

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

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

Надеюсь, что это поможет.

Ответ 6

По моему мнению, вы должны ВСЕГДА проверять наличие "недействительных" данных - независимо от того, является ли это частным или общедоступным методом.

Посмотрел с другой стороны... почему вы должны работать с чем-то недействительным только потому, что этот метод является приватным? Не имеет смысла, не так ли? Всегда старайтесь использовать защитное программирование, и вы будете счастливее в жизни; -)

Ответ 7

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

Предположим, что мы реализовали класс PersonList в библиотеке. Этот список может содержать только объекты типа Person. Мы также на нашем PersonList реализовали некоторые операции, и поэтому мы не хотим, чтобы он содержал нулевые значения.

Рассмотрим следующие две реализации метода Add для этого списка:

Реализация 1

public void Add(Person item)
{
    if(_size == _items.Length)
    {
        EnsureCapacity(_size + 1);
    }

    _items[_size++] = item;
}

Реализация 2

public void Add(Person item)
{
    if(item == null)
    {
        throw new ArgumentNullException("Cannot add null to PersonList");
    }

    if(_size == _items.Length)
    {
        EnsureCapacity(_size + 1);
    }

    _items[_size++] = item;
}

Скажем, мы идем с реализацией 1

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

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

Также для потребителя станет понятным, что он/она использует библиотеку неправильно, когда он/она получает ArgumentNullException на .Add вместо .Sort или similair.

Подводя итог, я предпочитаю проверять действительный аргумент, когда он предоставляется потребителем, и он не обрабатывается частными/внутренними методами библиотеки. Это в основном означает, что мы должны проверять аргументы в конструкторах/методах, которые являются общедоступными и принимают параметры. Наши методы private/internal могут быть вызваны только из наших публичных, и они уже проверили вход, что означает, что мы хороши!

Использование Кодовые контракты также следует учитывать при проверке ввода.