Пользователь
0,0
рейтинг
1 ноября 2013 в 01:34

Разработка → Усиливаем контроль типов: где в типичном C#-проекте присутствует непрошеный элемент слабой типизации?

C#*, .NET*

Проблема


Мы привыкли говорить о языках вроде C# как строго и статически типизированных. Это, конечно, правда, и во многих случаях тип, указываемый нами для некоторой языковой сущности хорошо выражает наше представление о ее типе. Но есть широко распространенные примеры, когда мы по привычке («и все так делают») миримся с не совсем верным выражением «желаемого типа» в «объявленном типе». Самый яркий — ссылочные типы, безальтернативно оснащенные значением «null».
В моем текущем проекте за год активной разработки не было ни одного NullReferenceException. Могу не без оснований полагать, что это следствие применения описанных ниже техник.

Рассмотрим фрагмент кода:

public interface IUserRepo 
{
	User Get(int id);
	User Find(int id);
}

Этот интерфейс требует дополнительного комментария: «Get возвращает всегда не null, но кидает Exception в случае ненахождения объекта; а Find, не найдя, возвращает null». «Желаемые», подразумеваемые автором типы возврата у этих методов разные: «Обязательно User» и «Может быть, User». А «объявленный» тип — один и тот же. Если язык не заставляет нас явно выражать эту разницу, то это не означает, что мы не можем и не должны делать это по собственной инициативе.

Решение


В функциональных языках, например, в F#, существует стандартный тип FSharpOption<T>, который как раз и представляет для любого типа контейнер, в котором может либо быть одно значение T, либо отсутствовать. Рассмотрим, какие возможности хотелось бы иметь от такого типа, чтобы им было удобно пользоваться, в том числе приверженцами разных стилей кодирования с разной степенью знакомства с функциональными языками.
С учетом этого гипотетического типа можно переписать наш репозиторий в таком виде:

public interface IUserRepo 
{
	User Get(int id);
	Maybe<User> Find(int id);
}

Сразу оговоримся, что первый метод все еще может вернуть null. Простого способа запретить это на уровне языка — нет. Однако, можно это сделать хотя бы на уровне соглашения в команде разработки. Успех такого начинания зависит от людей; в моем проекте такое соглашение принято и успешно соблюдается.
Конечно, можно пойти дальше и встроить в процесс сборки проверки на наличие ключевого слова null в исходном коде (с оговоренными исключениями из этого правила). Но в этом пока не было потребности, хватает просто внутренней дисциплины.
А вообще можно пойти и еще дальше, например, принудительно внедрить во все подходящие методы Contract.Ensure(Contract.Result<T>() != null) через какое-нибудь AOP-решение, например, PostSharp, в таком случае даже члены команды с низкой дисциплиной не смогут вернуть злосчастный null.

В новой версии интерфейса явно декларируется, что Find может и не найти объект, и в этом случае вернет значение Maybe<User>.Nothing. В этом случае никто не сможет по забывчивости не проверить результат на null. Пофантазируем далее об использовании такого репозитория:

// забывчивый разработчик забыл проверить на null
var user = repo.Find(userId); // возвращает теперь не User, а Maybe<User>
var userName = user.Name; // не компилируется, у Maybe нет Name

var maybeUser = repo.Find(userId); // зато код ниже компилируется,
string userName;
if (maybeUser.HasValue) // таким образом нас заставили НЕ забыть проверить на наличие объекта
{
	var user = maybeUser.Value;
	userName = user.Name;
}
else 
	userName = "unknown";

Этот код аналогичен тому, что мы бы написали с проверкой null, просто условие в if выглядит несколько иначе. Однако, постоянное повторение подобных проверок, во-первых, захламляет код, делая суть его операций менее явно заметной, во-вторых, утомляет разработчика. Поэтому было бы крайне удобно иметь для большинства стандартных операций готовые методы. Вот предыдущий код в fluent-стиле:

string userName = repo.Find(userId).Select(u => u.Name).OrElse("unknown");

Для тех же, кому близки функциональные языки и do-нотация, может быть поддержан совсем «функциональный» стиль:

