Должны ли функции возвращать ноль или пустой объект?


209

Какова лучшая практика при возврате данных из функций. Лучше вернуть нулевой или пустой объект? И почему один должен делать один над другим?

Учти это:

public UserEntity GetUserById(Guid userId)
{
     //Imagine some code here to access database.....

     //Check if data was returned and return a null if none found
     if (!DataExists)
        return null; 
        //Should I be doing this here instead? 
        //return new UserEntity();  
     else
        return existingUserEntity;
}

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


4
Я думаю, что вы имеете в виду if (!DataExists).
Сара Весселс

107
Это архитектурный вопрос и он совершенно уместен. Вопрос ОП действителен независимо от бизнес-проблемы, которую он пытается решить.
Джозеф Феррис

2
На этот вопрос уже достаточно ответили. Я думаю, что это очень интересный вопрос.
Джон Сципион

getUser () должен возвращать ноль. 'getCurrentUserInfo ()' или 'getCurrentPermissions ()', OTOH, были бы более показательными вопросами - они должны возвращать объект с ненулевым ответом независимо от того, кто / или кто-то вошел в систему.
Томас В.

2
Нет @ Берги, другой дубликат. Мой был спрошен сначала, в октябре, другой был задан 3 месяца спустя в декабре. Плюс другой говорит о коллекции, которая немного отличается.
7

Ответы:


207

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

Пустой объект подразумевает, что данные были возвращены, тогда как возвращение нуля ясно указывает на то, что ничего не было возвращено.

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


21
Вы должны выдать исключение, не проглотить проблему и вернуть null. Как минимум, вы должны войти в систему, а затем продолжить.
Крис Балланс

130
@ Крис: я не согласен. Если код четко документирует, что возвращаемое значение равно нулю, вполне допустимо возвращать пустое значение, если не найдено результатов, соответствующих вашим критериям. Бросок исключения должен быть вашим последним выбором, а не первым.
Майк Хофер

12
@ Крис: На каком основании вы решаете это? Добавление записи в уравнение, конечно, кажется чрезмерным. Пусть потребительский код решает, что - если что - то должно быть сделано в случае отсутствия пользователя. Как и в моем предыдущем комментарии, нет абсолютно никаких проблем с возвратом значения, которое универсально определено как «нет данных».
Адам Робинсон

17
Я немного сбит с толку, что разработчик Microsoft считает, что «возвращение нуля» означает «проглатывание проблемы». Если память служит, в Framework есть множество методов, в которых возвращаются методы null, если нет ничего, соответствующего запросу вызывающей стороны. Это "глотает проблему?"
Майк Хофер

5
И последнее, но не менее важное: « bool GetUserById(Guid userId, out UserEntity result)Я бы предпочел нулевое» возвращаемое значение, и это не так экстремально, как создание исключения. Это позволяет красивый, как nullбесплатный код if(GetUserById(x,u)) { ... }.
Марсель Джекверт

44

Это зависит от того, что имеет смысл для вашего случая.

Имеет ли смысл возвращать ноль, например, «такого пользователя нет»?

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

Или имеет смысл генерировать исключение (в виде «FileNotFound»), если вызывающий код требует пользователя с неверным идентификатором?

Однако - с точки зрения разделения интересов / SRP, первые два являются более правильными. И технически первое является наиболее правильным (но только на волосок) - GetUserById должен отвечать только за одну вещь - за получение пользователя. Обработка собственного случая «пользователь не существует» путем возврата чего-либо еще может быть нарушением SRP. Разделение на другую проверку - bool DoesUserExist(id)было бы целесообразно, если вы решите создать исключение.

Основываясь на подробных комментариях ниже : если это вопрос разработки уровня API, этот метод может быть аналогом «OpenFile» или «ReadEntireFile». Мы «открываем» пользователя из какого-либо хранилища и гидратируем объект из полученных данных. Исключение может быть уместным в этом случае. Это может быть не так, но это может быть.

Все подходы являются приемлемыми - это просто зависит от более широкого контекста API / приложения.


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

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

1
Договорились до последнего пункта. Там нет нарушения SRP, возвращая значение, которое универсально определяется как «нет данных». Это все равно что указывать, что база данных SQL должна возвращать ошибку, если предложение where не дает результатов. В то время как Исключение является допустимым выбором дизайна (хотя оно может раздражать меня как потребителя), оно не является «более правильным», чем возвращение нулевого значения. И нет, я не DV.
Адам Робинсон

