<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Функциональный C#</title>
    <description>Серия статей по программированию на C# в функциональном стиле с объяснениям основных концепций парадигмы</description>
    <link>https://tproger.ru/tag/functional-sharp</link>
    <atom:link href="https://tproger.ru/tag/functional-sharp/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Thu, 24 Sep 2026 22:55:08 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Функциональный C#</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Дорожная карта: из C# Middle разработчика в Senior C#</title>
      <link>https://tproger.ru/articles/dorozhnaya-karta-iz-c-middle-razrabotchika-v-senior-c-erid-ljn8kvpnf</link>
      <comments>https://tproger.ru/articles/dorozhnaya-karta-iz-c-middle-razrabotchika-v-senior-c-erid-ljn8kvpnf?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Екатерина Соколова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/dorozhnaya-karta-iz-c-middle-razrabotchika-v-senior-c-erid-ljn8kvpnf</guid>
      <description><![CDATA[<p>Как вырасти из Middle C# программиста в уверенного сеньора с нужным багажом знаний и навыков? Составили роадмап для будущего Senior C# developer.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/dorozhnaya-karta-iz-c-middle-razrabotchika-v-senior-c-erid-ljn8kvpnf">Дорожная карта: из C# Middle разработчика в Senior C#</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 18 Aug 2023 07:10:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Путь от Middle C# до сеньора похож на переходный возраст: нужно становиться самостоятельным и брать на себя всё больше ответственности. Для тех, кто готов шагнуть во «взрослый» карьерный этап и стать Senior C# разработчиком, мы совместно с экспертами <a href="https://ozon.ru/t/weAY2L8">курсов для Middle-специалистов Route 256 от Ozon Tech</a> сделали роадмап. Держите дорожную карту перехода C# Middle-разработчика в статус Senior.</p><ul><li><a href="https://tproger.ru/#part1">Чем Senior отличается от Middle</a></li><li><a href="https://tproger.ru/#part2">Что необходимо, чтобы стать Senior C# разработчиком</a>Технические навыкиПлатформа .NET и базы данныхПаттерны проектированияМикросервисыКонтейнеризация и облачные решенияCI/CD и тестированиеВидение проектаЛичностные качестваЛюбовь к «чистоте» кодаКоммуникативные навыкиНавыки ментораРабота с сообществом</li><li><a href="https://tproger.ru/#part15">Полезные ресурсы</a></li><li><a href="https://tproger.ru/#part16">Вместо эпилога</a></li></ul><h2>Чем Senior отличается от Middle</h2><p>Middle C# developer — это разработчик, который хорошо освоил язык и платформу .NET, понимает процессы разработки на проекте, умеет пользоваться всеми необходимыми инструментами и может справиться с повседневными задачами практически без помощи старших коллег.</p><p>У Senior C# разработчика больше опыта, экспертизы и потому больше ответственности. Он глубоко понимает архитектуру, устройство фреймворков и библиотек, видит технические риски и знает, как их предотвратить. А ещё сеньор умеет организовать работу над проектом: сформулировать и раздать задачи, следить за разработкой и сроками и презентовать результат. Как вы уже поняли, сеньор должен уметь не только программировать, но и управлять проектами и людьми.</p><h2>Что необходимо, чтобы стать Senior C# developer</h2><p>Чтобы начать путь от мидла к сеньору, необходимо развиваться в двух направлениях: прокачивать технические навыки и личностные качества. Поэтому мы разделили дорожную карту на две части и структурировали и подробно раскрыли оба направления.</p><h3>Технические навыки</h3><h4>Платформа .NET и базы данных</h4><p>То, что C# Middle разработчик воспринимает как функциональность языка C#, сеньор понимает глубже — как возможности для построения сложных систем.</p><p>Старший специалист в деталях знает, как работает многопоточность, асинхронность и чем они отличаются друг от друга. А ещё — как применять примитивы синхронизации потоков (lock, Monitor, Mutex, Semaphore и SemaphoreSlim), зачем нужен ThreadPool, какие операции лучше выполнять синхронно, а какие асинхронно (CPU bound, IO bound), что такое контекст синхронизации, CancellationToken и в каких случаях может пригодится ValueTask.</p><p>Сеньор точно знает, какие структуры данных лучше использовать и почему, как посчитать сложность алгоритма и измерить эффективность выбранного подхода. Для этого на уровне мидл необходимо уверенно освоить способ Big O Notation, алгоритмы сортировки и поиска элементов в коллекции, а также иметь чёткое представление, в чём разница между Array и List, как устроен Dictionary и почему он работает быстрее чем List, что такое коллизии в Dictionary и как он взаимодействует с ними.</p><p>Не менее важно для Senior C# developer уметь работать с базами данных. Старший специалист не просто знает, как писать запросы и в чём разница между SQL и NoSQL. Он может обоснованно выбрать ту или иную базу данных для решения конкретной задачи, построить план запросов и правильно их прочитать.</p><p>Сеньору следует знать, что такое уровни изоляции транзакций и какие проблемы они решают, как нормализовать данные и какие индексы необходимо использовать для ускорения выборки данных. А ещё иметь понимание, что такое ACID и BASE, теорема CAP, шардинг и вертикальное масштабирование.</p><h4>Паттерны проектирования</h4><p>Старшему специалисту пригодится умение работать с паттернами проектирования. У сеньора должен быть навык выявления необходимого и достаточного паттерна проектирования для конкретной ситуации, чтобы не допустить «кода ради кода».</p><p>Senior C# разработчику необходимо понимать, как следовать принципам SOLID, DRY, KISS, YAGNI и GRASP. А также знать, как и когда применять архитектурные паттерны, например, SOA, Microservices, MVC, MVVM, Client-Server, Broker, и контролировать использование антипаттернов, таких как Spaghetti code, Accidental complexity, Lava flow, God Object, Dependency hell.</p><p>Чтобы научиться обдуманно подходить к использованию паттернов, рекомендуем ресурс <a href="https://www.dofactory.com/net/design-patterns">dotfactory</a> — здесь вы найдёте подборку паттернов проектирования на C# с подробным описанием и примерами реализации.</p><h4>Микросервисы</h4><p>Senior C# разработчику нужно знать, что такое микросервисы и уметь их строить. Как проектировать сервис, как делить его на слои, чем отличается многослойная архитектура от чистой, что такое low coupling и high cohesion, как обеспечивать консистентность данных — всё это не должно вызывать вопросов.</p><p>Чтобы прокачаться до уровня сеньора, необходимо подтянуть следующие темы:</p><ul><li>межсервисное взаимодействие: HTTP call, Message Broker (AMQP) и gRPC;</li><li>подходы микросервисных решений: CQRS, Event Sourcing, Circuit Breaker, Saga pattern, Sidecar, Database per Service, Shared Database per Service, API Gateway;</li><li>безопасность микросервисов: Basic Authentication, JWT, ASP.NET Core Identity, OpenID Connect или OAuth.</li></ul><p>А также разобраться с RESTful-сервисами, stateless и уровнями зрелости дизайна REST API.</p><p>Чтобы научиться правильно строить микросервисную архитектуру, рекомендуем прочитать книги Криса Ричардсона <a href="https://www.labirint.ru/books/707677/">«Микросервисы. Паттерны разработки и факторинга»</a> и Сэма Ньюмена <a href="https://www.labirint.ru/books/937994/">«Создание микросервисов»</a>.</p><h4>Контейнеризация и логирование</h4><p>Сегодня сложно представить проект, в котором не используются контейнеризация и методы оркестрации контейнеров. Поэтому сеньору важно знать, что такое Docker, зачем использовать Kubernetes, и разбираться в технологиях Docker swarm, Nomad, Consul.</p><p>Немаловажной частью работы над сложными системами является мониторинг и алертинг компонентов. В этом сеньору пригодится навык настройки логирования таким образом, чтобы оно не перегружало инфраструктуру ненужной информацией. Здесь помогут навыки работы с Grafana, ELK-стеком, Prometheus, OpenTelemetry.</p><h4>CI/CD и тестирование</h4><p>Ещё сеньору пригодятся знания CI/CD-процессов и умение их использовать, чтобы выпускать более качественный продукт. Сеньор должен понимать, какие критерии проверки необходимо включить в CI/CD-процесс, какие использовать тесты — unit или integration, и какой процент покрытия тестами удовлетворительный для конкретного проекта.</p><p>Сеньор может успешно выстроить CI/CD-процесс на проекте. Он не забывает об автоматизации билда, Self-testing сборке, о том, что каждый commit в master должен быть сбилженным, билды — быстрыми, тестирование на pre-prod среде — максимально приближенное к production, а деплой — автоматизированным.</p><p>Старший специалист уверен в том, что находить дефекты в продукте — прежде всего задача разработчиков, а не тестировщиков. Именно разработчики отвечают за качество и функционал кода. Чтобы подтянуть свои навыки в тестировании, необходимо изучить библиотеки MSTest, xUnit, NUnit, Moq, NSubstitute, а также разобраться в разнице между Dummy, Fake, Stubs, Spies и Mock объектами.</p><h4>Видение проекта</h4><p>Старший разработчик C# понимает, как устроен проект в целом, и старается принести своими решениями максимальную выгоду бизнесу. Он видит проблемы и осознаёт, как применение того или иного подхода повлияет на проект и его компоненты. Хороший Senior C# на уровне рефлексов знает, какое решение правильное, а какое нет, — опыта за плечами хватает.</p><p>Ещё один необходимый скилл для сеньора — находить точки интеграции компонентов с другими командами, системами. Зачастую некорректная интеграция может критически сказаться на разработке и принести много проблем и рисков.</p><p>Также Senior C# программисту важно понимать изолированный контекст компонентов системы, чтобы они не становились большими и перегруженными. И в этом помогут знания DDD-подхода — рекомендуем изучить.</p><h3>Личностные качества</h3><h4>Любовь к «чистоте» кода</h4><p>Читаемый код с понятной архитектурой и логикой, который без труда могут разобрать другие разработчики — вот, к какому коду следует стремиться Middle C# разработчику, чтобы вырасти до старшего специалиста.</p><p>При этом на уровне старшего программиста нужно не только заботиться о чистоте и простоте своего кода, но и учить этому своих младших коллег. По мере возможности сеньору стоит погружаться технически не только в свои задачи, но и в задачи коллег и тщательно проводить код-ревью.</p><h4>Коммуникативные навыки</h4><p>Разработчик сеньор часто ведёт проекты самостоятельно. Он умеет обрабатывать ТЗ, разбивать его на мелкие задачи и выстраивать план работ. Сеньор знает, кому из специалистов и что делегировать, и какие задать вопросы коллегам для прояснения ТЗ, чтобы задачи были выполнены корректно и в срок.</p><p>А ещё Senior C# — хороший командный игрок. Он правильно выстраивает коммуникацию, организовывает процесс работы и знает, как избегать конфликтных ситуаций. Научиться этому поможет книга Дж. Ханк Рейнвотера <a href="https://www.labirint.ru/books/497448/">«Как пасти котов. Наставление для программистов, руководящих другими программистами»</a>.</p><h4>Навыки ментора</h4><p>Одна из задач сеньора — помогать младшим специалистам развиваться и расти. У сеньора достаточно опыта за плечами и набитых шишек, поэтому он точно знает, как достичь цели или решить задачу быстрее и с меньшими потерями.</p><p>Менторский опыт важно приобретать уже на уровне мидл. Станьте наставником для своих младших коллег или присоединитесь к сообществу IT-менторов. Это поможет прокачать лидерские, преподавательские навыки, эмпатию и эмоциональный интеллект. Нелишним будет прочесть книги Гарвардской школы бизнеса <a href="https://www.labirint.ru/books/363439/">«Делай как я! Руководитель как играющий тренер»</a> и Ларри Кинга <a href="https://www.labirint.ru/reviews/goods/476482/">«Как разговаривать с кем угодно, когда угодно и где угодно»</a>.</p><h4>Работа с сообществом</h4><p>Не ограничивайтесь только рабочими задачами и находите время делиться опытом и знаниями с коллегами. Вклад в комьюнити шарпистов может быть хорошим толчком к развитию C# Middle разработчика.</p><p>Устраивайте семинары в своей команде, пишите полезные статьи на Tproger, Habr или англоязычных Medium или Hackernoon, выступайте с докладами, — например, ребята из dotnet.ru регулярно проводят оффлайн- и онлайн-встречи и всегда открыты к сотрудничеству.</p><p>Публикуйте свои pet-проекты на Github или участвуйте в open-source проектах на площадке. Такой опыт даст возможность поработать со специалистами из разных стран и областей разработки. И, возможно, именно вам удастся внести свой вклад в следующий популярный фреймворк или новую технологию.</p><h2>Полезные ресурсы</h2><p>Чтобы путь от Middle уровня C# до старшего разработчика был максимально коротким, следует запастись полезными ресурсами.</p><p>Кроме тех книг, которые мы рекомендовали по ходу дорожной карты, стоит прочесть также:</p><p>Также советуем подписаться на YouTube-канал сообщества разработчиков .NET <a href="https://www.youtube.com/@DotNetRu">DotNetRu</a> и авторский канал <a href="https://www.youtube.com/@nickchapsas">Nick Chapsas</a>, на котором регулярно выходят интересные разборы проблем C# разработчиков и обсуждаются новые возможности .NET.</p><p>А ещё рекомендуем читать новостной ресурс <a href="https://www.infoq.com/dotnet/">InfoQ</a> о разработке программного обеспечения и регулярно заглядывать на сайт глобального онлайн-сообщества шарпистов <a href="https://www.c-sharpcorner.com/">C-Sharpcorner</a>, где разработчики обмениваются знаниями и опытом.</p><h2>Вместо эпилога</h2><p>Чтобы быстро освоить базис дорожной карты, рекомендуем пройти курс для Middle-специалистов Route 256 от Ozon Tech <a href="https://ozon.ru/t/weAY2L8">«Продвинутая разработка микросервисов на C#»</a>.</p><p>Программа рассчитана на разработчиков с опытом от 3 лет. Преподаватели и тьюторы — инженеры Ozon Tech. За два месяца вы сможете подтянуть скилы и освежить знания. На курсе вы научитесь создавать и настраивать микросервисы на ASP.NET Core, эффективно работать с асинхронным кодом, проектировать сложные распределенные системы, создавать REST и gRPC API и другое.</p><p><a href="https://ozon.ru/t/weAY2L8">Учиться вместе с Ozon Tech</a></p><p>Надеемся, статья оказалась для вас полезной. Остались вопросы? Задайте их в комментариях.<br />Реклама ООО «Озон технологии» LjN8KVpnf</p>]]></content:encoded>
    </item>
    <item>
      <title>Мнение: объектно-ориентированное программирование — катастрофа на триллион долларов</title>
      <link>https://tproger.ru/translations/oop-the-trillion-dollar-disaster</link>
      <comments>https://tproger.ru/translations/oop-the-trillion-dollar-disaster?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/oop-the-trillion-dollar-disaster</guid>
      <description><![CDATA[<p>Критика объектно-ориентированного программирования: автор называет его катастрофой на триллион долларов и сравнивает с функциональным подходом.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/oop-the-trillion-dollar-disaster">Мнение: объектно-ориентированное программирование — катастрофа на триллион долларов</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Объектно-ориентированное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 04 Sep 2019 13:08:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Илья Суздальницкий, senior full-stack-разработчик</p><p>Прим. ред. Мнение редакции может не совпадать с мнением автора оригинала.</p><p>По мнению многих, ООП является жемчужиной информатики. Идеальное решение для организации кода. Конец всем проблемам. Единственный верный способ написания программ. Дарован нам самим истинным Богом программирования.</p><p>Но это не так. Люди начинают уступать под тяжестью абстракций и сложного графа беспорядочно разделяемых изменяемых объектов. Драгоценное время и силы тратятся на размышления об абстракциях и шаблонах проектирования вместо решения реальных проблем. Многие критикуют объектно-ориентированное программирование, в том числе очень выдающиеся разработчики программного обеспечения. Даже сам изобретатель этой парадигмы известен своей критикой современного ООП.</p><p>Цель каждого разработчика — написать надёжное программное обеспечение. Ничто другое не имеет значения, если код глючит. При этом самый лучший подход к написанию надёжного кода — простота. Следовательно, первая и главная цель разработчиков должна заключаться в уменьшении сложности кода.</p><h2>Дисклеймер</h2><p>Буду честен, я не ярый поклонник ООП. И конечно, эта статья будет предвзятой. Однако у меня есть веские причины не любить этот подход.</p><p>Критика ООП — очень деликатная тема. Вероятно, она заденет многих читателей. Тем не менее, я делаю то, что считаю правильным. Цель — не обидеть, а повысить осведомлённость о проблемах ООП. Здесь нет критики ООП Алана Кея — он гений. Было бы замечательно, если бы эта парадигма была реализована так, как он это задумал. Я критикую современный подход Java/C#.</p><p>Думаю, что это неправильно — де-факто считать ООП стандартом для организации кода многими людьми, в том числе на очень высоких технических должностях. Также недопустимо, что многие основные языки не предлагают никаких альтернатив организации кода, кроме ООП.</p><p>Я перебарывал себя, работая над ООП-проектами. И я не имел ни малейшего понятия, почему я так напрягался. Может быть, я был недостаточно хорош? Может, мне нужно было выучить ещё несколько шаблонов проектирования? Так я думал, и в конце концов полностью выгорел.</p><p>Эта статья подводит итог моего первого десятилетнего пути из объектно-ориентированного в функциональное программирование. Как бы я ни старался, я больше не мог найти варианты использования для ООП. Я лично видел, как ООП-проекты терпят крах, потому что их становится слишком сложно обслуживать.</p><h2>В чём суть?</h2><p>Объектно-ориентированные программы предлагаются в качестве альтернативы правильным.</p><p>Объектно-ориентированное программирование было создано с одной целью — управлять сложностью процедурных кодовых баз. Другими словами, такой подход должен был улучшить организацию кода. Нет объективных и открытых доказательств того, что ООП лучше простого процедурного программирования.</p><p>Однако горькая правда в том, что ООП терпит неудачу в единственной задаче, для которой оно было предназначено. На бумаге это выглядит хорошо, но как только сложность приложения начинает увеличиваться, всё становится плохо. Вместо уменьшения сложности этот подход поощряет беспорядочное совместное использование изменяемого состояния и вносит дополнительную сложность своими многочисленными шаблонами проектирования. ООП делает общие практики, такие как рефакторинг и тестирование, неоправданно сложными.</p><p>Некоторые могут не согласиться со мной, но современное ООП в Java/C# никогда не было спроектировано должным образом. Оно никогда не было результатом работы хорошего исследовательского института (в отличие от Haskell/FP). Лямбда-исчисление предлагает полную теоретическую основу для функционального программирования. ООП не имеет ничего соответствующего этому. Использование ООП невинно в краткосрочной перспективе, особенно в начальных проектах. Но в долгосрочной перспективе это бомба замедленного действия, которая может взорваться, когда кодовая база станет достаточно большой. Проекты откладываются, сроки горят, разработчики перегорают, добавление новых функций становится практически невозможным. Организация помечает код как «легаси», а команда разработчиков планирует переписать его.</p><p>ООП не является естественным для человеческого мозга. Мыслительный процесс сосредоточен на том, чтобы «делать» вещи — гулять, разговаривать с другом, есть пиццу. Мозг развился, чтобы делать вещи, а не организовывать мир в сложные иерархии абстрактных объектов.</p><p>ООП-код является недетерминированным. В отличие от функционального программирования, нет гарантий в получении одинакового вывода при одинаковых входных данных. Это делает анализ программы очень сложным. В качестве упрощённого примера: вывод 2 + 2 или calculator.Add(2, 2) в основном равен четырём, но иногда он может становиться равным трём, пяти и даже 1004. Зависимости объекта Calculator могут изменить результат вычисления трудно уловимым, но основательным способом.</p><h2>Необходимость в стабильном фреймворке</h2><p>Хорошие программисты пишут хороший код, а плохие — плохой независимо от парадигмы программирования. Однако эта парадигма должна ограничивать плохих программистов от нанесения слишком большого ущерба. Конечно, вы не плохой программист, так как уже читаете эту статью и стараетесь учиться. Плохие никогда не успевают учиться, они просто нажимают кнопки на клавиатуре как сумасшедшие. Нравится вам это или нет, но вы будете работать с такими людьми. И, к сожалению, ООП не имеет достаточных ограничений, которые бы помешали плохим программистам нанести слишком большой ущерб.</p><p>Я не считаю себя плохим программистом. Но даже я не могу написать хороший код без сильного фреймворка, на котором можно было бы основать работу. Речь не идёт о софтверных фреймворках. Здесь подразумевается более общее словарное определение фреймворка — «необходимая несущая конструкция». Это фреймворки, которые регулируют более абстрактные вещи, такие как организация кода и уменьшение сложности кода. Хоть объектно-ориентированное и функциональное программирование являются парадигмами программирования, они также являются высокоуровневыми фреймворками.</p><h3>Ограничение выбора</h3><p>C++ — ужасный [объектно-ориентированный] язык… Ограничение вашего проекта до C означает, что люди не напортачат ни с какой идиотской «объектной моделью».</p><p>Линус Торвальдс широко известен своей открытой критикой C++ и ООП. Одна вещь, в которой он был на 100 % прав — это необходимость ограничения программистов в выборе. На самом деле, чем меньше у программистов выбора, тем более устойчивым становится их код. В приведённой выше цитате Торвальдс настоятельно рекомендует иметь хороший фреймворк, на котором будет основан ваш код.</p><p>Многим не нравятся ограничения скорости на дорогах, но они необходимы, чтобы не дать людям разбиться насмерть. Точно так же хорошая среда программирования должна обеспечивать механизмы, которые мешают делать глупости. Хорошая среда помогает писать надёжный код. Прежде всего, это должно помочь уменьшить сложность, предоставляя следующие вещи:</p><ul><li>модульность и возможность повторного использования;</li><li>правильная изоляция состояния;</li><li>высокое <a href="https://ru.wikipedia.org/wiki/%D0%9E%D1%82%D0%BD%D0%BE%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB/%D1%88%D1%83%D0%BC">отношение сигнал/шум</a>.</li></ul><p>ООП предоставляет разработчикам слишком много инструментов и вариантов, не налагая правильных ограничений. Несмотря на обещания ООП рассмотреть модульность и улучшить возможность повторного использования, оно не выполняет свои обещания (подробнее об этом позже). ООП-код поощряет использование разделяемого изменяемого состояния, которое может быть небезопасно от раза к разу. ООП обычно требует большого количества бойлерплейта (низкого отношения сигнал/шум).</p><h3>Функциональное программирование</h3><p>Что такое функциональное программирование (ФП)? Некоторые люди считают, что это очень сложная парадигма организации кода, которая применима только в академических кругах и не подходит для «реального мира». Функциональное программирование имеет прочную математическую основу и берёт начало в <a href="https://ru.wikipedia.org/wiki/Лямбда-исчисление">лямбда-исчислениях</a>. Большинство его идей возникли как ответ на слабые стороны более распространённых языков программирования. Функции являются основной абстракцией функционального программирования. При правильном использовании функции обеспечивают такой уровень модульности и возможности повторного использования кода, какой никогда не встречался в ООП. ФП даже имеет шаблоны проектирования, которые решают проблемы null-значений и обеспечивают превосходную обработку ошибок.</p><p>Одну вещь ФП делает действительно хорошо — помогает писать надёжное программное обеспечение. Потребность в отладчике почти полностью исчезает. Нет необходимости проверять весь код и смотреть переменные. Лично я не трогал отладчик в течение очень долгого времени. При этом, если вы знаете, как использовать функции, вы уже функциональный программист. Вам просто нужно узнать, как использовать их наилучшим образом.</p><p>Я не проповедую функциональное программирование, мне всё равно, какую парадигму вы используете при написании кода. Я пытаюсь рассказать о механизмах, которые предоставляет функциональное программирование для решения проблем, присущих ООП / императивному программированию.</p><h2>Мы всё не так поняли про ООП</h2><p>Мне очень жаль, что давным давно я ввёл термин «объекты», потому что они заставляют многих людей сосредоточиться на менее важной идее. Основная же идея — обмен сообщениями.</p><p>Erlang, вероятно, — <a href="https://stackoverflow.com/questions/3431509/is-erlang-object-oriented/3433808#3433808">единственный</a> популярный объектно-ориентированный язык, хоть и обычно не считается таковым. Конечно, Smalltalk — это тоже чистый ООП-язык, однако он не используется широко. И Smalltalk, и Erlang используют ООП так, как это было задумано его изобретателем Аланом Кеем.</p><h3>Обмен сообщениями</h3><p>Алан Кей ввёл термин «объектно-ориентированное программирование» в 1960-х годах. Он имел опыт работы в области биологии и пытался заставить компьютерные программы общаться так же, как живые клетки. Основная идея состояла в том, чтобы независимые программы (ячейки) общались, отправляя друг другу сообщения. Состояние независимых программ никогда бы не открылось внешней среде (инкапсуляция). Вот оно. ООП никогда не предназначался для наследования, полиморфизма, ключевого слова new и множества шаблонов проектирования.</p><h3>ООП в чистом виде</h3><p>Erlang — это ООП в чистом виде. В отличие от других популярных языков, он сосредоточен на основной идее ООП — обмен сообщениями. В Erlang объекты взаимодействуют, передавая между собой неизменяемые сообщения. Есть ли доказательства того, что неизменяемые сообщения лучше методов? Да, чёрт возьми! Erlang самый надёжный язык в мире. Он поддерживает большую часть мировой телекоммуникационной инфраструктуры, включая интернет. Некоторые из систем, написанных на Erlang, имеют надежность 99,9999999 % (вы правильно прочитали — девять девяток).</p><h2>Сложность кода</h2><p>Благодаря объектно-ориентированным языкам программирования, компьютерное программное обеспечение становится более многословным, менее читаемым, менее наглядным и более сложным для модификации и обслуживания.</p><p>Наиболее важным аспектом разработки ПО является снижение сложности кода. Ни одна из фич не имеет значения, если кодовую базу становится невозможно поддерживать. В этом случае даже стопроцентное тестовое покрытие ничего не стоит.</p><p>Что делает кодовую базу сложной? Есть много вещей, на которые следует обратить внимание. На мой взгляд, главными причинами являются: общее изменяемое состояние, ошибочные абстракции и низкое отношение сигнал/шум (бойлерплейт). Все они распространены в ООП.</p><h2>Проблемы состояния</h2><p>Состояние — это любые временные данные, хранящиеся в памяти: переменные или поля/свойства в ООП. Императивное программирование (включая ООП) описывает вычисления с точки зрения состояния программы и изменений в этом состоянии. Декларативное (функциональное) программирование описывает желаемые результаты и не указывает явно изменения состояния.</p><h3>Изменчивое состояние — акт умственного жонглирования</h3><p>Я думаю, что большие объектно-ориентированные программы борются со сложностью, возрастающей по мере построения большого графа изменяемых объектов. Понимаете, это как пытаться понять и держать в голове, что произойдет при вызове метода и какими будут побочные эффекты.</p><p>Состояние само по себе довольно безобидно. Но изменчивое состояние — большая угроза стабильности. Особенно, если его распространять. Что именно является изменчивым состоянием? Любое состояние, которое может измениться: переменные или поля в ООП.</p><p>Рассмотрим пример из реального мира. У вас есть чистый кусок бумаги, вы пишете на нём заметку. В результате вы получаете тот же кусок в другом состоянии (текст). Вы фактически изменили его состояние. Это вполне нормально в реальном мире, поскольку никто не заботится об этом куске бумаги. Если только это не оригинальная картина «Мона Лиза».</p><h3>Ограничения человеческого мозга</h3><p>Почему изменчивое состояние такая большая проблема? Человеческий мозг — самая мощная машина в известной вселенной. Но он очень плохо работает с состоянием, поскольку мы можем удерживать в рабочей памяти только 5 сущностей за раз. Гораздо проще рассуждать о куске кода, если вы думаете только о том, что он делает, а не о том, какие переменные он изменяет вокруг кодовой базы.</p><p>Программирование с изменяемым состоянием — это акт умственного жонглирования. Делать это двумя шарами довольно просто, но взяв три или больше, я уроню их все. С написанием кода так же. Я стал намного продуктивнее, а мой код стал намного надёжнее, как только я отбросил изменчивое состояние. Почему мы тогда пытаемся выполнять этот акт умственного жонглирования каждый день на работе? К сожалению, умственное манипулирование изменчивым состоянием лежит в основе ООП. Единственная цель существования методов объекта состоит в том, чтобы видоизменить этот же объект.</p><h3>Разрозненное состояние</h3><p>ООП ещё больше усугубляет проблему организации кода, разбрасывая состояние по всей программе. Рассеянное состояние затем беспорядочно распределяется между различными объектами.</p><p>Рассмотрим пример из реального мира. Забудем на секунду, что мы все взрослые. Притворимся, что пытаемся собрать крутой грузовик из Lego. Но здесь есть одна загвоздка — все детали грузовика случайно смешаны с деталями других ваших игрушек Lego. И они были разложены в 50 разных коробках, снова случайно. И вам не разрешают группировать детали вашего грузовика. Вы должны держать в голове, где находятся различные его части, и можете вынимать их только одну за другой. Да, вы в конечном счёте соберёте этот грузовик, но сколько времени это займет?</p><p>Какое это имеет отношение к программированию? В функциональном программировании состояние обычно является изолированным. Вы всегда знаете, откуда оно исходит. Состояние никогда не разбросано по разным функциям. В ООП каждый объект имеет своё состояние. При построении программы вы должны иметь в виду состояние всех объектов, с которыми вы в данный момент работаете. Чтобы облегчить жизнь, лучше всего иметь очень небольшую часть кода, связанную с состоянием. Пусть основные части вашего приложения не будут содержать состояния и будут чистыми. Это является главной причиной успеха для Flux-паттерна в фронтенде (он же <a href="https://ru.wikipedia.org/wiki/Redux">Redux</a>).</p><h3>Беспорядочно разделённое состояние</h3><p>Изменчивое состояние в реальном мире почти никогда не является проблемой, поскольку вещи хранятся в частном порядке. Это «правильная инкапсуляция» на работе. Представьте художника, который работает над следующей картиной Моны Лизы. Он работает над картиной один. Заканчивает, а затем продаёт свой шедевр за миллионы. А потом он решает устроить рисовальную вечеринку и приглашает своих друзей — эльфа, Гэндальфа, полицейского и зомби. Все они начинают рисовать на одном и том же холсте одновременно. Конечно, ничего хорошего из этого не выйдет.</p><p>Общее изменяемое состояние не имеет смысла в реальном мире. Но это именно то, что происходит в программах ООП — состояние беспорядочно разделяется между различными объектами и они изменяют его любым удобным для них способом. Это, в свою очередь, делает анализ программы всё сложнее, так как кодовая база продолжает расти.</p><h3>Проблемы параллелизма</h3><p>Беспорядочное совместное использование изменяемого состояния в ООП делает распараллеливание такого кода практически невозможным. Для решения этой проблемы были изобретены сложные механизмы: блокировка потоков, мьютекс и многие другие. У таких сложных подходов есть свои недостатки — взаимоблокировки, отсутствие возможности компоновки, плюс отладка многопоточного кода очень сложна и отнимает много времени. Речь даже не идёт об увеличении сложности, вызванном использованием таких механизмов параллелизма.</p><h3>Не все состояния — зло</h3><p>Вероятно, изменение состояний по задумке Алана Кея — не зло. Оно, вероятно, полезно, если действительно изолировано (не как в ООП). Также вполне нормально иметь неизменяемые объекты передачи данных. Ключ здесь «неизменяемые». Такие объекты затем используются для передачи данных между функциями.</p><p>Однако такие объекты также делают методы и свойства ООП избыточными. Какая польза от наличия методов и свойств объекта, если его нельзя изменить?</p><h3>Изменчивость неотъемлема в ООП</h3><p>Некоторые утверждают, что изменяемое состояние — решение разработчиков в ООП, а не данность. Но это не так. Это не выбор разработчиков, а практически единственный вариант. Да, неизменяемые объекты можно передавать в методы Java/C#. Но это делается редко, поскольку большинство разработчиков по умолчанию используют изменение данных. Даже если разработчики пытаются использовать неизменяемость в своих программах ООП, языки не предоставляют встроенных механизмов для неизменяемости и для эффективной работы с неизменяемыми данными (то есть постоянными структурами данных).</p><p>Можно сделать так, что объекты будут общаться только путём передачи неизменяемых сообщений и никогда не будут передавать никакие ссылки (что на самом деле редкость). Такие программы будут более надёжными, чем основные в ООП. Но объекты всё ещё должны изменить своё собственное состояние после получения сообщения. Сообщение является побочным эффектом, и его единственная цель — вызвать изменения. Сообщения были бы бесполезны, если бы они не могли изменить состояние других объектов.</p><p>Невозможно использовать ООП, не вызывая изменения состояния.</p><h3>Троянский конь инкапсуляции</h3><p>Часто упоминается, что инкапсуляция является одним из величайших преимуществ ООП. Предполагается защитить внутреннее состояние объекта от внешнего доступа. Но есть небольшая проблема. Это не работает.</p><p>Инкапсуляция — это троянский конь ООП. Он продвигает идею общего изменяемого состояния, делая его, казалось бы, безопасным. Инкапсуляция позволяет небезопасному коду проникать в кодовую базу (и даже поощряет это), заставляя её гнить изнутри.</p><h3>Проблема глобального состояния</h3><p>Часто твердят, что глобальное состояние является корнем всех бед и его следует избегать любой ценой. Инкапсуляция, по сути, является глобальным состоянием. Чтобы код был более эффективным, объекты передаются не по значению, а по ссылке. Вот где «внедрение зависимости» падает в грязь лицом.</p><p>Позвольте объяснить. Всякий раз, когда создаётся объект, ссылки на его зависимости передаются конструктору. Эти зависимости также имеют своё внутреннее состояние. Вновь созданный объект хранит ссылки на эти зависимости в своём внутреннем состоянии, а затем изменяет их любым удобным для него способом. Он также передаёт эти ссылки на всё остальное, что может в конечном счёте использовать. Это создаёт сложный граф разнородных общих объектов, которые изменяют состояние друг друга. Это, в свою очередь, вызывает огромные проблемы. Становится почти невозможно увидеть, что вызвало изменение состояния программы. Дни могут быть потрачены впустую в попытках отладить такие изменения. И вам повезёт, если не придётся иметь дело с параллелизмом (подробнее об этом позже).</p><h3>Методы/Свойства</h3><p>Методы или свойства, которые обеспечивают доступ к определённым полям, не лучше, чем непосредственное изменение значения поля. Не имеет значения, изменяете ли вы состояние объекта с помощью необычного свойства или метода, результат один и тот же — изменённое состояние.</p><h2>Проблема с моделированием реального мира</h2><p>Некоторые утверждают, что ООП пытается смоделировать реальный мир. Это неправда — ООП не имеет ничего общего с реальным миром. Попытка смоделировать программы как объекты является одной из самых больших ошибок ООП.</p><h3>Реальный мир не иерархичен</h3><p>ООП пытается моделировать всё как иерархию объектов. Но это не так. Объекты в реальном мире взаимодействуют друг с другом с помощью сообщений, но в основном они независимы друг от друга.</p><h3>Наследование в реальном мире</h3><p>ООП-наследование не отражает наследование реального мира. Родительский объект не может изменить поведение дочерних объектов во время выполнения. Даже если вы наследуете свою ДНК от родителей, они не могут вносить изменения в вашу ДНК по своему усмотрению. Вы не наследуете поведение от своих родителей. Вы развиваете своё поведение. И вы не можете переопределить поведение своих родителей.</p><h3>В реальном мире нет методов</h3><p>Имеет ли лист бумаги, на котором вы пишете, метод «записи»? Нет. Вы просто берёте пустой лист бумаги, берёте ручку и пишете текст. Вы, как человек, тоже не имеете метода «записи» — вы принимаете решение написать какой-то текст на основе внешних событий или ваших внутренних мыслей.</p><h2>Королевство Существительных</h2><p>Объекты связывают функции и структуры данных вместе в неделимых единицах. Я думаю, что это фундаментальная ошибка, поскольку функции и структуры данных принадлежат совершенно разным мирам.</p><p>Объекты (или существительные) находятся в самом центре ООП. Основное ограничение ООП состоит в том, что он превращает всё в объекты. Но не всё должно быть смоделировано как существительные. Операции (функции) не должны моделироваться как объекты. Зачем создавать класс Multiplier, когда всё, что нужно, — функция, умножающая два числа? Просто сделайте функцию Multiply. Пусть данные будут данными, а функции будут функциями.</p><p>В языках, отличных от ООП, выполнять тривиальные задачи вроде сохранения данных в файл очень просто. Это похоже на то, как вы описали бы действие на простом английском языке.</p><p>Рассмотрим ситуацию из реального мира. Возьмём того же художника. Пусть он владеет фабрикой рисования (PaintingFactory). Он нанял менеджера по кистям (BrushManager), менеджера по цвету (ColorManager), менеджера по холсту (CanvasManager) и поставщика Моны Лизы (MonaLisaProvider). Его хороший друг зомби использует стратегию потребления мозга (BrainConsumingStrategy). Эти объекты, в свою очередь, определяют следующие методы: создать картину (CreatePainting), найти кисть (FindBrush), выбрать цвет (PickColor), вызвать Мону Лизу (CallMonaLisa) и потреблять мозги (ConsumeBrainz).</p><p>Конечно, это чушь. Это никогда не могло бы произойти в реальном мире. Сколько ненужной сложности было создано для простого акта рисования картины? Нет необходимости изобретать странные концепции для хранения ваших функций, когда им разрешено существовать отдельно от объектов.</p><h2>Модульное тестирование</h2><p>Автоматическое тестирование является важной частью процесса разработки и очень помогает в предотвращении регрессий (ошибок, вносимых в существующий код). Модульное тестирование играет огромную роль в процессе автоматического тестирования.</p><p>Некоторые могут не согласиться, но ООП-код общеизвестно труден для модульного тестирования. Модульное тестирование предполагает изоляцию, и чтобы создать метод для такого вида тестирования, нужно:</p><ul><li>извлечь его зависимости в отдельный класс;</li><li>создать интерфейс для вновь созданного класса;</li><li>объявить поля для хранения экземпляра вновь созданного класса;</li><li>использовать фреймворк, чтобы «mock-ать» зависимости;</li><li>использовать специальный фреймворк для внедрения зависимостей.</li></ul><p>Сколько ещё препятствий нужно преодолеть, чтобы сделать фрагмент кода тестируемым? Сколько времени было потрачено впустую? Кроме того, нужно создавать экземпляр всего класса, чтобы протестировать один метод. Это подтянет код из всех его родительских классов. С ООП писать тесты для унаследованного кода ещё сложнее, практически невозможно. Целые компании были созданы (<a href="https://www.typemock.com/">TypeMock</a>) из-за проблемы тестирования легаси-кода.</p><h3>Шаблонный код</h3><p>Шаблонный код (бойлерплейт) является самой большой проблемой, когда речь идёт о соотношении сигнал/шум. Шаблонный код — это «шум» для компиляции программы. Такой код требует времени для написания и делает кодовую базу менее читаемой.</p><p>Хоть в ООП пропагандируется «программа для интерфейса, а не для реализации», не всё должно становиться интерфейсом. Следовало бы прибегнуть к использованию интерфейсов во всей кодовой базе с единственной целью — тестирование. Также, вероятно, пришлось бы использовать внедрение зависимостей, что в дальнейшем привело бы к ненужной сложности.</p><h3>Тестирование приватных методов</h3><p>Некоторые утверждают, что приватные методы не должны тестироваться. Я склонен не согласиться, модульное тестирование называется «модульным», так как тестируются небольшие изолированные блоки кода. Тем не менее, тестирование приватных методов в ООП практически невозможно. Не следует делать приватные методы внутренними только ради тестирования.</p><p>Чтобы достичь тестируемости частных методов, их обычно извлекают в отдельный объект. Это, в свою очередь, вносит ненужную сложность и шаблонный код.</p><h2>Рефакторинг</h2><p>Рефакторинг является важной частью работы разработчика. По иронии судьбы ООП-код трудно реорганизовать. Рефакторинг должен сделать код менее сложным и более понятным. Напротив, реорганизованный код в ООП становится значительно более сложным. Чтобы сделать код тестируемым, нужно использовать внедрение зависимостей и создать интерфейс для реорганизованного класса. И даже тогда рефакторинг ООП-кода сложен без специальных инструментов, таких как Resharper.</p><p>В этом простом примере количество строк увеличилось более чем в два раза только для извлечения одного метода. Почему рефакторинг создаёт ещё большую сложность, когда код подвергается рефакторингу, который нужен в первую очередь для уменьшения сложности?</p><p>Сравните это с аналогичным рефакторингом не-ООП-кода в JavaScript:</p><p>Код буквально остался прежним. Функция isValidInput() просто переместилась в другой файл и добавилась одна строка для импорта этой функции. Также добавилась _isValidInput() к сигнатуре функции для удобства тестирования.</p><p>Это простой пример, но на практике сложность ООП-кода возрастает экспоненциально по мере увеличения кодовой базы. И это ещё не всё. Рефакторинг ООП-кода крайне рискован. Сложные графики зависимостей и состояния, разбросанные по всей кодовой базе ООП, не позволяют человеческому мозгу рассмотреть все потенциальные проблемы.</p><h2>Костыли в ООП</h2><p>Что вы делаете, когда что-то не работает? Обычно есть два варианта — выбросить или попробовать исправить. ООП не может быть легко выброшено: миллионы разработчиков обучаются ООП, миллионы организаций по всему миру используют его. В течение десятилетий люди много думали, пытаясь решить проблемы, распространённые в ООП-коде. И они придумали множество шаблонов проектирования.</p><h3>Шаблоны проектирования</h3><p>ООП предоставляет набор рекомендаций, которые теоретически должны позволить разработчикам постепенно наращивать всё большие и большие системы: принцип SOLID, внедрение зависимостей, шаблоны проектирования и т. д. К сожалению, шаблоны проектирования — это не что иное, как костыли. Они существуют исключительно для устранения недостатков ООП. На эту тему было написано множество книг. И они не были бы такими плохими, если бы не вносили огромную сложность в кодовые базы.</p><h3>Фабрика проблем</h3><p>Фактически невозможно написать хороший и поддерживаемый объектно-ориентированный код.</p><p>На одной стороне спектра есть кодовая база, которая является непоследовательной и не придерживается каких-либо стандартов. По другую сторону — башня с чрезмерно сконструированным кодом, куча ошибочных абстракций, построенных одна поверх другой. Шаблоны проектирования очень помогают при построении таких башен абстракций.</p><p>Вскоре добавление новой функциональности и даже понимание всей сложности становится всё труднее и труднее. Кодовая база будет полна таких вещей, как SimpleBeanFactoryAwareAspectInstanceFactory, AbstractInterceptorDrivenBeanDefinitionDecorator, TransactionAwarePersistenceManagerFactoryProxy или RequestProcessorFactoryFactory.</p><p>Нужно тратить драгоценные интеллектуальные ресурсы, пытаясь понять башню абстракций, которую создали сами разработчики. Отсутствие структуры во многих случаях лучше, чем плохая структура.</p><figure><img src="https://media.tproger.ru/uploads/2019/08/1__xDSrTC0F2lke6OYtkRm8g.png" alt="" /></figure><p>Статья на тему: <a href="https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition">FizzBuzzEnterpriseEdition</a>.</p><h2>Падение четырёх столпов ООП</h2><p>Четыре столпа ООП: абстракция, наследование, инкапсуляция и полиморфизм.</p><p>Посмотрим, что они из себя представляют на самом деле, один за другим.</p><h3>Наследование</h3><p>Я думаю, что невозможность повторного использования затрагивает объектно-ориентированные, а не функциональные языки. Поскольку проблема с ООП-языками заключается в том, что у них есть вся эта неявная среда, которую они носят с собой. Вы хотели банан, а получили гориллу с бананом и целые джунгли в придачу.</p><p>Наследование не имеет ничего общего с реальным миром. Оно является худшим способом достижения повторного использования кода. <a href="https://ru.wikipedia.org/wiki/Design_Patterns">Банда четырёх</a> недвусмысленно рекомендовала отдавать предпочтение композиции перед наследованием, а <a href="https://ru.wikipedia.org/wiki/Go">некоторые современные языки программирования</a> вообще его избегают.</p><p>Есть несколько проблем с наследованием:</p><ul><li>происходит добавление большого количества кода, который даже не нужен вашему классу (проблема с бананом и джунглями);</li><li>определение частей вашего класса где-либо ещё затрудняет анализ кода, особенно с несколькими уровнями наследования;</li><li>в большинстве языков множественное наследование даже невозможно, это в основном делает наследование бесполезным в качестве механизма совместного использования кода.</li></ul><h3>Полиморфизм</h3><p>Полиморфизм великолепен, он позволяет изменять поведение программы во время выполнения. Это очень базовая концепция в компьютерном программировании. Я не очень уверен, почему в ООП ему уделяют так много внимания. Он выполняет свою работу, но в очередной раз приводит к умственному жонглированию. Это делает кодовую базу значительно более сложной. Анализ конкретного метода, который вызывается, становится очень трудным.</p><p>Функциональное программирование позволяет добиться того же полиморфизма гораздо более элегантным способом — просто передав функцию, определяющую желаемое поведение во время выполнения. Что может быть проще этого? Не нужно определять кучу перегруженных абстрактных виртуальных методов в нескольких файлах (и интерфейсе).</p><h3>Инкапсуляция</h3><p>Как обсуждалось ранее, инкапсуляция — троянский конь ООП. На самом деле это глобальное изменяемое состояние, благодаря которому небезопасный код выглядит безопасным. Небезопасная практика написания кода — это основа, на которую программисты ООП полагаются в своей повседневной работе.</p><h3>Абстракция</h3><p>Абстракция в ООП пытается решить сложность, скрывая ненужные детали от программиста. Теоретически, это должно позволить разработчику анализировать кодовую базу, не думая о скрытой сложности.</p><p>Причудливое слово для простой концепции. В процедурных/функциональных языках вы можете просто «спрятать» детали реализации в соседнем файле. Нет необходимости называть этот основной акт «абстракцией».</p><p>Для подробной информации о столпах ООП можете прочитать статью: <a href="https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53">«Goodbye, Object-Oriented Programming»</a>.</p><h2>Почему ООП доминирует в индустрии?</h2><p>Ответ прост: рептилоидная инопланетная раса вступила в сговор с АНБ (и русскими), чтобы замучить вас (программистов) до смерти.</p><p>А если серьезно, то, вероятно, ответ — Java.</p><p>Java — самая неприятная вещь, случившаяся с компьютерами со времен MS-DOS.</p><h3>Java был прост</h3><p>Когда он был впервые представлен в 1995 году, Java был очень простым языком программирования по сравнению с альтернативами. В то время входной барьер для написания настольных приложений был высок. Разработка настольных приложений включала написание низкоуровневых API-интерфейсов win32 на С. Разработчики также должны были заниматься ручным управлением памятью. Другой альтернативой был Visual Basic, но многие не хотели замыкаться в экосистеме Microsoft.</p><p>Когда появился Java, для многих разработчиков работать с ним было просто. Он был бесплатным и мог использоваться на всех платформах. Такие вещи, как встроенная сборка мусора, понятные API-интерфейсы (по сравнению с загадочными win32-API), правильные пространства имён и знакомый C-подобный синтаксис сделали Java ещё более доступным.</p><p>GUI-программирование также становилось всё более популярным. Казалось, что различные компоненты пользовательского интерфейса хорошо отображаются на классы. Автозаполнение метода в IDE также заставило людей утверждать, что ООП API проще в использовании.</p><p>Возможно, Java не был бы таким плохим, если бы он не вынуждал разработчиков реализовывать ООП. Всё остальное в Java выглядело довольно хорошо. Сборка мусора, переносимость, функции обработки исключений, которых не хватало в других основных языках, — всё это было очень удобным.</p><h3>Затем появился C#</h3><p>Первоначально Microsoft в значительной степени опиралась на Java. Когда что-то начало идти не так (и после долгой юридической битвы с Sun Microsystems за лицензирование Java), Microsoft решила инвестировать в свою собственную версию Java. Так появился C# 1.0. C# как язык всегда считался «лучшей версией Java». Однако есть одна огромная проблема — это тот же язык ООП с теми же недостатками, скрытыми под слегка улучшенным синтаксисом.</p><p>Microsoft вкладывала значительные средства в свою экосистему .NET, которая также включала в себя хорошие инструменты для разработчиков. В течение многих лет Visual Studio была одной из лучших доступных сред IDE. Это привело к широкому распространению платформы .NET, особенно на предприятиях.</p><p>В последнее время Microsoft вкладывает значительные средства в экосистему браузера, продвигая свой TypeScript. TypeScript великолепен, потому что он может компилировать чистый JavaScript и добавляет такие вещи, как статическая проверка типов. Но уже не так великолепно отсутствие надлежащей поддержки функциональных конструкций: нет встроенных неизменяемых структур данных, нет композиции функций, нет правильного сопоставления с образцом. TypeScript является в первую очередь ООП-языком. В основном это C# для браузеров. Даже отвечал за разработку и C#, и TypeScript один человек — <a href="https://ru.wikipedia.org/wiki/Хейлсберг,_Андерс">Андерс Хейлсберг</a>.</p><h3>Функциональные языки</h3><p>Функциональные языки никогда не поддерживались такими крупными компаниями, как Microsoft. F# не считается, так как инвестиции были незначительными. Разработка функциональных языков в основном осуществляется сообществом. Это объясняет различия в популярности между объектно-ориентированными и функциональными языками.</p><h2>Пора двигаться дальше?</h2><p>Теперь мы знаем, что ООП — эксперимент, который провалился. Настало время двигаться дальше. Настало время нам, как сообществу, признать эту идею провальной и отказаться от неё.</p><p>Думаю, довольно легко продолжать использовать то, что использовали десятилетиями. Большинство людей никогда не пробовало функциональное программирование. А те, кто попробовал (как и я), никогда не смогут вернуться к написанию ООП-кода.</p><p>Генри Форд однажды сказал: «Если бы я спросил людей, чего они хотят, они бы ответили — более быстрых лошадей». В мире программного обеспечения большинство людей захотят «лучший ООП-язык». Люди могут легко описать пожелания, которые у них есть (более организованная и менее сложная кодовая база), но не лучшее решение.</p><h2>Каковы альтернативы?</h2><p>Внимание, спойлер — функциональное программирование.</p><p>Если термины вроде «функтор» или «монада» вызывают у вас некоторое беспокойство, то вы не одиноки. Функциональное программирование не было бы таким страшным, если бы оно дало более интуитивные названия некоторым из его концепций. Функтор — то, что можно преобразовать с помощью функции (вспомните list.map). Монада — вычисления, которые можно объединить в цепочку.</p><p>Используя функциональное программирование, вы станете более хорошим разработчиком. У вас будет время писать реальный код, который решает реальные проблемы, вместо размышлений об абстракциях и шаблонах проектирования.</p><p>Вы можете не осознавать этого, но вы уже функциональный программист, если используете функции в своей повседневной работе. Вам просто нужно узнать, как использовать их наилучшим образом.</p><p>Два великолепных функциональных языка с очень плавной кривой обучения — это <a href="https://elixir-lang.org/">Elixir</a> и <a href="https://elm-lang.org/">Elm</a>. Они позволяют разработчику сосредоточиться на важнейшем — написании надёжного программного обеспечения — одновременно устраняя все сложности, которые имеют более традиционные функциональные языки.</p><p>Какие есть ещё варианты? Ваша организация уже использует C#? Попробуйте F# — удивительный функциональный язык, обеспечивающий отличную совместимость с существующим кодом .NET. Используете Java? Тогда Scala или Clojure — хорошие альтернативы. Используете JavaScript? С правильным руководством и линтингом JavaScript может быть хорошим функциональным языком.</p><h2>Защитники ООП</h2><p>Реакция от защитников ООП вполне ожидаема. Они скажут, что эта статья полна неточностей. Некоторые могут даже начать называть имена. Они могли бы даже назвать меня junior-разработчиком без реального опыта ООП. Кто-то может сказать, что мои предположения ошибочны, а примеры бесполезны. Это не имеет значения.</p><p>Они имеют право на своё мнение. Однако их аргументы в защиту ООП обычно довольно слабы. По иронии судьбы, большинство из них никогда не программировали на настоящем функциональном языке. Как можно провести сравнение между двумя вещами, если вы никогда не пробовали их обе? Такие сравнения не очень хороши.</p><p>Закон Деметры не очень полезен — он ничего не делает для решения проблемы недетерминированности. Общее изменяемое состояние всё ещё является общим изменяемым состоянием, независимо от того, каким образом вы получаете доступ или изменяете его. Метод a.total() не сильно лучше a.getB().getC().total(). Это просто заметает проблему под ковёр.</p><p>Предметно-ориентированное проектирование? Это полезная методология разработки, она немного помогает со сложностью. Но она по-прежнему ничего не делает для решения фундаментальной проблемы общего изменяемого состояния.</p><h3>Просто инструмент в наборе</h3><p>Часто слышу мнение людей, что ООП — просто ещё один инструмент в наборе. Да, это такой же инструмент, как и то, что лошади и машины — инструменты для перевозки. В конце концов, все они служат одной цели. Зачем использовать машины, если можно продолжать ездить на старых добрых лошадях?</p><h3>История повторяется</h3><p>В начале 20-го века автомобили начали заменять лошадей. В 1900 году в Нью-Йорке было всего несколько автомобилей на дорогах, но в основном люди использовали для перевозки лошадей. Огромная индустрия была сосредоточена вокруг конного транспорта. Целые предприятия были созданы вокруг уборки навоза. <a href="https://99percentinvisible.org/article/cities-paved-dung-urban-design-great-horse-manure-crisis-1894/">Люди сопротивлялись переменам</a>. Они называли автомобили ещё одной «модой», которая в конечном итоге проходит, ведь лошади были здесь веками. Некоторые даже просили правительство вмешаться. В 1917 году на дорогах больше не осталось лошадей.</p><p>Насколько это актуально? Индустрия программного обеспечения сосредоточена вокруг ООП. Миллионы людей обучаются ООП и миллионы компаний используют его в своём коде. Конечно, они попытаются дискредитировать всё, что угрожает их хлебу с маслом. Это просто здравый смысл. Ясно видно, как история повторяется. В 20-м веке это были лошади против автомобилей, а в 21-м — объектно-ориентированное и функциональное программирование.</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 4. Обработка исключений</title>
      <link>https://tproger.ru/translations/functional-sharp-4</link>
      <comments>https://tproger.ru/translations/functional-sharp-4?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-4</guid>
      <description><![CDATA[<p>Заключительная часть цикла о функциональном C#: обработка ошибок и исключений по мотивам концепции Railway Oriented Programming от Scott Wlaschin.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-4">Функциональный C#. Часть 4. Обработка исключений</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 03 Apr 2015 22:54:24 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы продолжаем цикл статей о функциональном C#. Сегодняшняя часть заключительная, и мы в ней рассмотрим вопрос обработки исключений и ошибок. Предлагаем вспомнить предыдущие части серии:</p><ol><li><a href="https://tproger.ru/translations/functional-sharp-1/">Функциональный C#: Неизмяемные объекты</a></li><li><a href="https://tproger.ru/translations/functional-sharp-2/">Функциональный C#: Одержимость примитивами</a></li><li><a href="https://tproger.ru/translations/functional-sharp-3/">Функциональный C#: Ненулевые ссылочные типы</a></li><li>Функциональный C#: Обработка исключений</li></ol><h3>Обработка ошибок: основной подход</h3><p>Концепция проверок и обработок исключений хорошо известна многим из вас, но код, необходимый для этого, может действительно раздражать. По крайней мере в таком языке программирования как C#. Эта статья вдохновлена концепцией Railway Oriented Programming, которую представил <a href="http://fsharpforfunandprofit.com/about/">Scott Wlaschin</a> в своей <a href="http://fsharpforfunandprofit.com/rop/">презентации</a>. Советуем вам просмотреть его полное выступление, так как это даст вам бесценные знания о том, каким неудобным может быть C# и как это обойти.</p><p>Посмотрите на пример ниже:</p><p>Кажется, в этом коде все ясно и кристально чисто. Мы создаем экземпляр класса Customer, сохраняем его в репозиторий, а затем отправляем ему поздравления по электронной почте. Но все верно ровно до тех пор, пока у нас все проходит без ошибок. Мы в этом коде предполагаем, что все функции выполнятся абсолютно точно. Но, к превеликому сожалению, такое практически невозможно.</p><p>Когда же вы начинаете отлавливать ошибки, то код становится похожим на что-то подобное:</p><p>Дела обстоят еще хуже, если при возникновении ошибки, нам потребуется отменить последнюю операцию. Тогда наш код станет еще больше:</p><p>Ну наконец-то! Теперь уж точно все. Но есть одна проблема… Наш метод ранее состоял из 5 строк, а сейчас их целых 35! Семикратное увеличение! И это только один простой метод. Более того, теперь нам трудно ориентироваться в написанном. Эти самые 5 строчек значимого кода погребены под толщей обработок исключений.</p><h3>Обработка исключений и ошибок ввода в функциональном стиле</h3><p>Можно ли обойтись без такого расширения исходного кода? К счастью, да. Давайте пройдемся по нашему методу и посмотрим, что же мы можем сделать.</p><p>Вы, возможно, заметили, что мы используем технику, описанную в одной из наших <a href="https://tproger.ru/translations/functional-c-primitive-obsession/">прошлых статей</a>. Вместо того, чтобы использовать примитивы, мы используем классы. Например, CustomerName и BillingInfo. Это позволяет ставить всю проверку на корректность в одном месте и придерживаться <a href="https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself">принципа DRY</a>.</p><p>Статический метод Create возвращает объект класса Result, который инкапсулирует всю необходимую информацию, касающуюся результатов операции — сообщение об ошибке в случае неудачи и экземпляр объекта в случае успеха.</p><p>Кроме того, обратите внимание, что функции, которые могли бы вызвать ошибки, обернуты в конструкцию try/catch. Такой подход нарушает одно из лучших практик, которая описана в <a href="http://enterprisecraftsmanship.com/2015/02/26/exceptions-for-flow-control-in-c/">этой статье</a>. Суть заключается в том, что если вы знаете, как бороться с исключением, то обработать его следует на минимально возможном уровне. Давайте перепишем код:</p><p>Как вы могли заметить, мы оборачиваем все возможные места ошибок в Result. Работа такого класса очень похожа на работу монады Maybe, о которой мы говорили в <a href="https://tproger.ru/translations/functional-c-non-nullable-reference-types/">одной из прошлых статей</a>. Используя Result, мы можем анализировать код, не глядя в детали реализации. Вот как выглядит сам класс (некоторые детали опущены для краткости):</p><p>Теперь мы можем применить тот же принцип, который используется в функциональных языках. Вот, где происходит настоящая магия:</p><p>Если вы знакомы с функциональными языками программирования, то вы, возможно, заметили, что метод OnSuccess — это, фактически, метод Bind.</p><p>Как же работает OnSuccess? Метод проверяет предыдущий результат и в случае успеха выполняет текущую операцию. В противном случае он просто возвращает последний успешный результат. Таким образом, цепь продолжается до тех пор, пока не встретится ошибка. Если же она встречается, все остальные методы попросту пропускаются.</p><p>Метод OnFailure работает с точностью до наоборот, как вы могли догадаться: выполняет текущий метод только в том случае, если предыдущая операция привела к ошибке.</p><p>OnBoth находится в конце цепочки. Используется он для вывода различного рода сообщений и логов.</p><p>Итак, мы написали нужный нам метод с обработками исключений и ошибок, но с гораздо меньшим количеством кода. Более того, обратите внимание, что теперь намного легче понимать, что вообще делает метод.</p><h3>А что насчет принципа CQS?</h3><p>CQS — Command-Query Separation — принцип императивного программирования. Он гласит, что каждый метод должен быть либо командой, которая выполняет какое-то действие, либо запросом, возвращающим данные. Есть ли у нас конфликт с данным принципом?</p><p>Нет. И более того, наш подход увеличивает читаемость кода таким же способом, что и в принципе CQS. Но теперь потенциальный диапазон информации, которую мы получаем от наших методов, расширился вдвое. Вместо 2 (значение null и какой-либо объект, возвращаемый запросом) у нас их 4:</p><p>public void Save(Customer customer)</p><p>public Customer GetById(long id)</p><p>public Result Save(Customer customer)</p><p>public Result GetById(long id)</p><p>Когда говорится, что метод не может привести к ошибке, мы не имеем в виду то, что ошибки не может быть совсем. Всегда есть какая-то вероятность, что выпадет исключение (особенно там, где его никто не ожидал). Под методами, которое не могут привести к ошибке, подразумеваются такие функции, которые должны всегда работать без исключений. То есть любая произошедшая в них ошибка является неожиданной.</p><h3>Вывод</h3><p>Очень важно смотреть на свой код и сразу понимать, что там выполняется. Код должен быть читаем. Но одновременно с этим вы должны уделять большое внимание обработке исключений. Однако классический подход довольно громоздкий и неудобный. Именно поэтому есть смысл применять технику, описанную в этой статье.</p><p>В сочетании с другими тремя техниками — <a href="https://tproger.ru/translations/functional-c-immutability/">неизменяемыми объектами</a>, <a href="https://tproger.ru/translations/functional-sharp-2/">избавлением от одержимости примитивами</a> и <a href="https://tproger.ru/translations/functional-sharp-3/">необнуляемыми ссылочными типами</a> — этот подход представляет мощную модель программирования, которая позволяет существенно повысить производительность.</p><p>Перевод статьи «Functional C#: Handling failures, input errors»</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 3. Ненулевые ссылочные типы</title>
      <link>https://tproger.ru/translations/functional-sharp-3</link>
      <comments>https://tproger.ru/translations/functional-sharp-3?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-3</guid>
      <description><![CDATA[<p>Ненулевые ссылочные типы в C# помогают избавиться от NullReferenceException, когда неизвестно, вернёт ли метод вроде GetById реальный экземпляр или null.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-3">Функциональный C#. Часть 3. Ненулевые ссылочные типы</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 30 Mar 2015 19:40:35 GMT</pubDate>
      <content:encoded><![CDATA[<p>Эта статья третья в серии “Функциональный C#”. Все части:</p><ol><li><a href="https://tproger.ru/translations/functional-sharp-1/">Функциональный C#: Неизменяемые объекты</a></li><li><a href="https://tproger.ru/translations/functional-sharp-2/">Функциональный C#: Одержимость примитивами</a></li><li>Функциональный C#: Ненулевые ссылочные типы</li><li><a href="https://tproger.ru/translations/functional-sharp-4/">Функциональный C#: Обработка исключений</a></li></ol><h3>Ненулевые ссылочные типы в C#: введение</h3><p>Посмотрите на пример, приведенный ниже:</p><p>Что-то знакомое, да? Но какие ошибки вы наблюдаете?</p><p>Проблема заключается в том, что мы не знаем наверняка, действительно ли метод GetById вернет ненулевой экземпляр. Несмотря ни на что, есть определенный шанс того, что мы получим null. В таком случае мы получим исключение NullReferenceException. Ситуация может быть еще хуже, если перед тем, как использовать переменную customer, она еще не будет инициализирована методом GetById. В этом случае нам будет очень сложно отловить такую ошибку.</p><p>Чем быстрее мы получаем обратную связь, тем меньше времени мы имеем для исправления ошибки. Конечно, максимально быстрый ответ мы можем получить только от компилятора. Согласитесь, было бы здорово написать наш код, и пусть компилятор сделает все необходимые проверки?</p><p>Где Customer! означает ненулевой тип, т.е. экземпляры такого класса не могут принимать значение null в принципе. Как думаете, было бы здорово быть уверенным в том, что компилятор укажет нам, если какой-либо кусок кода может вернуть null?</p><p>Безусловно, было бы круто! Или даже лучше:</p><p>То есть, сделать все ссылочные типы по умолчанию ненулевыми (так же, как и типы значений). И если мы захотим применить нулевой тип, то следует воспользоваться таким кодом:</p><p>Можете ли вы себе представить мир без всех этих раздражающих NullReferenceException? Я тоже не могу.</p><p>К сожалению, необнуляемые ссылочные типы не могут быть введены в C# как признак языка. Чтобы подробнее узнать об этой теме, рекомендую к прочтению эти статьи:</p><ol><li><a href="http://blog.coverity.com/2013/11/20/c-non-nullable-reference-types/#.VPYwaV19JDx">Статья Эрика Липперта</a> (Eric Lippert)</li><li><a href="http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake">Интересная, но практически нереализуемое “дизайнерское” решение</a></li></ol><p>Но не волнуйтесь. Хоть мы и не можем заставить компилятор помочь нам и использовать мощности ненулевых ссылочных типов, есть и обходные пути, к которым сейчас и прибегнем. Давайте взглянем на класс Customer, который мы написали в <a href="http://enterprisecraftsmanship.com/2015/03/07/functional-c-primitive-obsession/">прошлой статье</a>:</p><p>Как вы могли заметить, мы перенесли поля класса Customer (имя и почту) в отдельные классы. Однако, мы ничего не можем поделать с проверками на null. Это единственные условия, которые остались в классе Customer.</p><h3>Как избавиться от проверок на null</h3><p>Итак, как мы можем избавиться от таких проверок? Конечно же при помощи IL рерайтинга (Intermediate Language Rewrite)!</p><p>Есть замечательный NuGet пакет под названием <a href="https://github.com/Fody/NullGuard">NullGuard.Fody</a>. Для начала вам нужно скачать и установить пакет. Пометьте сборку таким атрибутом:</p><p>[assembly: NullGuard(ValidationFlags.All)]</p><p>Что же мы сделали? Отныне каждый метод и свойство в сборке автоматически проверяется на null. Теперь мы можем переписать класс Customer. Выглядеть он будет просто и изящно:</p><p>Или даже еще проще:</p><p>Несмотря на визуальную простоту класса, на самом деле он выглядит вот так:</p><h3>Но как быть со значением null?</h3><p>Так как же мы можем определить то, что значение некоторого типа может быть пустым (null)? Для этого мы будем использовать монаду Maybe.</p><p>Как вы можете заметить, входные значения для класса Maybe отмечены атрибутом AllowNull. Теперь мы можем написать следующий код с использованием Maybe:</p><p>И теперь становится очевидным то, что метод GetById может вернуть нулевое значение. Кроме того, теперь вы не сможете случайно перепутать значение, которое может принимать null, с необнуляемым значением, что привело бы к ошибке компилятора.</p><p>Конечно же вы теперь должны решить, какие сборки будут обработаны пакетом NullGuard.Fody. Вероятно, применение данного пакета в WPF не лучшая идея, поскольку там имеется множество системных компонентов, которые являются по своей сути обнуляемыми. Именно поэтому добавление проверок на null не даст вам особого преимущества. Однако для всех остальных сборок данный метод будет более чем актуальным.</p><p>Небольшое замечание о монаде Maybe. Вы, возможно, захотите назвать ее Option из-за соглашения об именовании языка F#. Я лично предпочитаю использовать Maybe, но по-моему, распределение программистов в этом вопросе примерно 50 на 50. Конечно, это всего лишь дело вкуса.</p><h3>Что насчет статических проверок?</h3><p>Окей, быстрая обратная связь во время выполнения это хорошо, но это все еще обратная связь во время выполнения. Было бы замечательно анализировать код еще быстрее — скажем, на этапе компиляции.</p><p>Ответ кроется в атрибуте NotNull замечательного дополнения к Visual Studio — <a href="https://www.jetbrains.com/resharper/">ReSharper</a>. Применив атрибут NotNull к какому-то методу и вернув в нем нулевое значение, вы получите предупреждение от плагина ReSharper.</p><p>Данный способ может здорово облегчить вам жизнь, однако он страдает от некоторых проблем.</p><p>Во-первых, ReSharper работает по методу от обратного. Сейчас вам нужно отмечать атрибутом NotNull те значения, которые не могут быть нулевыми. Было бы гораздо удобнее помечать этим атрибутом значения, которые, наоборот, могли бы быть нулевыми. Остальные же по умолчанию считались бы необнуляемыми.</p><p>Во-вторых, предупреждения — это всего лишь предупреждения. Вы можете запросто не обратить на них внимание и пропустить их. Разумеется, мы можем установить настройки Visual Studio так, что он будет понимать предупреждения как ошибки. Однако с монадой Maybe у вас гораздо меньше шансов ошибиться.</p><p>Именно по этим причинам я не использую ReSharper, хотя они и очень полезны.</p><h3>Вывод</h3><p>Подход, описанный выше, действительно очень мощный:</p><ol><li>Вы быстро отловите баг с нулевым значением. Теперь вас не будут надоедать постоянные NullReferenceException.</li><li>Вы увеличите читаемость кода. Теперь не будут везде мелькать постоянные проверки значения на null перед использованием объекта.</li><li>По умолчанию все ваши методы будут защищены от исключения NullReferenceException. Причем вам не надо будет помечать каждый новый метод атрибутом NotNull.</li></ol><p>А на этом все! В следующей статье мы мы рассмотрим вопрос обработки исключений в функционально C#.</p><p>Перевод статьи “Functional C#: Non-nullable reference types”</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 2. Одержимость примитивами</title>
      <link>https://tproger.ru/translations/functional-sharp-2</link>
      <comments>https://tproger.ru/translations/functional-sharp-2?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-2</guid>
      <description><![CDATA[<p>Хранение имени и почты класса Customer в примитивных типах кажется простым решением, пока не доходит до проверки значений полей на корректность.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-2">Функциональный C#. Часть 2. Одержимость примитивами</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 27 Mar 2015 22:44:56 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы продолжаем цикл статей о функциональном программировании на языке C#:</p><ol><li><a href="https://tproger.ru/translations/functional-sharp-1/">Функциональный C#: Неизменяемые объекты</a></li><li>Функциональный C#: Одержимость примитивами</li><li><a href="https://tproger.ru/translations/functional-sharp-3/">Функциональный C#: Ненулевые ссылочные типы</a></li><li><a href="https://tproger.ru/translations/functional-sharp-4/">Функциональный C#: Обработка исключений</a></li></ol><p>Представьте себе ситуацию, что вам потребовалось описать некий класс Customer, содержащий определенные данные — скажем, имя и электронную почту. К сожалению, многие программисты подумают, что использовать для имени и почты поля элементарных типов данных куда проще, чем написать базовый класс.</p><p>Если вы тоже так думаете, то вашим результатом будет подобный код:</p><p>Данный метод является правильным ровно до того момента, пока вам не понадобится проверять значения полей на корректность. Но вас так просто не убедить и вы пишете уйму проверок. В итоге класс увеличивается и обрастает различными условиями:</p><p>Да и ладно, если бы эти проверки оставались внутри класса. Но ведь они выходят на уровень выше — в основной класс приложения:</p><p>Согласитесь, такой подход, мягко говоря, не совсем корректный. А как же <a href="https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself">принцип DRY</a> и единый источник истины? В приведенном выше примере по меньшей мере 3 таких источника, что совсем не оправдано.</p><p>Именно эта ситуация и называется состоянием одержимости примитивами. В следующей главе мы покажем вам, как обойти эту “болезнь”.</p><h3>Как избавиться от одержимости примитивами?</h3><p>Очень просто! Мы всего-навсего должны ввести два новых класса, в которых мы и будем проверять значения на валидность. Это и будет единым источником истины, о котором говорилось выше.</p><p>Красота такого подхода заключается в том, что если вы захотите изменить логику проверки значений, вам нужно будет подкорректировать код ровно в одном месте. Чем меньше дублирования кода в вашей программе, тем меньше ошибок вы допустите и тем счастливее ваши клиенты!</p><p>Обратите внимание на то, что конструктор класса Email приватный. А новый экземпляр мы можем создать при помощи метода Create, который прогоняет входное значение через множество фильтров, проверяя его на валидность. Это сделано для того, чтобы значение объекта было корректным с самого начала его существования.</p><p>А вот и пример применения таких классов:</p><p>Обратите внимание, что экземпляры Result&lt;Email&gt; и Result&lt;CustomerName&gt; явно говорят нам, что метод Create может вызвать ошибку. И если это произойдет, то информацию об ошибке можно узнать из свойства Error.</p><p>А теперь давайте взглянем на класс Customer после того, как мы ввели два маленьких побочных класса:</p><p>Практически все проверки были перемещены в классы Email и CustomerName. Остались только условия с проверками на null, но их мы рассмотрим в следующей статье.</p><p>Так какие преимущества мы получили, избавившись от одержимости примитивами?</p><ol><li>Мы создали единый авторитетный источник знаний для каждого объекта и избавились от дублирования кода.</li><li>Теперь невозможно по ошибке присвоить объекту Email или CustomerName такое значение, которое привело бы к ошибке компилятора.</li><li>Нет необходимости в дополнительной проверке корректности электронной почты или имени покупателя. Если объекты класса Email или CustomerName существуют, то мы точно знаем, что данные в них хранятся абсолютно верные.</li></ol><p>Есть одна деталь, на которой бы хотелось остановиться поподробней. Дело в том, что некоторые программисты избавляются от одержимости элементарных типов не полностью. Например:</p><p>Нужно помнить, что использовать элементарные типы стоит только тогда, когда объект покидает пределы программы. То есть в тех случаях, когда значения заносятся в базу данных или экспортируются во внешний файл. Но в своем приложении старайтесь использовать написанные вами классы-обертки настолько часто, насколько это возможно. Это сделает ваш код более чистым. Убедитесь в этом сами:</p><h3>Обратная сторона: ограничения</h3><p>К сожалению, создание пользовательских типов данных в C# реализовано не так безупречно, как в функциональных языках: F#, например. Возможно, ситуацию исправит новая версия языка: C# 7.0.</p><p>Именно поэтому я считаю, что в некоторых ситуациях использование примитивов лучше, чем создание простого класса-обертки. Например, для представления денег. Они могут быть выражены при помощи элементарного типа данных с одной лишь проверкой знака числа. Да, вам придется продублировать это условие, но это решение проще, даже в долгосрочной перспективе.</p><p>Как и всегда, скажу вам, чтобы вы перед тем, как что-то написать, взвесили все “за” и “против” и только потом принимали решение. И не бойтесь менять свое мнение несколько раз.</p><h3>Вывод</h3><p>С неизменяемыми и не примитивными типами данных мы становимся все ближе и ближе к решению задач в парадигме функционального программирования. А в следующей статье мы попробуем избавиться от многочисленных проверок на null.</p><h3>Исходные коды</h3><ol><li><a href="https://gist.github.com/vkhorikov/5f0c7167250edb17cd2f">Код с одержимостью примитивами</a></li><li><a href="https://gist.github.com/vkhorikov/063953c90f5d60ddc24b">Код без одержимости примитивами</a></li></ol><p>Перевод статьи «Functional C#: Primitive obsession»</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный C#. Часть 1. Неизменяемые объекты</title>
      <link>https://tproger.ru/translations/functional-sharp-1</link>
      <comments>https://tproger.ru/translations/functional-sharp-1?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/functional-sharp-1</guid>
      <description><![CDATA[<p>Сложность кода — главная беда корпоративных систем, и неизменяемые объекты в C# делают программу читабельнее и проще для проверки на корректность.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/functional-sharp-1">Функциональный C#. Часть 1. Неизменяемые объекты</a>»</p>]]></description>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Функциональный C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 26 Mar 2015 12:56:37 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мы начинаем цикл статей, в которых покажем вам, как программировать на языке C# в парадигме функционального программирования. Нами будут рассмотрены темы:</p><ol><li>Функциональный C#: Неизменяемые объекты</li><li><a href="https://tproger.ru/translations/functional-sharp-2/">Функциональный C#: Одержимость примитивами</a></li><li><a href="https://tproger.ru/translations/functional-sharp-3/">Функциональный C#: Ненулевые ссылочные типы</a></li><li><a href="https://tproger.ru/translations/functional-sharp-4/">Функциональный C#: Обработка исключений</a></li></ol><h3>Почему мы рассматриваем неизменяемые объекты?</h3><p>Самой большой проблемой корпоративного программного обеспечения является сложность кода. Читабельность — это, пожалуй, один из самых важных аспектов программирования. И вы должны стремиться к тому, чтобы ваш код был лаконичен. Код, написанный без учета этого фактора, очень сложно анализировать и проверять на корректность.</p><p>Давайте рассмотрим такой пример:</p><p>Будет ли изменен объект запроса к тому моменту, когда мы выполняем второй поиск? Может быть, да. А, может быть, нет. Это зависит от того, найдем ли мы что-нибудь, осуществив первый поиск. А еще и от того, изменятся ли критерии поиска после выполнения метода AdjustSearchCriteria. Проще говоря, мы не можем знать заранее, изменится ли объект запроса во втором поиске.</p><p>А теперь рассмотрим следующий код:</p><p>Вот здесь сразу все ясно: после того, как мы ничего не нашли во время первого поиска, метод AdjustSearchCriteria создаст новые критерии, которые в свою очередь будут использоваться во втором поиске.</p><p>Итак, какие существуют проблемы в работе с подвергающимися изменениям структурами данных?</p><ol><li>Трудно оценивать написанный код, если мы не можем быть уверенными в том, изменятся ли определенные данные или нет.</li><li>Довольно сложно следовать за многочисленными отсылками, если вам потребовалось взглянуть не только на сам метод, но и на функции, которые вызываются в этом методе.</li><li>Если же вы пишете многопоточное приложение, то отслеживание и отладка кода становятся еще сложнее.</li></ol><h3>Как описать неизменяемые объекты?</h3><p>Если у вас есть относительно простой класс, то вы всегда должны рассматривать вопрос о том, чтобы сделать его неизменяемым. Это правило коррелирует с понятием Value Objects — они просты и их легко сделать неизменяемыми.</p><p>Так как же нам описать неизменяемые объекты? Давайте рассмотрим пример: у нас есть класс ProductPile, представляющий некоторые продукты, которые мы выставили на продажу:</p><p>Чтобы сделать поля класса ProductPile неизменяемыми, мы отметим их доступными только для чтения и создадим конструктор:</p><p>Итак, чего же мы добились такой организацией класса?</p><ol><li>Теперь мы можем не волноваться о корректности данных — проверять значение мы будем только один раз в конструкторе.</li><li>Мы абсолютно уверены в том, что значения объектов корректны всегда.</li><li>Объекты автоматически становятся потокобезопасными.</li><li>Увеличилась читаемость кода, поскольку теперь нет необходимости проверять, не изменились ли значения объектов.</li></ol><h3>Ограничения в использовании</h3><p>Конечно, все имеет свою цену. Мы можем применить нашу идею в маленьких и простых классах, однако она совсем не применима к большим.</p><p>Прежде всего стоит отметить производительность. Если ваш объект получается достаточно большим, то создание его копий каждый раз при изменении какого-то параметра не лучшим образом скажется на быстродействии приложения.</p><p>Хорошим примером здесь являются неизменяемые коллекции (immutable collections). Их авторы учли проблемы с производительностью и добавили класс Builder, который позволяет “мутрировать”, изменять коллекцию:</p><p>Также вы встретите множество проблем, если попытаетесь сделать изменчивый по своей природе класс неизменяемым. Но пусть вас это не останавливает.</p><h3>Вывод</h3><p>Рассмотрите все достоинства и недостатки перевода объектов класса к неизменяемым, оцените затраты и не забывайте рассуждать трезво. В большинстве случаев вы получите выгоду (разумеется, в том случае, когда ваш класс достаточно маленький и простой).</p><p>Перевод статьи «Functional C#: Immutability»</p>]]></content:encoded>
    </item>
  </channel>
</rss>