string userName = (from user in repo.Find(userId) select user.Name).OrElse("unknown");

Или, пример посложнее:

(
 from roleAProfile in provider.FindProfile(userId, type: "A")
 from roleBProfile in provider.FindProfile(userId, type: "B")
 from roleCProfile in provider.FindProfile(userId, type: "C")
 where roleAProfile.IsActive() && roleCProfile.IsPremium()
 let user = repo.GetUser(userId)
 select user
).Do(HonorAsActiveUser);

с его императивным эквивалентом:

var maybeProfileA = provider.FindProfile(userId, type: "A");
if (maybeProfileA.HasValue)
{
	var profileA = maybeProfileA.Value;
	var maybeProfileB = provider.FindProfile(userId, type: "B");
	if (maybeProfileB.HasValue)
	{
		var profileB = maybeProfileB.Value;
		var maybeProfileC = provider.FindProfile(userId, type: "C");
		if (maybeProfileC.HasValue)
		{
			var profileC = maybeProfileC.Value;
			if (profileA.IsActive() && profileC.IsPremium())
			{
				var user = repo.GetUser(userId);
				HonorAsActiveUser(user);
			}
		}
	}
}

Также требуется интеграция Maybe<T> с его достаточно близким родственником — IEnumerable<T>, как минимум в таком виде:

var admin = users.MaybeFirst(u => u.IsAdmin); // вместо FirstOrDefault(u => u.IsAdmin);
Console.WriteLine("Admin is {0}", admin.Select(a => a.Name).OrElse("not found"));

Из приведенных выше «мечтаний» ясно, что хочется иметь в типе Maybe
  • доступ к информации о наличии значения
  • и к самому значению, если оно доступно
  • набор удобных методов (или методов-расширений) для потокового стиля вызовов
  • поддержка синтаксиса LINQ-выражений
  • интеграция с IEnumerable<T> и другими компонентами, при работе с которыми часто возникают ситуации отсутствия значения


Рассмотрим, какие решения может предложить нам Nuget для быстрого включения в проект и сравним их по приведенным выше критериям:

Название пакета Nuget и тип типа HasValue Value FluentAPI Поддержка LINQ Интеграция с IEnumerable Примечания и исходный код
Option, class есть нет, только pattern-matching минимальное нет нет github.com/tejacques/Option
Strilanc.Value.May, struct есть нет, только pattern-matching богатое есть есть Принимает null как допустимое значение в May
github.com/Strilanc/May
Options, struct есть есть среднее есть есть Также предлагается тип Either
github.com/davidsidlinger/options
NeverNull, class есть есть среднее нет нет github.com/Bomret/NeverNull
Functional.Maybe, struct есть есть богатое есть есть github.com/AndreyTsvetkov/Functional.Maybe
Maybe, нет типа минимальное нет методы расширения работают с обычным null
github.com/hazzik/Maybe
upd: а вот скринкаст от mezastel с аналогичным подходом и подробным объяснением: www.techdays.ru/videos/4448.html
WeeGems.Options, struct есть есть минимальное нет нет Также есть другие функциональные полезности: мемоизация, частичное применение функций
bitbucket.org/MattDavey/weegems

Так сложилось, что у меня в проекте вырос свой пакет, он есть среди вышеперечисленных.

Из этой таблицы видно, что самое «легкое», минимально инвазивное решение — это Maybe от hazzik, которое не требует никак менять API, а просто добавляет пару методов-расширений, позволяющих избавиться от одинаковых if-ов. Но, увы, никак не защищает забывчивого программиста от получения NullReferenceException.

Самые богатые пакеты — Strilanc.Value.Maybe (тут автор объясняет, в частности, почему он решил что (null).ToMaybe() не то же самое, что Maybe.Nothing), Functional.Maybe, Options.

Выбирайте на вкус. А вообще, хочется, конечно, стандартного решения от Microsoft, а еще функциональных типов в C#, кортежей и т.п :). Поживем — увидим.