@JacobM мы генерируем исключения, когда запрашиваем путь к файловой системе, который не существует, не возвращает ноль, но не из баз данных. Ясно, что оба уместны, к чему я и стремлюсь - это просто зависит.
Рекс М

2
@Charles: Вы отвечаете на вопрос «должно ли быть исключение в какой-то момент», но вопрос в том, «должна ли эта функция генерировать исключение». Правильный ответ - «возможно», а не «да».
Адам Робинсон

30

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


Просто собираюсь добавить это как ответ сам. NullObjectPattern или шаблон особого случая. Тогда вы могли бы реализовать один для каждого случая, NoUserEntitiesFound, NullUserEntities и т. Д.
Дэвид Суинделлс

27

Если ваш тип возвращаемого значения - массив, тогда верните пустой массив, иначе верните ноль.


Является ли 0 элементов в списке таким же, как список неназначенный в это время?
AnthonyWJones

3
0 элементов в списке не совпадает с null. Это позволяет использовать его в foreachоператорах и запросах linq, не беспокоясь о NullReferenceException.
Дарин Димитров

5
Я удивлен, что за это больше не голосовали. Это кажется довольно разумным руководством для меня.
Шаши

Ну, пустой контейнер - это просто конкретный экземпляр шаблона нулевого объекта. Что может быть уместным, мы не можем сказать.
Дедупликатор

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

12

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

Для строк, массивов и коллекций ситуация обычно отличается. Я помню некоторые инструкции из MS, которые методы должны принимать nullкак «пустой» список, но возвращать коллекции нулевой длины, а не null. То же самое для строк. Обратите внимание, что вы можете объявить пустые массивы:int[] arr = new int[0];


Рад, что вы упомянули, что строки разные, так как Google показал мне это, когда я решал, возвращать ли пустую строку.
Нумен

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

1
Кажется, вы упускаете смысл и контекст. .Wher(p => p.Lastname == "qwerty")должен вернуть пустую коллекцию, а не null.
Хенк Холтерман

@HenkHolterman Если вы можете получить доступ ко всей коллекции и применить фильтр, который не принимает элементы в коллекции, пустая коллекция является правильным результатом. Но если полная коллекция не существует, пустая коллекция вводит в заблуждение - ноль или бросание будет правильным в зависимости от того, является ли ситуация нормальной или исключительной. Поскольку ваше сообщение не соответствовало ситуации, о которой вы говорили (и теперь вы поясняете, что говорите о первой ситуации), и поскольку ФП говорил о последней ситуации, я вынужден с вами не согласиться.
Восстановите Монику

11

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

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

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

РЕДАКТИРОВАТЬ: (чтобы обратиться к комментарию от Адама в другом ответе)

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

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


-1 для downvoter, +1 для Charles - это полностью деловой вопрос, и для этого не существует наилучшей практики.
Остин Салонен

Это пересекает потоки. Является ли это «условием ошибки», определяется бизнес-логикой. Как справиться с этим - решение архитектуры приложения. Бизнес-логика не будет диктовать возвращение нулевого значения, просто требования будут выполнены. Если бизнес решает типы возвращаемых методов, то они слишком вовлечены в технический аспект реализации.
Джозеф Феррис

@joseph, основной принцип «структурированной обработки исключений» заключается в том, что исключения должны создаваться, когда методы не могут завершить какую-либо функцию, которую они были написаны для реализации. Вы правы в том, что если бизнес-функция, которую этот метод был написан для реализации, может быть «успешно завершена» (что бы это ни значило в модели предметной области), тогда вам не нужно выдавать исключение, вы можете вернуть нуль или логическая переменная «FoundUser», или что-то еще ... То, как вы сообщаете вызывающему методу, что пользователь не был найден, становится технической деталью реализации.
Чарльз Бретана

10

Лично я бы возвратил ноль, потому что именно так я ожидаю, что слой DAL / Repository будет действовать.

Если он не существует, не возвращайте ничего, что может быть истолковано как успешное извлечение объекта, nullздесь прекрасно работает.

Самое главное - быть последовательным на уровне DAL / Repos, чтобы не запутаться в том, как его использовать.


7

