Comments 63
Еще есть www.nuget.org/packages/Monads/ от sergun
+3
Спасибо за ссылки на библиотеки. У меня есть несколько дежурных экстеншн методов проверки на null, но зачем использовать велосипед, попробую вашу библиотеку :) Maybe — хорошее и выразительное название для функции.
0
> первый метод все еще может вернуть null. Простого способа запретить это на уровне языка — нет.
Есть. И вы сами же упомянули контракты. Тем более что это стандартная фича.
Есть. И вы сами же упомянули контракты. Тем более что это стандартная фича.
+2
Ну, я имею в виду так, чтобы глобально, например, для целого проекта запретить. На уровне метода — да, контракты. Ну а на уровне проекта — тоже контракты, но с AOP, я об этом написал ниже :)
0
А как это может выглядеть на уровне проекта (особенно не трогая код конкретных интерфейсов)? Что-то слабо представляю. Все равно как-то придется отмечать методы, а это суть — контракты (вопрос реализации).
0
Почему же, если на уровне проекта мы заявляем: «методы никогда не возвращают null», то, соответственно, нужно внедрить в код каждого метода, возвращающего ссылочный тип, вышеуказанный контракт. Это работа для AOP.
При этом подразумевается, что повсеместно (где необходимо) передается/возвращается Maybe. Точки сочленения с внешним миром оборачиваются, если необходимо, тоже в Maybe. И — вуаля, «ошибка на миллион долларов» исправлена :)
При этом подразумевается, что повсеместно (где необходимо) передается/возвращается Maybe. Точки сочленения с внешним миром оборачиваются, если необходимо, тоже в Maybe. И — вуаля, «ошибка на миллион долларов» исправлена :)
0
Мы с этой же целью используем стандартные решарперовские атрибуты [CanBeNull], [NotNull], а статический анализатор подскажет, что нужна проверка на нуль еще во время написания кода. И не нужны никакие соглашения в команде.
+12
Добавлю, что можно сделать принудительную элевацию (в т.ч. в масштабах solution'а) уровня проверок на PossibleNullReferenceException в R# с Warning до Error. Таким образом даже сборка станет невозможной, пока в коде присутствуют подобные скользкие места.
0
Мне кажется, что ошибки, обнаруженные Resharper никак не влияют на способность компилятора собрать проект, или я ошибаюсь и это можно где-нибудь включить?
Было бы, кстати, весьма неприятно, так как Resharper иногда перестает видеть типы из других сборок и тогда половина проекта становится красной, но на компиляцию это, к счастью, не влияет.
Было бы, кстати, весьма неприятно, так как Resharper иногда перестает видеть типы из других сборок и тогда половина проекта становится красной, но на компиляцию это, к счастью, не влияет.
0
На способности csc.exe конечно не влияют, а вот на способности Visual Studio — да. И если R# говорит, что есть ошибка, то она есть и сборки не произойдет. Не надо путать Hint, Suggestion, Warning и Error — все решарперовское, но вот только Error'ы, которые генерирует ReSharper вполне себе Visual Studio'йные.
0
Ой, картинка неверно загрузилась, в следующем комментарии.
0

Что я делаю не так? Всё прекрасно собирается, несмотря на ошибки, замеченные Resharper. Так было всегда на моей памяти — пользуюсь Resharper уже много лет.
0
Насчет Error — верно, он никак не может влиять на сборку. Однако можно попробовать использовать ReSharper Command line tools www.jetbrains.com/resharper/features/command-line.html, настроив там проверку на вышеуказанный Error, и встроить результаты анализа в билд скрипт, которые при наличии таких ошибок в отчете будет валить сборку.
0
Да, такая возможность была бы полезно, но у нас в компании не всем нравится решарпер и, думаю, было бы весьма сложно убедить их в том, что он нужен на билд-контроллере. А на своей машине я и так вижу результаты анализа, к тому же в моём коде нашлось всего одно такое место, и то не критичное.
0
Моё правило в разработке — методы поиска объектов не возвращают null, а генерируют исключение при отсутствии объекта.
Если факт отсутствия нужного объекта допускается бизнес-логикой приложения, то метод поиска пишется в bool TryGet(T key, out value) стиле — в этом случае выражение if получается автоматически.
Т.е. работаем по принципу Dictionary — можно попробовать нагло получить значение, если уверены в том, что оно обязано быть или попробовать получить с проверкой на наличие. При этом сам метод очевиден, понятен, не требует оборачивания в дополнительные классы.
NullReference попадаются, но практически всегда это в данных, получаемых из сторонних компонентов или через ServiceReference.
Если факт отсутствия нужного объекта допускается бизнес-логикой приложения, то метод поиска пишется в bool TryGet(T key, out value) стиле — в этом случае выражение if получается автоматически.
Т.е. работаем по принципу Dictionary — можно попробовать нагло получить значение, если уверены в том, что оно обязано быть или попробовать получить с проверкой на наличие. При этом сам метод очевиден, понятен, не требует оборачивания в дополнительные классы.
NullReference попадаются, но практически всегда это в данных, получаемых из сторонних компонентов или через ServiceReference.
+2
Весьма спорно, при поиске кидать исключение. Поиск подразумевает возможность получения пустого списка, а вот операция получения конкретного объекта — уже как раз может кидать исключение. Тоесть все методы в наименовании которых есть Search* или Find* — никогда не кинут исключение о том что ничего не нашли, а вот Get* — кинет. Но это имхо)
+1
Уже после публикации сообщения заметил, что написал «поиск» вместо «получения». Понятно, что методы поиска, которые могут вернуть несколько объектов возвращают IEnumerable. На IEnumerable у меня есть свой Extension.
Если коллекция не была создана в текущем методе, а была получена откуда-то (т.е. я не уверен что она не Null), то я всегда пользуюсь AnySafe.
public static bool AnySafe<T>(this IEnumerable<T> collection, Func<T,bool> predicate = null)
{
return collection != null && (predicate == null ? collection.Any() : collection.Any(predicate));
}
Если коллекция не была создана в текущем методе, а была получена откуда-то (т.е. я не уверен что она не Null), то я всегда пользуюсь AnySafe.
0
Моё имхо:
Для решения вопроса генерить или нет исключения, применяется простое правило: если метод не выполнил действие, которое его просили выполнить, то надо генерить исключение.
Если говорят «дай!» (get), а он не даёт, то генерится исключение.
Если говорят «найди!» (find) а он не находит, то генерится исключение.
Наверное вы путаете английские слова find (найти, найди!) и search (seek, look for), (искать, ищи!)
Короче, find должен или находить, или генерить исключение,
а search (seek, look for) должен искать и сообщать о результате поиска,
который может быть отрицательным (не нашёл) или положительным (нашёл, что нашёл).
Такого принципа последнее время придерживаются и в Microsoft (см. например Find из Linq).
Для решения вопроса генерить или нет исключения, применяется простое правило: если метод не выполнил действие, которое его просили выполнить, то надо генерить исключение.
Если говорят «дай!» (get), а он не даёт, то генерится исключение.
Если говорят «найди!» (find) а он не находит, то генерится исключение.
Наверное вы путаете английские слова find (найти, найди!) и search (seek, look for), (искать, ищи!)
Короче, find должен или находить, или генерить исключение,
а search (seek, look for) должен искать и сообщать о результате поиска,
который может быть отрицательным (не нашёл) или положительным (нашёл, что нашёл).
Такого принципа последнее время придерживаются и в Microsoft (см. например Find из Linq).
0
Именно. Методы должны делать то, что следует из их названия — это путь к предсказуемости API.
Сколько я намучился с кодом предыдущих разработчиков, которые писали ядро системы. Они написали его так, что при явном поиске конкретного объекта по индексатору через this[short key] возвращается null, если объекта с key не существует. Хуже всего в этой ситуации то, что возвращенный null может стрельнуть в непредсказуемом месте, где-нибудь через десяток строк от места получения или даже в другом методе.
Например Calculate(..., Context[Key], ...). И в этом случае начинается увлекательный DEBUG.
Сколько я намучился с кодом предыдущих разработчиков, которые писали ядро системы. Они написали его так, что при явном поиске конкретного объекта по индексатору через this[short key] возвращается null, если объекта с key не существует. Хуже всего в этой ситуации то, что возвращенный null может стрельнуть в непредсказуемом месте, где-нибудь через десяток строк от места получения или даже в другом методе.
Например Calculate(..., Context[Key], ...). И в этом случае начинается увлекательный DEBUG.
0
Это не поиск по индексатору, а явное обращение к конкретному объекту через индексатор. По сути — GetByKey(short key).
PS. Еще по поводу поведения Find:
В LINQ Find — msdn.microsoft.com/en-us/library/x0b5b5bc.aspx
The first element that matches the conditions defined by the specified predicate, if found; otherwise, the default value for type T.
PS. Еще по поводу поведения Find:
В LINQ Find — msdn.microsoft.com/en-us/library/x0b5b5bc.aspx
The first element that matches the conditions defined by the specified predicate, if found; otherwise, the default value for type T.
0
Согласен, неудачно выбранный термин. «Получение объектов по индексатору».
Хотя никто не мешает написать даже так:
Что позволяет особо одаренным программистам реализовывать через индексаторы любую логику, которая придёт в голову.
Хотя никто не мешает написать даже так:
public IEnumerable<object> this[int key]
{
get
{
Что позволяет особо одаренным программистам реализовывать через индексаторы любую логику, которая придёт в голову.
0
otherwise, the default value for type T.
Удивлён. Поражён. Перепутал с First, который генерит исключение, если не нашёл ничего по заданному критерию, т.е. фактически выполняет поиск. Я его (First) всегда и использую для поиска…
0
Уж если апелировать к MS то в EF Find возвращает null: msdn.microsoft.com/en-us/library/gg696418(v=vs.113).aspx
При поиске(как Search так и Find) не гарантируется, что будет что-то найдено. При явном запросе — Get — вы априори считаете что такой объект уже есть, и отсутствие такового — исключение.
При поиске(как Search так и Find) не гарантируется, что будет что-то найдено. При явном запросе — Get — вы априори считаете что такой объект уже есть, и отсутствие такового — исключение.
0
Скринкаст: Монада Maybe на языке C#.
+5
Конечно, это оно, да и Func<T, ...> тоже есть, как функциональный тип.
Но и то, и другое можно поудобнее сделать.
Например, поддержать анонимный функциональный тип вида (int -> string), автоматически приводимый к любому подходящему делегату, как Func<int, string> так и какому-нибудь sting MyCustomDelegate(int a).
То же и с кортежами:
Сейчас так:
А можно было бы так:
Но и то, и другое можно поудобнее сделать.
Например, поддержать анонимный функциональный тип вида (int -> string), автоматически приводимый к любому подходящему делегату, как Func<int, string> так и какому-нибудь sting MyCustomDelegate(int a).
То же и с кортежами:
Сейчас так:
var tuple = IntegerDivide(5, 3);
var div = tuple.Item1;
var mod = tuple.Item2;
А можно было бы так:
var (div, mod) = IntegerDivide(5, 3);
0
Есть анонимные типы, хотя с ними тоже в одну строку не получится, но всё же:
var tuple = IntegerDivide(5, 3);
var anon = new { div = tuple.Item1, mod = tuple.Item2 };
Хотя я не совсем понимаю зачем перекладывать значения в локальные переменные, если они уже лежат в самом Tuple. Разве что для повышения читабельности кода, но тогда Tuple лучше не использовать вообще, а завести мелкую структуру или класс.
var tuple = IntegerDivide(5, 3);
var anon = new { div = tuple.Item1, mod = tuple.Item2 };
Хотя я не совсем понимаю зачем перекладывать значения в локальные переменные, если они уже лежат в самом Tuple. Разве что для повышения читабельности кода, но тогда Tuple лучше не использовать вообще, а завести мелкую структуру или класс.
0
Вот эти Item1… ItemN IMHO большой минус для Tuple. Без доки или изучения кода вызываемого метода фиг поймешь что в них.
0
Только и разницы, что вместо одной строчки две. Никто не умрет, но суть немного затуманивается. Чем такого меньше, тем лучше.
+1
Мне кажется удобнее использовать нультипы. Во всех враперах можно все равно запросить значние если его нет, и это ситуация, также порождает ексцепшен. Т.е. синтаксически ничем не отличается от проверки на null.
0
Нультипы — это Nullable? Их нельзя использовать с ссылочными типами, т.е. к текущей проблеме это не имеет отношения — Int или структура всё равно не может быть null.
0
Нет, это тип твоего объекта но содержащий спец информацию, отладочную или пишуший лог. т.е. FindUser будет всегда находить объект, если юзера нет, то вернется спец объект типа User. И проверять нужно не на Null а на, например, User.Empty
Вот например string.Epmty, Guid.Empty это оно и есть.
Вот например string.Epmty, Guid.Empty это оно и есть.
0
EventArgs.Empty и т.д. Пользовался, но не знал что это нультипами называется. Мне кажется что это не всегда применимо — типы бывают достаточно тяжелыми и жестко связанными с окружением. Ну и плюс никак не гарантируется компилятором, что некто будет этот нультип не то, что использовать, а даже знать о нём. Как проверял на null, так и будет проверять.
0
Я так понял, что вы имеете в виду шаблон Null Object. Это вполне хорошее решение, когда оно на своем месте. Однако, на мой взгляд, на роль универсального избавителя от null оно не подходит. Этот шаблон относится к моделированию предметной области: если у нас есть семейство объектов (например, разные типы Начислений в финансовом приложении), и для них можно (для унификации обработки) говорить также об Отсутствующем Начислении (если это имеет смысл с точки зрения той модели, в которой мыслят пользователи и проектировщики приложения), то это хорошее место для применения Null Object.
Однако, если у меня есть коллекция users и мне нужно сделать следующее:
То тут вводить NullUser, на мой взгляд, неуместно, так как он не будет представлять никакую существенную концепцию в модели предметной области.
Что же касается возможности вызвать maybe.Value при отсутствии значения, она, хоть и приведет к исключению, все же является явной операцией. Вы прицеливаетесь и четко стреляете себе в ногу, сие есть ваше неотъемлемое право. Мы лишь гарантируем, чтобы пистолет не выстрелил без нажатия на курок :)
Однако, если у меня есть коллекция users и мне нужно сделать следующее:
var defaultAdmin = ...;
var admin = users.FirstMaybe(u => u.IsAdmin).OrElse(defaultAdmin);
То тут вводить NullUser, на мой взгляд, неуместно, так как он не будет представлять никакую существенную концепцию в модели предметной области.
Что же касается возможности вызвать maybe.Value при отсутствии значения, она, хоть и приведет к исключению, все же является явной операцией. Вы прицеливаетесь и четко стреляете себе в ногу, сие есть ваше неотъемлемое право. Мы лишь гарантируем, чтобы пистолет не выстрелил без нажатия на курок :)
+1
А на столько ли страшен null, как его малюют? В нормально спроектированной системе null может вернуться только из поисковых запросов. Если активно используется linq, то .First() и .Single() решают проблему полностью. В других случаях, кроме как вредительством возвращение null не назовешь.
Сам активно использую реализацию Maybe от mezastel, заметил, что кроме linq она у меня встречается только в вариантах, с linq или где нельзя поставить ??
Сам активно использую реализацию Maybe от mezastel, заметил, что кроме linq она у меня встречается только в вариантах, с linq или где нельзя поставить ??
+1
По личному опыту поиска багов в чужом коде, могу сказать, что 90% NullReferenceException во время исполнения — результаты .First() и .Single().
-2
Странный у Вас опыт — результатом First или Single не может быть null, разве что null был элементом массива, но тогда это не беда First или Single, а беда логики, анализирующей результаты поиска.
+1
Точно. Недопроснулся. InvalidOperationException
+1
Это совсем не то же самое, что NullReference, и за эту ошибку нужно точно бить по рукам — разработчик не понимает бизнес-логики своего приложения.
Этими методами нужно пользоваться только тогда, когда уверен в наличии нужного элемента и когда его отсутствие свидетельствует только о каких-то серьезных проблемах, при которых продолжать дальнейшую работу с приложением не только бессмысленно, но и чревато нарушениями каких-то бизнес-процессов, поэтому все First желательно оборачивать в try...catch с генерацией осмысленной ошибки.
Этими методами нужно пользоваться только тогда, когда уверен в наличии нужного элемента и когда его отсутствие свидетельствует только о каких-то серьезных проблемах, при которых продолжать дальнейшую работу с приложением не только бессмысленно, но и чревато нарушениями каких-то бизнес-процессов, поэтому все First желательно оборачивать в try...catch с генерацией осмысленной ошибки.
+1
Либо оставить и не париться, если это очевидное место кода, например — вызов API. Скажем какой-нибудь
Можно, конечно, лучше детализировать ошибку, но если это внутренние API и так сойдет.
class ProductController
{
//...
public int Buy(int userId, int productId)
{
this._uow.Users.Single(u => u.Id == userId);
//...
}
}
Можно, конечно, лучше детализировать ошибку, но если это внутренние API и так сойдет.
+1
Пользователя уже могли успеть удалить или перевести в архивное состояние. Если есть время поставить try...catch — лучше это сделать — потом сам себе спасибо скажешь.
Но в целом согласен — иногда приходится делать и ...First().Action() просто потому что, если First ничего не вернул, то дальше и разговаривать не о чем, а обработчики писать некогда.
Сейчас решил побаловаться монадами с проверкой на null — воткнул в пару мест по коду. Кажется ничего так получается — вполне читабельно и удобно.
Но в целом согласен — иногда приходится делать и ...First().Action() просто потому что, если First ничего не вернул, то дальше и разговаривать не о чем, а обработчики писать некогда.
Сейчас решил побаловаться монадами с проверкой на null — воткнул в пару мест по коду. Кажется ничего так получается — вполне читабельно и удобно.
+1
я о том же
0
>все First желательно оборачивать в try...catch с генерацией осмысленной ошибки.
А какую можно сгенерировать ошибку, более осмысленную чем InvalidOperation со стектрейсом, показывающим прямо на вызов First/Single?
А какую можно сгенерировать ошибку, более осмысленную чем InvalidOperation со стектрейсом, показывающим прямо на вызов First/Single?
0
Например «Товара с кодом '100' нет в прайсе. Возможно он был удалён или ....». Не знаю каков у Вас опыт сопровождения продуктов, но InvalidOperation со стектрейсом для конечного пользователя — не просто китайская грамота — это как красная тряпка для быка. А InvalidOperation со стектрейсом аккуратненько уходит в лог в виде InnerException.
+1
Да, и в одном методе может быть не один Linq.First, как разработчику узнать чего именно не оказалось?
0
По номеру строки, например.
0
В Release версии? Ну-ну — удачи.
0
Если приложение не с секретными какими технологиями, то я не вижу причин не класть pdb-шки в релизную версию.
0
У серьезного приложения котчами должны быть охвачены основные уязвимые места, особенно если конечный пользователь после этого сможет самостоятельно разобраться с её причиной или хотя бы понять что не так, вместо чтения ненужных ему стеков.
0
Идея сделать ошибки user-frendly — она вроде как правильная по-идее. Но на практике для юзера все равно сколько там страшных букв будет, а для программиста или админа — настоящая причина ошибки лучше, чем то, что там другие программисты подумали могло случиться.
Так что я в серьезных приложениях стараюсь делать только top-level перехват исключений для логирования, и общей устойчивости.
Кстати советую убедиться что у тебя логируются innerException-ы. Многие логгеры без дополнительных приседаний этого не делают, log4net, например.
Так что я в серьезных приложениях стараюсь делать только top-level перехват исключений для логирования, и общей устойчивости.
Кстати советую убедиться что у тебя логируются innerException-ы. Многие логгеры без дополнительных приседаний этого не делают, log4net, например.
+1
Если так хочется строгости и не смущает Maybe, то можно изобрести Always:
public interface IUserRepo
{
Always<User> Get(int id);
Maybe<User> Find(int id);
}
0
Проблему того, то что в C#-е зачем-то придумали иметь null-значение для всех value-типов — никакие Maybe не решают. Они ее лишь усугубляют — вместо одного null, получается три: null, Nothing и Maybe(null).
От всяких LINQ и extension-методов плюсы наоборот видны хорошо. Но их можно делать и поверх object-ов.
От всяких LINQ и extension-методов плюсы наоборот видны хорошо. Но их можно делать и поверх object-ов.
0
При условии, что:
ваш первый тезис становится неверным.
- null запрещен (одним из вышеперечисленных способов) и
- выбранная реализация Maybe не разрешает Maybe(null) (или приравнивает его Nothing),
ваш первый тезис становится неверным.
+1
Я не вижу способа запретить null совсем — он все равно будет прилетать из библиотек, будет в полях пока их не инициализировали (или забыли). Любые штатные средства — XmlSerializer или какой-нибудь EF не поймут Maybe, и там тоже придется как-то оборачивать null туда-сюда. Null часто нужен в алгоритмах как начальное или промежуточное состояние какой-нибудь переменной.
В результате Maybe принесет больше разброда и WTF-а, нежели пользы.
Думаю прагматичным подходом будет договориться что может возвращать null, а что — нет. И как-то с этим жить — тот же LINQ-стайл только поверх object-а, например, может быть в тему.
В результате Maybe принесет больше разброда и WTF-а, нежели пользы.
Думаю прагматичным подходом будет договориться что может возвращать null, а что — нет. И как-то с этим жить — тот же LINQ-стайл только поверх object-а, например, может быть в тему.
0
Вполне могу согласиться с таким подходом, лишь бы команда его принимала и использовала единоообразно.
Хотя, при желании, вызовы библиотек прекрасно оборачиваются ToMaybe, об использовании неинициализированных переменных и полей предупреждает компилятор или решарпер, а в алгоритмах вполне можно как обходиться без null, так и «разрешить» его локальное использование особым способом (зависящим от того, каким методом null был запрещен; например, специальным комментарием).
Хотя, при желании, вызовы библиотек прекрасно оборачиваются ToMaybe, об использовании неинициализированных переменных и полей предупреждает компилятор или решарпер, а в алгоритмах вполне можно как обходиться без null, так и «разрешить» его локальное использование особым способом (зависящим от того, каким методом null был запрещен; например, специальным комментарием).
0
Я-то по названию решил, что новая статья про неявную динамическую типизацию в шарпе в некоторых случаях, а получилась обычная пропаганда монад…
0
Вместо Get и Find, я бы предложил Get и TryToGet аналогично Parse :-)
0
Only those users with full accounts are able to leave comments. Log in, please.
Усиливаем контроль типов: где в типичном C#-проекте присутствует непрошеный элемент слабой типизации?