UPD: товарищ aikixd написал оппонирующую статью.
Андрей Цветков @AndreyTS
карма
19,4
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (63)

  • +3
    Еще есть www.nuget.org/packages/Monads/ от sergun
  • 0
    Спасибо за ссылки на библиотеки. У меня есть несколько дежурных экстеншн методов проверки на null, но зачем использовать велосипед, попробую вашу библиотеку :) Maybe — хорошее и выразительное название для функции.
    • 0
      Пожалуйста :) Исходные коды его на githube, если в процессе использования родятся какие-то еще общеполезные расширения — пушьте, включим в новые версии пакета.
  • +2
    > первый метод все еще может вернуть null. Простого способа запретить это на уровне языка — нет.

    Есть. И вы сами же упомянули контракты. Тем более что это стандартная фича.
    • 0
      Ну, я имею в виду так, чтобы глобально, например, для целого проекта запретить. На уровне метода — да, контракты. Ну а на уровне проекта — тоже контракты, но с AOP, я об этом написал ниже :)
      • 0
        А как это может выглядеть на уровне проекта (особенно не трогая код конкретных интерфейсов)? Что-то слабо представляю. Все равно как-то придется отмечать методы, а это суть — контракты (вопрос реализации).
        • 0
          Почему же, если на уровне проекта мы заявляем: «методы никогда не возвращают null», то, соответственно, нужно внедрить в код каждого метода, возвращающего ссылочный тип, вышеуказанный контракт. Это работа для AOP.

          При этом подразумевается, что повсеместно (где необходимо) передается/возвращается Maybe. Точки сочленения с внешним миром оборачиваются, если необходимо, тоже в Maybe. И — вуаля, «ошибка на миллион долларов» исправлена :)
          • 0
            Ну по сути получается обратный подход по сравнению с контрактами. Если там мы считаем что по умолчанию все могут вернуть null и ограничиваем нужные без null, то тут — по умолчанию все не могут возвращать null, но выделяем кто может.
  • +12
    Мы с этой же целью используем стандартные решарперовские атрибуты [CanBeNull], [NotNull], а статический анализатор подскажет, что нужна проверка на нуль еще во время написания кода. И не нужны никакие соглашения в команде.
    • 0
      Добавлю, что можно сделать принудительную элевацию (в т.ч. в масштабах solution'а) уровня проверок на PossibleNullReferenceException в R# с Warning до Error. Таким образом даже сборка станет невозможной, пока в коде присутствуют подобные скользкие места.
      • 0
        Мне кажется, что ошибки, обнаруженные 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
                  Ну нравится/не нравится Решарпер — это скорее вопрос относящийся к IDE, command line tools не требует наличия решарпера у всех разработчиков, плюс к этому он бесплатный, насколько я знаю(надо уточнить у коллег)
  • +2
    Моё правило в разработке — методы поиска объектов не возвращают null, а генерируют исключение при отсутствии объекта.
    Если факт отсутствия нужного объекта допускается бизнес-логикой приложения, то метод поиска пишется в bool TryGet(T key, out value) стиле — в этом случае выражение if получается автоматически.
    Т.е. работаем по принципу Dictionary — можно попробовать нагло получить значение, если уверены в том, что оно обязано быть или попробовать получить с проверкой на наличие. При этом сам метод очевиден, понятен, не требует оборачивания в дополнительные классы.
    NullReference попадаются, но практически всегда это в данных, получаемых из сторонних компонентов или через ServiceReference.
    • +1
      Весьма спорно, при поиске кидать исключение. Поиск подразумевает возможность получения пустого списка, а вот операция получения конкретного объекта — уже как раз может кидать исключение. Тоесть все методы в наименовании которых есть Search* или Find* — никогда не кинут исключение о том что ничего не нашли, а вот Get* — кинет. Но это имхо)
      • 0
        Уже после публикации сообщения заметил, что написал «поиск» вместо «получения». Понятно, что методы поиска, которые могут вернуть несколько объектов возвращают IEnumerable. На IEnumerable у меня есть свой Extension.

        	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).
        • 0
          Именно. Методы должны делать то, что следует из их названия — это путь к предсказуемости API.
          Сколько я намучился с кодом предыдущих разработчиков, которые писали ядро системы. Они написали его так, что при явном поиске конкретного объекта по индексатору через 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.
            • 0
              Согласен, неудачно выбранный термин. «Получение объектов по индексатору».
              Хотя никто не мешает написать даже так:

              		public IEnumerable<object> this[int key]
              		{
              			get
              			{
              


              Что позволяет особо одаренным программистам реализовывать через индексаторы любую логику, которая придёт в голову.
              • 0
                Пример такой реализации — HttpContext ;)
            • 0
              otherwise, the default value for type T.

              Удивлён. Поражён. Перепутал с First, который генерит исключение, если не нашёл ничего по заданному критерию, т.е. фактически выполняет поиск. Я его (First) всегда и использую для поиска…
              • 0
                Ну First подразумевает, что в коллекции есть хоть 1 элемент) А вот FirstOrDefault — нет)
        • 0
          Уж если апелировать к MS то в EF Find возвращает null: msdn.microsoft.com/en-us/library/gg696418(v=vs.113).aspx

          При поиске(как Search так и Find) не гарантируется, что будет что-то найдено. При явном запросе — Get — вы априори считаете что такой объект уже есть, и отсутствие такового — исключение.
  • +5
  • +1
    кортежей и т.п

    А это не оно разве?
    • 0
      Конечно, это оно, да и Func<T, ...> тоже есть, как функциональный тип.

      Но и то, и другое можно поудобнее сделать.

      Например, поддержать анонимный функциональный тип вида (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 лучше не использовать вообще, а завести мелкую структуру или класс.
        • 0
          Вот эти Item1… ItemN IMHO большой минус для Tuple. Без доки или изучения кода вызываемого метода фиг поймешь что в них.
          • 0
            Потому и пользуюсь Tuple в случаях крайней нехватки времени или для совсем уж маловажных глубоко спрятанных private кусков.
        • +1
          Только и разницы, что вместо одной строчки две. Никто не умрет, но суть немного затуманивается. Чем такого меньше, тем лучше.
  • 0
    Мне кажется удобнее использовать нультипы. Во всех враперах можно все равно запросить значние если его нет, и это ситуация, также порождает ексцепшен. Т.е. синтаксически ничем не отличается от проверки на null.
    • 0
      Нультипы — это Nullable? Их нельзя использовать с ссылочными типами, т.е. к текущей проблеме это не имеет отношения — Int или структура всё равно не может быть null.
      • 0
        Нет, это тип твоего объекта но содержащий спец информацию, отладочную или пишуший лог. т.е. FindUser будет всегда находить объект, если юзера нет, то вернется спец объект типа User. И проверять нужно не на Null а на, например, User.Empty
        Вот например string.Epmty, Guid.Empty это оно и есть.
        • 0
          EventArgs.Empty и т.д. Пользовался, но не знал что это нультипами называется. Мне кажется что это не всегда применимо — типы бывают достаточно тяжелыми и жестко связанными с окружением. Ну и плюс никак не гарантируется компилятором, что некто будет этот нультип не то, что использовать, а даже знать о нём. Как проверял на null, так и будет проверять.
    • +1
      Я так понял, что вы имеете в виду шаблон Null Object. Это вполне хорошее решение, когда оно на своем месте. Однако, на мой взгляд, на роль универсального избавителя от null оно не подходит. Этот шаблон относится к моделированию предметной области: если у нас есть семейство объектов (например, разные типы Начислений в финансовом приложении), и для них можно (для унификации обработки) говорить также об Отсутствующем Начислении (если это имеет смысл с точки зрения той модели, в которой мыслят пользователи и проектировщики приложения), то это хорошее место для применения Null Object.

      Однако, если у меня есть коллекция 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 или где нельзя поставить ??
    • –2
      По личному опыту поиска багов в чужом коде, могу сказать, что 90% NullReferenceException во время исполнения — результаты .First() и .Single().
      • +1
        Странный у Вас опыт — результатом First или Single не может быть null, разве что null был элементом массива, но тогда это не беда First или Single, а беда логики, анализирующей результаты поиска.
        • +1
          Точно. Недопроснулся. InvalidOperationException
          • +1
            Это совсем не то же самое, что NullReference, и за эту ошибку нужно точно бить по рукам — разработчик не понимает бизнес-логики своего приложения.
            Этими методами нужно пользоваться только тогда, когда уверен в наличии нужного элемента и когда его отсутствие свидетельствует только о каких-то серьезных проблемах, при которых продолжать дальнейшую работу с приложением не только бессмысленно, но и чревато нарушениями каких-то бизнес-процессов, поэтому все First желательно оборачивать в try...catch с генерацией осмысленной ошибки.
            • +1
              Либо оставить и не париться, если это очевидное место кода, например — вызов 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 — воткнул в пару мест по коду. Кажется ничего так получается — вполне читабельно и удобно.
            • 0
              я о том же
            • 0
              >все First желательно оборачивать в try...catch с генерацией осмысленной ошибки.
              А какую можно сгенерировать ошибку, более осмысленную чем InvalidOperation со стектрейсом, показывающим прямо на вызов First/Single?
              • +1
                Например «Товара с кодом '100' нет в прайсе. Возможно он был удалён или ....». Не знаю каков у Вас опыт сопровождения продуктов, но InvalidOperation со стектрейсом для конечного пользователя — не просто китайская грамота — это как красная тряпка для быка. А InvalidOperation со стектрейсом аккуратненько уходит в лог в виде InnerException.
              • 0
                Да, и в одном методе может быть не один Linq.First, как разработчику узнать чего именно не оказалось?
                • 0
                  По номеру строки, например.
                  • 0
                    В Release версии? Ну-ну — удачи.
                    • 0
                      Если приложение не с секретными какими технологиями, то я не вижу причин не класть pdb-шки в релизную версию.
                      • 0
                        У серьезного приложения котчами должны быть охвачены основные уязвимые места, особенно если конечный пользователь после этого сможет самостоятельно разобраться с её причиной или хотя бы понять что не так, вместо чтения ненужных ему стеков.
                        • +1
                          Идея сделать ошибки user-frendly — она вроде как правильная по-идее. Но на практике для юзера все равно сколько там страшных букв будет, а для программиста или админа — настоящая причина ошибки лучше, чем то, что там другие программисты подумали могло случиться.

                          Так что я в серьезных приложениях стараюсь делать только top-level перехват исключений для логирования, и общей устойчивости.

                          Кстати советую убедиться что у тебя логируются innerException-ы. Многие логгеры без дополнительных приседаний этого не делают, log4net, например.
  • 0
    Если так хочется строгости и не смущает 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-ов.
    • +1
      При условии, что:
      • null запрещен (одним из вышеперечисленных способов) и
      • выбранная реализация Maybe не разрешает Maybe(null) (или приравнивает его Nothing),

      ваш первый тезис становится неверным.
      • 0
        Я не вижу способа запретить null совсем — он все равно будет прилетать из библиотек, будет в полях пока их не инициализировали (или забыли). Любые штатные средства — XmlSerializer или какой-нибудь EF не поймут Maybe, и там тоже придется как-то оборачивать null туда-сюда. Null часто нужен в алгоритмах как начальное или промежуточное состояние какой-нибудь переменной.

        В результате Maybe принесет больше разброда и WTF-а, нежели пользы.

        Думаю прагматичным подходом будет договориться что может возвращать null, а что — нет. И как-то с этим жить — тот же LINQ-стайл только поверх object-а, например, может быть в тему.
        • 0
          Вполне могу согласиться с таким подходом, лишь бы команда его принимала и использовала единоообразно.

          Хотя, при желании, вызовы библиотек прекрасно оборачиваются ToMaybe, об использовании неинициализированных переменных и полей предупреждает компилятор или решарпер, а в алгоритмах вполне можно как обходиться без null, так и «разрешить» его локальное использование особым способом (зависящим от того, каким методом null был запрещен; например, специальным комментарием).
  • 0
    Я-то по названию решил, что новая статья про неявную динамическую типизацию в шарпе в некоторых случаях, а получилась обычная пропаганда монад…
  • 0
    Вместо Get и Find, я бы предложил Get и TryToGet аналогично Parse :-)

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