Я склонен

  • return nullесли идентификатор объекта не существует, когда заранее неизвестно, должен ли он существовать.
  • throwесли идентификатор объекта не существует, когда он должен существовать.

Я различаю эти два сценария с этими тремя типами методов. Первый:

Boolean TryGetSomeObjectById(Int32 id, out SomeObject o)
{
    if (InternalIdExists(id))
    {
        o = InternalGetSomeObject(id);

        return true;
    }
    else
    {
        return false;
    }
}

Во-вторых:

SomeObject FindSomeObjectById(Int32 id)
{
    SomeObject o;

    return TryGetObjectById(id, out o) ? o : null;
}

Третий:

SomeObject GetSomeObjectById(Int32 id)
{
    SomeObject o;

    if (!TryGetObjectById(id, out o))
    {
        throw new SomeAppropriateException();
    }

    return o;
}

Вы имеете в виду outнетref
Мэтт Эллен

@Matt: Да, сэр, я, безусловно, делаю! Исправлена.
Иоганн Герелл

2
На самом деле, это единственный ответ, который подходит всем и, следовательно, является единственно верной истиной! :) Да, это зависит от предположений, на основании которых вызывается метод ... Итак, сначала уточните эти предположения, а затем выберите правильную комбинацию из вышеперечисленного. Пришлось прокручивать слишком много вниз, чтобы попасть сюда :) +100
yair

Похоже, это шаблон для использования, за исключением того, что он не поддерживает асинхронные методы. Я сослался на этот ответ и добавил асинхронное решение с литералами Tuple -> здесь
ttugates

6

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

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
}

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

public void GetUserById(Guid id, UserCallback callback, NotFoundCallback notFound)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
    else
        notFound(); // or notFound.Call();
}

Тот же подход с использованием одного объекта может выглядеть следующим образом:

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback.Found(userEntity);
    else
        callback.NotFound();
}

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


Интересный. Когда вы начали говорить о делегатах, я сразу начал задаваться вопросом, можно ли здесь использовать лямбда-выражения.
7

Ага! Насколько я понимаю, лямбда-синтаксис C # 3.0 и выше в основном является синтаксическим сахаром для анонимных делегатов. Точно так же в Java, без хорошего лямбда-выражения или синтаксиса анонимного делегата, вы можете просто создать анонимный класс. Это немного страшнее, но может быть очень удобно. Полагаю, в наши дни мой пример C # мог бы использовать Func <UserEntity> или что-то подобное вместо именованного делегата, но последний проект C #, в котором я работал, все еще использовал версию 2.
Marc

+1 Мне нравится такой подход. Проблема, однако, заключается в том, что это не является традиционным и немного увеличивает барьер для входа для кодовой базы.
Тимоксли

4

Мы используем CSLA.NET и придерживаемся мнения, что неудачная выборка данных должна возвращать «пустой» объект. Это на самом деле довольно раздражает, так как требует соглашения о проверке, obj.IsNewа не obj == null.

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

Лично я думаю, что nullэто более элегантно.

Это очень распространенный случай, и я удивлен, что люди здесь удивляются этому: в любом веб-приложении данные часто выбираются с помощью параметра querystring, который, очевидно, может быть искажен, поэтому требуется, чтобы разработчик обрабатывал случаи «not found». ».

Вы можете справиться с этим путем:

if (User.Exists (id)) {
  this.User = User.Fetch (id);
} еще {
  Response.Redirect ( "~ / notfound.aspx");
}

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

this.User = User.Fetch (id);

if (this.User == null) {
  Response.Redirect ( "~ / notfound.aspx");
}

... требуется только один звонок.


4

Я предпочитаю null, так как он совместим с оператором null-coalescing ( ??).


4

Я бы сказал, вернуть пустой вместо пустого объекта.

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

Это правило, которому я обычно следую:

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

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

3

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

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


+1 Я подвергал сомнению ту же логику, которую вы говорите здесь, поэтому я опубликовал вопрос, чтобы увидеть, что другие мнения об этом будут
7wp

3

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

Редактировать:

Обычно такие методы являются членами некоторого класса «Пользователь» и не имеют доступа к его членам экземпляра. В этом случае метод должен быть статическим, в противном случае необходимо создать экземпляр «User», а затем вызвать метод GetUserById, который будет возвращать другой экземпляр «User». Согласитесь, это сбивает с толку. Но если метод GetUserById является членом некоторого класса «DatabaseFactory» - не проблема оставить его в качестве члена экземпляра.


