Что является лучшей практикой при возвращении данных из функции. Лучше возвратит
null или пустой объект? И почему необходимо использовать один вариант по сравнению с другим.
Рассмотрим следующий вариант:
public UserEntity GetUserById(Guid userId)
{
//Здесь код доступа к базе данных...
//Проверяем вернувшиеся данные и возвращаем null если ничего не найдено
if (!DataExists)
return null;
//Или же я должен вернуть пустой объект?
//return new UserEntity();
else
return existingUserEntity;
}
Перевод
Ответы
Ответ 1
Выбор между null и null-объектом зависит от того, как метод будет использоваться.
Примеры
Получение коллекции пользователей
ICollection GetAllUsers();
Как этот метод будет использоваться? Скорее всего, примерно так:
foreach (User user in userService.GetAllUsers()) {
// Обработка или отображение конкретного юзера
}
Возвращение пустой коллекции в этом случае нам позволит сэкономить на одном if (foreac
не умеет игнорировать null). Нам неважно, были какие-то элементы в коллекции или нет, логика вызывающего метода от этого обычно не зависит. Если же зависит, то мы всегда можем воспользоваться свойством Count.
Получение одного пользователя
User GetUser(int id);
Как этот метод будет использоваться? Скорее всего, примерно так:
User user = userService.GetUser(id);
if (user == null) {
// Всё плохо, обработать ошибку
}
else {
// Обработать или отобразить пользователя
}
В этом случае разумно вернуть null, потому что наличие и отсутствие результата предполагает различное поведение вызывающего кода.
Чего точно не следует делать — это возвращать new User(): это не позволит нормальн
проверить, существует запрошенный пользователь или нет. Если уж делать null-объекты, то они по возможности должны быть в единственном экземпляре и неизменяемы.
Получение текущего пользователя
User GetCurrentUser();
Как этот метод будет использоваться? Скорее всего, примерно так:
User currentUser = userService.GetCurrentUser();
Console.WriteLine("{0} ({1})", currentUser.Name, currentUser.Level);
В этом случае обработка пользователя обычно не зависит от наличия или отсутстви
реального пользователя, поэтому можно вернуть объект "пользователь-гость". В случае, если проверка требуется, то можно реализовать свойство вроде User.IsRegistered или User.AccessLevel.
Планы на будущее
В C# 7 планируется добавить разделение между nullable и non-nullable для reference-типов (в дополнение к value-типам). Тогда методы выше будут выглядеть так:
ICollection GetAllUsers();
User? GetUser(int id); // Обратите внимание на знак вопроса
User GetCurrentUser();
Если вызвать метод GetUser и не проверить на значение null, то компилятор выдас
предупреждение.
ReSharper
Если у вас есть R#, то вы можете добавить аннотации уже сейчас:
[NotNull, ItemNotNull]
ICollection GetAllUsers();
[CanBeNull]
User GetUser(int id);
[NotNull]
User GetCurrentUser();
Эти аннотации позволят анализатору R# явно разделять, что может быть null, а чт
не может, и за счёт этого предупреждать пользователя о потенциальных ошибках.
Ответ 2
Если вы под пустым объектом понимаете Null Object pattern, я бы практически никогд
не рекомендовал его использовать. Проблема в том, что это позволяет подавить ошибку вместо того, чтобы проверить её. Этот паттерн по сути не особенно отличается от возвращения кода ошибки, только код ошибки при этом совмещён с возвращаемым объектом.
В случае возвращения null код, который по ошибке не проверяет это, немедленно упадё
в первом же тесте. Если же возвращать искусственный «пустой объект», код, который не проверяет его, будет считать, что он правильно работает, и вы никогда не найдёте ошибку.
У метода должен быть контракт: имеет ли право возвращаемый объект «отсутствовать
или нет. Для случая, когда отсутствие возвращаемого объекта есть нормальный, ожидаемый случай, нужно возвращать null. Для случая, когда отсутствие возвращаемого объекта означает по сути ошибку, нужно выбросить исключение.
Возврат коллекции из метода работает по сути так же. Если у вас метод отработал нормально
и полученная коллекция оказалась пустой (и это не является ошибкой), вы обычным образо
возвращаете коллекцию, состоящую из 0 элементов, это нормальный случай, успешное выполнение функции. Если у вас в результате ошибки результата нет, и это ожидаемый случай, то имеет смысл вернуть null. Если у вас в результате ошибки результата нет, и этот случай в принципе не является ожидаемым, то стоит выбросить исключение.
Ответ 3
Возвращение null мне кажется лучшей идеей для случаев когда данные недоступны
Возвращение пустого объекта предполагает что данные были возвращены, в то время как возврат null ясно дает понять что ничего возвращено не было.
Кроме того, возврат null может привести к возникновению NullReferenceException пр
попытке доступа к членам объекта, что может быть полезно для выявления ошибочного кода. Доступ же к членам пустого объекта не приведет к возникновению ошибки и данная ошибка долго может оставаться нераскрытой.
Перевод
Ответ 4
возвратить null или пустой объект?
Для reference-типов обычно возвращают null, а для value-типов возвращают 'пусто
объект' Empty (определен во многих структурах в .NET Framework).
Надо стараться не возвращать null, т.к. это является причиной большого количеств
багов. Tony Hoare (добавил null в algol в 1965) в 2009 году сказал, что null reference
-- The Billion Dollar Mistake (ошибка на миллиард долларов).
Например, если метод не может вернуть запрошенный объект, то вместо возврата null
можно использовать принцип Fail Fast. Или надо переопределить метод как Try*, например, int.TryParse.
Ответ 5
Частично эту проблему можно решить с помощью монады Maybe, т.е. возвращать что-то вроде:
Maybe GetUser(int id);
Или же монада With:
string name = rep.GetUser(-1).With(u => u.Name);
Можно использовать библиотеки из nuget (искать по monads).
Хотя такой подход накладывает обязательства на его поддержку во всей модели.
Ссылки по теме:
http://sergeyteplyakov.blogspot.ru/2014/06/void-safety-in-c.html - Борьба с "нулевыми" ссылками в C#
http://devtalk.net/2010/09/12/chained-null-checks-and-the-maybe-monad/ - Chaine
null checks and the Maybe monad
Комментариев нет:
Отправить комментарий