Pull to refresh
8
0
Send message
Извините, перепутал null object pattern.
А так, полностью с вами согласен.
По поводу выбрасывания исключения в dao, это не правильно, если для одной сущности это так, а для другой нет. Например если метод Get для номера телефона может вернуть null, а Get для пользователя выбросить исключение. Так делать нельзя. Можно писать 2 метода, один с NRE другой без. Однако это ничего по сути не меняет. Только способ обработки null. В одном месте проверить через не равенство, в другом блоком try. И в любом случае, это будет сделано на уровне логики.
Null object не всегда можно подставить. Например, я хочу получить пост с таким то id. Если этого поста нет, то что мне подставить вместо? С точки зрения логики не существует дефолтного поста. И даже если это будет реализовано, это приведет к условным проверкам. Например, придется проверять Id на 0 или еще как нибудь извращаться.
Для поддержания семантики можно использовать TryGet упомянутый Klotos. Очень гибкое и удобное решение. Maybe добавляет сложности, но не добавляет решений.

Что касается аргументов, то я считаю, что никому они ничего не должны. Передавая класс и поменяв в нем что-то, это отобразится везде, а передавая struct нет. А поменяв что-то в классе внутри struct'а, это отобразится опять-же везде. В коде нет ничего святого. Есть случаи когда лучше скопировать ссылку, когда ее лучше передать, а есть когда удобней передать указатель. А выстрелить себе в ногу, всегда способ найдется.
Я хотел сказать, что сборщик, это фича (языка, платформы, системы), которая точно так-же позволяет переложить часть ответственности с программиста на машину. Пусть уже встроенная в язык, но фича. И если уж отказыватся от фич типизации, то почему-бы не отказаться и от этой. Видимо <irony> надо ставить…

Эта фича исключила контроль памятью из приложения, сократив количество кода и уменьшив сложность. Что бы работать с ней, нужно делать меньше, чем без нее. Maybe увеличивает сложность, не решая задач. Не вижу тут никакой иронии.

Ещё раз повторю мысль из всех комментариев. По умолчанию null это метко. В большинстве случаев мне, как программисту, не хочется проверять исключительный случай, когда объект null. Более того мне кажется неправильным разделение на `один особенный объект` и `все остальные`.
Поэтому предлагается ввести конструкцию Maybe, которая позволит явно указать места, где метка null может появляться, а в остальных местах про неё просто забыть. Таким образом во всех функциях где возможно исключительное поведение (возвращение нулл, как GetItem) ставится Maybe<T>.

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

Под объектом я имел ввиду экземпляр Maybe<T>. Класс должен выглядеть как
private readonly T item;
public Maybe(T obj){
if(obj == null) throw new InvalidOperationException();
item = obj;
}
и не менять объект внутри него не при каких обстоятельствах.


Если я могу увидеть объект, то я могу создать новую ссылку на него. Так и делают: var item = mbItem.Value; Новая ссылка, новая головная боль.
Я не говорю, что плохо если такой метод есть, плохо если только такой и есть. DAL не должен знать когда ему бросать исключение на основе данных, а на основе того, что ему сказали сверху.
Главное не оставлять клиента с методом который бросает исключение всякий раз, как объект не найден. Его можно добавить, но это уже дело вкуса.
В первой озвучивается типичный для меня при следовании DDD подход к реализации Repository: методы Get и Find (в практике еще часто встречаю метод Exists для других сценариев). При чем первый не должен возвращать null ни в коем случае — либо результат, либо exception, который должен быть более конкретным чем NRE, хотя бы RecordNotFound.

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

Второй момент в исходной статье — Null Object pattern, который в приведеннои примере позволяет следовать функциональному («ленивому») подходу.

Null object pattern это из другой оперы. Его идея заключается в том, что если объект не найден или его функционал не требуется, то создается другой объект с тем же интерфейсом, который ничего не делает. Например, если на одном из серверов не нужно кеширование, ему дается пустышка, которая ничего не делает. Посмотрите тут пример на C#.

Про Nullable я ответил чуть выше.
Все верно, однако в том же linq есть, например, методы Single и SingleOrDefault, и оба вызываются кем-то сверху. И тот кто наверху и решает какой из методов больше подходит. Сам же linq не знает о том какой и методов уместней, он просто делает, что ему сказали.
1. Разумеется можно использовать struct, но это приводит к другим сложным моментам, из за копирования struct'а. Nullable работает хорошо поскольку оперирует value types, или предназначен для этого, во всяком случае. Использование struct'а, как контейнера для класса, создаст много проблем с целостностью ссылок в разных частях стека, поэтому я сразу отбросил этот вариант, как слишком неуправляемый. Если вы считаете, что стоит это упомянуть, я обязательно сделаю.

2. Возможно я не так понял это замечание, поправте если так. Метод SomeMethod достает объект из репозитория и, в случае если он действительно есть, хочет его модифицировать. В строке var item = mbItem.Value; создается новая ссылка на тот же объект, которая будет передана в метод ModifyItem. Если внутри этого метода, эта ссылка будет стоять слева от знака равно, то ей будет присвоен новый адрес, в то время как ссылка в контейнере будет ссылаться на старый объект. Таким образом новый объект останется внутри блока if.
Сборка мусора тоже добавляет новый элемент в язык. Принципиальных отличий нет. Разве что сборку мусора не отключить в текущей реализации языка.

Я ни разу не трогал сборщик в реальных проектах. Работает он сам по себе, никого не трогает и не мешает. Его можно настроить или вызвать, но это не обязанность, а лишь возможность.

А так Maybe забирает ответственность за контролем невалидных данных (если следовать контракту, что явно не указанная возможность null — ошибка) с пользователя на компилятор. Задачи одного порядка.

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

А про непреднамеренное копирование ссылок я вообще не понял. Если Вы про ModifyItem(ref item), то это ад и погибель. Item должен быть неизменяемым. Добавление функции с сигнатурой
Maybe<TResult> Map<T, TResult>(this Maybe<T> souce, Func<T,TResult> transformation);
вообще избавит от большинства проблем.

Ref это способ передать ссылку по стеку. То, что вы можете пользоваться одной и той же ссылкой в начале и в конце метода, вас не смущает?
И я не понял, почему объект должен быть неизменяемым (как я понял, речь об immutable)? Как же мне тогда обновлять репозиторий?
И как добавление метода с данной сигнатурой спасет? Даже если это не вызовет ада со ссылками, это просто функция, язык не обязывает ею пользоваться. Напротив, ref в сигнатуре оставляет лишь один способ воспользоваться методом.
Зачем нам нужна эта противная сборка мусора. Всегда были стандартные средства для ручного управления памятью. Усложнять систему созданием гарбадж коллектора совсем необязательно.


Сборка мусора, как раз упрощает систему, поскольку забирает ответственность за контролем памяти от клиента. Вариант с Maybe добавляет новый элемент в прогамму и способствует непреднамеренному копированию ссылок.

Основная проблема null, в том что он не сохраняет тип. Зачем это нужно хорошо описано в Types and Programming Languages


А еще Boxing/Unboxing не сохраняет тип. Надо сделать строго типизированный вариант для этого. И еще, для каждого проекта писать свою реализацию кеша. А еще что-бы не получить DivisionByZero сделать struct который не сможет принимать 0.

Невозможно покрыть все скользкие места. Иногда нужно просто писать корректный код,
Не в ту ветку.
12 ...
45

Information

Rating
Does not participate
Registered
Activity