Могу я спросить, почему я хотел бы сделать мой метод статичным? Что если я хочу использовать Dependency Injection?
7

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

3

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

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


+1 Мне нравится альтернативный взгляд на проблему. Таким образом, в основном вы говорите, какой подход я выберу, должно быть в порядке, если метод согласован во всем приложении?
7

1
Это моя вера. Я думаю, что последовательность чрезвычайно важна. Если вы делаете вещи несколькими способами в разных местах, это повышает риск появления новых ошибок. Мы лично выбрали объектный подход по умолчанию, потому что он хорошо работает с шаблоном Essence, который мы используем в нашей предметной модели. У нас есть один универсальный метод расширения, который мы можем проверить на всех объектах домена, чтобы сообщить нам, заполнен он или нет, так что мы знаем, что любой DO может быть протестирован с вызовом objectname.IsDefault () - избегая любых проверок на равенство напрямую ,
Джозеф Феррис

3

В наших Business Objects у нас есть 2 основных метода Get:

Для простоты в контексте, или вы задаете вопрос, они будут:

// Returns null if user does not exist
public UserEntity GetUserById(Guid userId)
{
}

// Returns a New User if user does not exist
public UserEntity GetNewOrExistingUserById(Guid userId)
{
}

Первый метод используется при получении конкретных объектов, второй метод используется специально при добавлении или редактировании объектов на веб-страницах.

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


3

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

Используя Java, нас просят добавить assert exists(object) : "You shouldn't try to access an object that doesn't exist"; в начале любого метода, который мог бы возвратить нуль, чтобы выразить «предварительное условие» (я не знаю, что это за слово по-английски).

ИМО, это действительно не просто использовать, но это то, что я использую, ожидая чего-то лучшего.


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

1
Одним из преимуществ является то, что проверка на существование является достаточно абстрактной: if (userExists) немного более читабелен, ближе к проблемной области и менее «компьютерный», чем: if (user == null)
timoxley

И я бы сказал, что 'if (x == null)' - это шаблон десятилетий, который, если вы не видели его раньше, вы не писали код очень долго (и вы должны привыкнуть к нему, как он есть в миллионы строк кода). "Computery"? Мы говорим о доступе к базе данных ...
Ллойд Сарджент

3

Если случай, когда пользователь не найден, встречается достаточно часто, и вы хотите по-разному с этим справляться в зависимости от обстоятельств (иногда выбрасывая исключение, иногда подставляя пустого пользователя), вы также можете использовать что-то близкое к типу F # Optionили Haskell. Maybe, который явно отделяет регистр «без значения» от «нашел что-то!». Код доступа к базе данных может выглядеть так:

public Option<UserEntity> GetUserById(Guid userId)
{
 //Imagine some code here to access database.....

 //Check if data was returned and return a null if none found
 if (!DataExists)
    return Option<UserEntity>.Nothing; 
 else
    return Option.Just(existingUserEntity);
}

И использовать так:

Option<UserEntity> result = GetUserById(...);
if (result.IsNothing()) {
    // deal with it
} else {
    UserEntity value = result.GetValue();
}

К сожалению, каждый, кажется, бросает подобный себе тип.


2

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


2

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

Используя шаблон NullObject, это будет:

public UserEntity GetUserById(Guid userId)

{// Представьте себе некоторый код для доступа к базе данных .....

 //Check if data was returned and return a null if none found
 if (!DataExists)
    return new NullUserEntity(); //Should I be doing this here instead? return new UserEntity();  
 else
    return existingUserEntity;

}

class NullUserEntity: IUserEntity { public string getFirstName(){ return ""; } ...} 

2

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

Исключения для исключительных обстоятельств

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

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

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

Теперь я скажу, что это общее правило. Есть моменты, когда вы можете сломать это. Тем не менее, мой опыт и эксперименты с C # (много чего) и Java (кое-что) научили меня, что гораздо эффективнее работать с исключениями, чем обрабатывать предсказуемые проблемы с помощью условной логики. Я говорю о мелодии на 2 или 3 порядка дороже в некоторых случаях. Итак, если возможно, что ваш код может оказаться в цикле, я бы посоветовал вернуть null и протестировать его.


2

Прости мой псевдо-php / код.

