Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
null не является валидным элементом всех коллекций, за исключением IEnumerable<Nullable<T>>. Возможно что-то подправили в решарперовском переопределении свойств. (у них же на всё xml-ки есть)Переводить же все баги в разряд скрытых и трудноотлавливаемых — это как лгать самому себе.
чем жестче будет падать система — тем лучше, быстрее починят
return items.FirstMaybe(cond).OrElse(() => new ItemNotFoundException());
Зачем нам нужна эта противная сборка мусора. Всегда были стандартные средства для ручного управления памятью. Усложнять систему созданием гарбадж коллектора совсем необязательно.
Основная проблема null, в том что он не сохраняет тип. Зачем это нужно хорошо описано в Types and Programming Languages
Сборка мусора, как раз упрощает систему, поскольку забирает ответственность за контролем памяти от клиента. Вариант с Maybe добавляет новый элемент в прогамму и способствует непреднамеренному копированию ссылок.Сборка мусора тоже добавляет новый элемент в язык. Принципиальных отличий нет. Разве что сборку мусора не отключить в текущей реализации языка.
ModifyItem(ref item), то это ад и погибель. Item должен быть неизменяемым. Добавление функции с сигнатурой Maybe<TResult> Map<T, TResult>(this Maybe<T> souce, Func<T,TResult> transformation);Сборка мусора тоже добавляет новый элемент в язык. Принципиальных отличий нет. Разве что сборку мусора не отключить в текущей реализации языка.
А так Maybe забирает ответственность за контролем невалидных данных (если следовать контракту, что явно не указанная возможность null — ошибка) с пользователя на компилятор. Задачи одного порядка.
А про непреднамеренное копирование ссылок я вообще не понял. Если Вы про ModifyItem(ref item), то это ад и погибель. Item должен быть неизменяемым. Добавление функции с сигнатурой
Maybe<TResult> Map<T, TResult>(this Maybe<T> souce, Func<T,TResult> transformation);
вообще избавит от большинства проблем.
<irony> надо ставить…Maybe<T>.int SaveItem(Item toSave)) Maybe<T> не используется.<T>. Класс должен выглядеть какprivate readonly T item;public Maybe(T obj){ if(obj == null) throw new InvalidOperationException(); item = obj;}public Maybe<Item> SomeMethod(){ return GetMbItem().Map(ModifyItem);}Я хотел сказать, что сборщик, это фича (языка, платформы, системы), которая точно так-же позволяет переложить часть ответственности с программиста на машину. Пусть уже встроенная в язык, но фича. И если уж отказыватся от фич типизации, то почему-бы не отказаться и от этой. Видимо <irony> надо ставить…
Ещё раз повторю мысль из всех комментариев. По умолчанию null это метко. В большинстве случаев мне, как программисту, не хочется проверять исключительный случай, когда объект null. Более того мне кажется неправильным разделение на `один особенный объект` и `все остальные`.
Поэтому предлагается ввести конструкцию Maybe, которая позволит явно указать места, где метка null может появляться, а в остальных местах про неё просто забыть. Таким образом во всех функциях где возможно исключительное поведение (возвращение нулл, как GetItem) ставится Maybe<T>.
Под объектом я имел ввиду экземпляр Maybe<T>. Класс должен выглядеть как
private readonly T item;
public Maybe(T obj){
if(obj == null) throw new InvalidOperationException();
item = obj;
}
и не менять объект внутри него не при каких обстоятельствах.
var item = mbItem.Value; Новая ссылка, новая головная боль.Maybe увеличивает сложность, не решая задач. Не вижу тут никакой иронии.То что оно не решает задач исключительно Ваше мнение.
Null не особенный и не объект. Это свойство ссылки.Null это страшное наследие из C, которое во многих современных языках уже прибили. И да, я не хочу думать над программой в терминах указателей, ссылок и их свойств. Оставьте это ребятам из лагеря плюсов.
Если я могу увидеть объект, то я могу создать новую ссылку на него. Так и делают: var item = mbItem.Value; Новая ссылка, новая головная боль.Тогда исключительно Ваша проблема, что Вы решили себе создать немного новой головной боли. Я показал как в реальной задаче сделать так, чтобы боли не возникло даже случайно.
И да, я не хочу думать над программой в терминах указателей, ссылок и их свойств. Оставьте это ребятам из лагеря плюсов.
если вам это так не нравится, то следует выбрать другой язык.
Maybe<User> Find(int id);var user = FindUser(1);
if (user.Value.LastName == "Иванов")
var user = FindUser(1);
if (user.HasValue)
{
if (user.Value.LastName == "Иванов")
...
}
else
{
// Обработка отсутствия значения
}
var user = FindUser(1);
if (user != null)
{
if (user.LastName == "Иванов")
...
}
else
{
// Обработка nul
}
Maybe<TResult> Map<T, TResult>(this Maybe<T> souce, Func<T,TResult> transformation);T Extract<T>(this Maybe<T> souce, T fallbackValue)(например методы из EntityFramework и базирующихся на них RIA-сервисы)Про это уже писали в habrahabr.ru/post/118934/
var ivanov = from u in FindUser(1) where u.LastName == "Иванов"
FindUser(1)
.Where(u => u.LastName == "Иванов")
.Match(GreetUser, orElse: LaunchMissiles)
Невозможно покрыть все скользкие места. Иногда нужно просто писать корректный код,
Зачем засовывать один ссылочный тип в другой и добавлять в контейнер свойство HasValue, для меня решительно не понятно.— а почему вы решили, что контейнер Maybe обязательно должен быть ссылочным типом? Что мешает его сделать значимым, наподобие контейнера Nullable? В критикуемой вами статье есть таблица с реализациями, так вторая реализация Strilanc — May использует значимый тип (
public struct May<T> : IMayHaveValue, IEquatable<May<T>>).В случае если метод ModifyItem подменил объект, то метод SomeMethod вернет контейнер со старым объектом. Иди потом, ищи этот баг.— а вот здесь поподробнее, пожалуйста. Метод
SomeMethod() из вашего примера вернёт старый объект в контейнере лишь и только в том случае, если при создании экземпляра контейнера Maybe его код выполняет глубокую копию своего значения и сохраняет именно эту копию. Все же контейнеры, если они написаны адекватно, хранят лишь ссылку на значение, которая может быть NULL (в этом случае метод контейнера HasValue возвратит false).Boolean TryGet(ID, out Result).var item = mbItem.Value; создается новая ссылка на тот же объект, которая будет передана в метод ModifyItem. Если внутри этого метода, эта ссылка будет стоять слева от знака равно, то ей будет присвоен новый адрес, в то время как ссылка в контейнере будет ссылаться на старый объект. Таким образом новый объект останется внутри блока if.if. Впрочем, возможна и ситуация, когда тот же ModifyItem модифицирует полученное значение без создания новой ссылки — в этом случае всё будет ОК.Data access layer должен достать объект из репозитория и передать клиенту. Он не знает как обработать отсутствие объекта и вообще не в курсе плохо ли это. За это отвечает уровень логики.
однако
Ссылочный тип может ссылаться на null. Зачем засовывать один ссылочный тип в другой и добавлять в контейнер свойство HasValue, для меня решительно не понятно.
В первой озвучивается типичный для меня при следовании DDD подход к реализации Repository: методы Get и Find (в практике еще часто встречаю метод Exists для других сценариев). При чем первый не должен возвращать null ни в коем случае — либо результат, либо exception, который должен быть более конкретным чем NRE, хотя бы RecordNotFound.
Второй момент в исходной статье — Null Object pattern, который в приведеннои примере позволяет следовать функциональному («ленивому») подходу.
Я не трогал метод Find, потому что с ним все хорошо.
Null object pattern это из другой оперы.
public void Buy(int itemId, int count)
{
....
Decimal totalPrice = CalcTotalPrice(DAL.GetItem(ItemId), count);
....
}
private decimal CalcTotalPrice(Item item, int count)
{
return item.Price * count;
}
Можно писать 2 метода, один с NRE другой без
это не правильно, если для одной сущности это так, а для другой нет
Вы согласны, что одни методы должны всегда вернуть непустой результат и если они не могут этого сделать, это исключительная ситуация, а другие вполне могут вернуть пустоту как штатный результат? И если да, то как это согласуется с вашим заявлением:
public Item GetItem(int itemId)
{
return dataAccessor
.getItemById(itemId)
.ToMaybe()
.OrElse(() => new ItemNotFound(itemId));
}
public Maybe<Item> FindItem(int itemId) // а Get выше тогда должен реализовываться через этот Find для устранения дублирования
{
return dataAccessor.getItemById(itemId).ToMaybe();
}
При этом в одном из примеров он использует контракты, что противоречит их принципам.При этом «противоречие принципам» автор иллюстрирует якобы необходимостью вставить в сторожевое условие сам код метода:
Что бы достать объект из репозитория, нужно сначала вытащить объект из репозитория.
Правда в том, что никто не может гарантировать существование объекта в репозитории.
Я перефразирую: nullable reference. Масло масляное
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W)…
Давайте явно уясним одну вещь: (a) возможность/невозможность принимать значение null и (b) различие между значимыми и ссылочными типами — это ((a) vs (b)) ортогональные (независимые) свойства и это правильно. Было бы хорошо, если бы любой тип мог явно задекларировать, поддерживает ли он значение null, или нет.
Что мешает обратится к содержимому объекту без этой проверки? Можно точно так-же безалаберно не проверить на null через неравенство.
public Maybe<Item> SomeMethod()
{
var mbItem = GetMbItem();
if(mbItem.HasValue)
{
var item = mbItem.Value;
ModifyItem(ref item);
}
return mbItem;
}
public Maybe<Item> SomeMethod()
{
return GetMbItem().Select(ModifyItem);
}
public Maybe<Item> SomeMethod()
{
return GetMbItem().Select(i => { ModifyItem(ref i); return i; });
}
для того, чтобы не допускать NullReferenceException, нужно быть внимательным,готов принять даже более широкий тезис, например, такой: «нужно быть внимательным».
В C# с типами все в порядке