Я думаю, что это действительно зависит от предполагаемого использования результата.

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

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

Пример:

function saveTheRow($prim_key, $data) {
    $row = getRowByPrimKey($prim_key);

    // Populate the data here

    $row->save();
}

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

Однако, если конечная цель возвращаемого значения - прочитать и что-то сделать с данными, то я бы возвратил ноль. Таким образом, я могу очень быстро определить, не были ли возвращены данные, и отобразить соответствующее сообщение для пользователя.

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

Пример:

function displayData($row_id) {
    // Logging of the error would happen in this function
    $row = getRow($row_id);
    if($row === null) {
        // Handle the error here
    }

    // Do stuff here with data
}

function getRow($row_id) {
 $row = null;
 try{
     if(!$db->connected()) {
   throw excpetion("Couldn't Connect");
  }

  $result = $db->query($some_query_using_row_id);

  if(count($result) == 0 ) {
   throw new exception("Couldn't find a record!");
  }

  $row = $db->nextRow();

 } catch (db_exception) {
  //Log db conn error, alert admin, etc...
  return null; // This way I know that null means an error occurred
 }
 return $row;
}

Это мое общее правило. Пока это хорошо работает.


2

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

Возможно, хороший подход, чтобы отличить методы броска от методов с нулевым возвратом, состоит в том, чтобы найти соглашение в вашей команде: методы, которые говорят, что они «получают» что-то, должны генерировать исключение, если нечего получить. Методы, которые могут возвращать null, могут быть названы по-другому, возможно, вместо этого «Find ...».


+1 Мне нравится идея использовать единообразное соглашение об именах для сигнализации программисту о том, как должна использоваться эта функция.
7

1
Внезапно я понимаю, что именно это делает LINQ: рассмотрим First (...) против FirstOrDefault (...)
Марк Витке

2

Если возвращаемый объект является чем-то, что может быть повторено, я бы возвратил пустой объект, так что мне не нужно сначала проверять на ноль.

Пример:

bool IsAdministrator(User user)
{
    var groupsOfUser = GetGroupsOfUser(user);

    // This foreach would cause a run time exception if groupsOfUser is null.
    foreach (var groupOfUser in groupsOfUser) 
    {
        if (groupOfUser.Name == "Administrators")
        {
            return true;
        }
    }

    return false;
}

2

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

Кроме того, такие методы, которые не могут вернуть результат, должны указывать это через свое имя. Обычно я помещаю Try или TryGet или TryFind в начало имени метода, чтобы указать, что он может вернуть пустой результат (например, TryFindCustomer, TryLoadFile и т. Д.).

Это позволяет вызывающему применять различные методы, как сбор конвейерные (см Мартина Фаулера Коллекция Pipeline ) на результате.

Вот еще один пример, где возвращаемый Option вместо null используется для уменьшения сложности кода: Как уменьшить цикломатическую сложность: функциональный тип Option


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

1

Больше мяса для измельчения: допустим, мой DAL возвращает NULL для GetPersonByID, как советуют некоторые. Что должен делать мой (довольно тонкий) BLL, если он получает NULL? Передать этот NULL и позволить конечному потребителю беспокоиться об этом (в данном случае страница ASP.Net)? Как насчет того, чтобы BLL выдал исключение?

BLL может использоваться ASP.Net и Win App, или другой библиотекой классов - я думаю, что было бы несправедливо ожидать, что конечный потребитель «знает», что метод GetPersonByID возвращает ноль (если, конечно, не используются нулевые типы) ).

Мое мнение (для чего это стоит) состоит в том, что мой DAL возвращает NULL, если ничего не найдено. Для НЕКОТОРЫХ ОБЪЕКТОВ это нормально - это может быть список 0: много вещей, так что отсутствие каких-либо вещей - это хорошо (например, список любимых книг). В этом случае мой BLL возвращает пустой список. Для большинства отдельных объектов (например, пользователь, учетная запись, счет-фактура), если у меня их нет, то это определенно проблема, и это является дорогостоящим исключением. Тем не менее, видя, что поиск пользователя по уникальному идентификатору, который был ранее задан приложением, всегда должен возвращать пользователя, исключение является «правильным» исключением, поскольку оно является исключительным. Конечный потребитель BLL (ASP.Net, f'rinstance) только когда-либо ожидает, что все будет просто так, поэтому вместо упаковки каждого вызова GetPersonByID в блок try-catch будет использоваться обработчик необработанных исключений.

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

Мне нравится этот пост, много хороших предложений для сценариев "все зависит" :-)


И, конечно же, сегодня я столкнулся со сценарием, в котором я собираюсь вернуть NULL из моего BLL ;-) Тем не менее, я все еще могу выдать исключение и использовать try / catch в моем классе потребления, НО у меня все еще есть проблема : как мой потребительский класс знает, как использовать try / catch, подобно тому, как они знают, что нужно проверять NULL?
Майк Кингскотт

Вы можете задокументировать, что метод генерирует исключение через doctag @throws, и вы задокументируете факт, что он может возвращать null в doctag @return.
Тимоксли

1

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

Будет большое количество пунктов охраны, рассматривающих нулевую ссылку if (f() != null).

Что это null, принятый ответ или проблема? Является ли null допустимым состоянием для определенного объекта? (представьте, что вы клиент для кода). Я имею в виду, что все ссылочные типы могут быть нулевыми, но должны ли они?

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

Есть несколько решений tester-doer patternили реализация option typeиз функционального программирования.


0

Я озадачен количеством ответов (по всей сети), в которых говорится, что вам нужны два метода: метод IsItThere () и метод GetItForMe (), и это приводит к состоянию гонки. Что не так с функцией, которая возвращает null, присваивая ее переменной и проверяя переменную на Null все в одном тесте? Мой прежний код на C был приправлен

if (NULL! = (variable = function (arguments ...))) {

Таким образом, вы получаете значение (или ноль) в переменной и результат сразу. Эта идиома была забыта? Зачем?


0

Я согласен с большинством сообщений здесь, которые имеют тенденцию к null .

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

Для чего-либо с итератором я бы всегда использовал пустую коллекцию. Что-то вроде

foreach (var eachValue in collection ?? new List<Type>(0))

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

Краевой случай есть String. Многие люди говорят, что String.IsNullOrEmptyэто на самом деле не нужно, но вы не всегда можете различить пустую строку и нулевую. Кроме того, некоторые системы баз данных (Oracle) вообще не различают их ( ''хранятся как DBNULL), поэтому вы вынуждены обращаться с ними одинаково. Причина этого заключается в том, что большинство строковых значений поступают либо из пользовательского ввода, либо из внешних систем, в то время как ни текстовые поля, ни большинство форматов обмена не имеют разных представлений для ''и null. Таким образом, даже если пользователь хочет удалить значение, он не может сделать ничего, кроме очистки элемента управления вводом. Кроме того, различие nvarcharполей Nullable и Nullable базы данных более чем сомнительно, если ваша СУБД не оракул - обязательное поле, которое позволяет''странно, ваш пользовательский интерфейс никогда не допустит этого, поэтому ваши ограничения не отображаются. Так что ответ здесь, на мой взгляд, обрабатывать их одинаково, всегда.

Относительно вашего вопроса об исключениях и производительности: если вы генерируете исключение, которое вы не можете полностью обработать в логике вашей программы, вы должны в какой-то момент прервать то, что делает ваша программа, и попросить пользователя повторить то, что он только что сделал. В этом случае снижение производительности a catchявляется наименьшим из ваших беспокойств - необходимость спросить пользователя - это слон в комнате (что означает повторную визуализацию всего пользовательского интерфейса или отправку некоторого HTML-кода через Интернет). Поэтому, если вы не следуете анти-шаблону « Программный поток с исключениями », не беспокойтесь, просто бросьте один, если это имеет смысл. Даже в пограничных случаях, таких как «Исключение при валидации», производительность на самом деле не является проблемой, так как в любом случае вам придется снова спрашивать пользователя.


0

Асинхронный TryGet шаблон:

Я полагаю, что для синхронных методов ответ @Johann Gerell - это шаблон, который следует использовать во всех случаях.

Однако шаблон TryGet с outпараметром не работает с асинхронными методами.

С C # 7's Tuple Literals теперь вы можете сделать это:

async Task<(bool success, SomeObject o)> TryGetSomeObjectByIdAsync(Int32 id)
{
    if (InternalIdExists(id))
    {
        o = await InternalGetSomeObjectAsync(id);

        return (true, o);
    }
    else
    {
        return (false, default(SomeObject));
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.