ООП провалилось: осваивай функциональные языки прямо сейчас

ООП или объектно-ориентированное программирование – парадигма, которую порой позиционируют как решение всех проблем. Так ли это на самом деле?

ООП провалилось, осваивай функциональные языки прямо сейчас

На практике людям приходится держать в голове сложные абстракции и беспорядочные графики общих объектов, подверженных изменениям. В итоге драгоценное время и интеллектуальный потенциал тратится на обдумывания «абстракций» и «шаблонов проектирования» вместо решения реальных проблем.

Конечная цель разработки софта состоит в написании надёжного кода. Забагованному и ненадёжному коду не помогут никакие концепции. А лучший путь к надёжности кода – простота. Следовательно, главная задача разработчиков – снижение сложности кода.

Так в чём проблема?

ООП провалилось, осваивай функциональные языки прямо сейчас

«Объектно-ориентированные программы предложены как альтернатива правильным...»

– Эдсгер Вибе Дейкстра, один из разработчиков концепции структурного программирования.

Объектно-ориентированное программирование создавалось с одной целью: управление сложностью процедурного кода. Другими словами, оно должно было улучшить организацию кода. Между тем, не существует объективного и открытого доказательства превосходства ООП над чистым процедурным программированием.

Горькая правда состоит в том, что ООП терпит неудачу в единственной поставленной ему задаче. Оно выглядит хорошо на бумаге, где у нас чистые иерархии животных, собак, людей и так далее. Всё это рушится по мере роста сложности приложения. Вместо понижения сложности оно поощряет беспорядочное совместное использование изменяемого состояния и вносит дополнительную сложность с его многочисленными шаблонами проектирования. ООП усложняет рефакторинг и тестирование.

Многие не согласятся, но правда в том, что современное ООП никогда не разрабатывалось правильно. Оно никогда не опиралось на правильные исследовательские институты (в отличие от Haskell/FP). У ООП нет десятилетий строгих научных исследований. Лямбда-исчисления предлагают полную теоретическую основу для функционального программирования. У ООП нет ничего близкого к этому. По большому счёту, ООП «просто случилось».

ООП-код недетерминированный, в отличие от функционального программирования нам не гарантирован одинаковый вывод при одном и том же вводе. В качестве упрощённого примера: 2 + 2 или calculator.Add(2, 2) дают на выходе 4, но иногда могу дать 3, 5 или вообще 1004. Зависимости объекта Calculator могут менять результат вычислений в тонкой, но глубокой манере.

Необходимость в гибком фреймворке

«C++ – ужасный [объектно-ориентированный] язык... Ограничение вашего проекта до C означает, что люди не облажаются с какой-нибудь идиотской "объектной моделью"c&@p.» – Линус Торвальдс, создатель Linux.

Линус Торвальдс известен своей критикой в адрес C++ и ООП. Он прав в ограничении программистов. Чем меньше выбора у программиста, тем гибче становится его код. В цитате выше Линус Торвальдс настоятельно рекомендует использовать хороший фреймворк как базу для кода.

ООП провалилось, осваивай функциональные языки прямо сейчас

Многим не нравятся знаки ограничения скорости на дороге, но они необходимы для предотвращения несчастных случаев. Хороший фреймворк должен также предотвращать глупые действия.

Хороший фреймворк помогает писать надёжный код. Он должен помогать снижать сложность с помощью:

  1. Модульности и повторного использования.
  2. Правильной изоляции состояний.
  3. Высокого отношения сигнал/шум.

К несчастью, ООП предоставляет разработчикам слишком много инструментов без ограничений. Даже если ООП обещает обратиться к модульности и улучшению повторного использования, ему не удаётся выполнить обещания (далее подробней). ООП-код поощряет использование общих изменяемых состояний, которые снова и снова доказывают свою небезопасность.

Функциональное программирование

Некоторые люди склонны считать функциональное программирование очень сложной парадигмой, которую применяют только в научной среде, и которая непригодна для «реального мира». Конечно, это не так!

Да, функциональное программирование имеет под собой сильное математическое основание и уходит своими корнями в лямбда-исчисления. Но большинство идей возникли в нём, как ответ на слабость мейнстримных языков программирования. Функция – это основная абстракция функционального программирования. При правильном использовании функции дают модульность и повторное использование кода, невиданное в ООП.

Функциональное программирование отлично справляется с задачей написания надёжного софта. Необходимость в дебаггере полностью исчезает. Больше не нужно перемещаться по своему коду и отслеживать переменные.

Наконец, если вы умеете использовать функции, вы уже функциональный программист. Осталось научиться использовать функции наилучшим образом!

В ООП всё неправильно

«Мне очень жаль, что я давно ввёл термин "объекты" для этой темы, потому что он заставляет многих людей сосредоточиться на меньшей идее. Основная идея – обмен сообщениями.» – Алан Кэй, один из пионеров в областях объектно-ориентированного программирования.

Алан Кэй придумал термин «Объектно-ориентированное программирование» а 1960х. Благодаря своему биологическому бэкграунду он пытался создать коммуникацию между компьютерными программами по подобию живых клеток.

ООП провалилось: осваивай функциональные языки прямо сейчас

Основная идея Алана Кэя состояла в коммуникации между независимыми программами (клетками). Состояние независимых программ никогда не раскрывалось внешнему миру (инкапсуляция).

Вот и всё. ООП никогда не было предназначено для таких вещей, как наследование, полиморфизм, ключевого слова «new» и несчётного количества шаблонов проектирования.

Чистый ООП

Erlang – это ООП в чистом виде. В отличие от большинства мейнстримных языков, он сосредотачивается вокруг главной идеи ООП – обмена сообщениями. В Erlang объекты взаимодействуют путём передачи неизменяемых сообщений между собой.

А есть ли доказательства превосходства неизменяемых сообщений над вызовами методов?

Конечно! Erlang – это, возможно, самый надёжный язык в мире. Он поддерживает основную часть мировой инфраструктуры телекома (следовательно, и интернета). Некоторые системы, написанные на Erlang надёжны на 99.9999999%.

Сложность кода

Самый важный аспект разработки – это постоянное понижение уровня сложности. Красивые фичи теряют смысл в коде, который невозможно поддерживать. Даже 100% покрытие тестами ничего не стоит, если код становится слишком сложным.

Что усложняет код? В основном общее изменяемое состояние, ошибочные абстракции, низкое отношение сигнал/шум (часто вызываемое шаблонным кодом). Всё это распространено в ООП.

Проблема состояния

ООП провалилось: осваивай функциональные языки прямо сейчас

«Я думаю, что большие объектно-ориентированные программы борются со сложностью, возрастающей по мере построения большого графика изменяемых объектов. Ну, знаете, когда вы пытаетесь понять и держите в памяти то, что произойдёт при вызове метода, и какие будут побочные эффекты.» – Ричард Хикки, создатель языка программирования Clojure.

Состояние само по себе безвредно. Но изменяемое состояние – это большой нарушитель. Особенно если оно общее. Что такое изменяемое состояние? Любое состояние, которое может измениться. То есть, переменные или поля в ООП.

ООП ухудшает проблему организации кода, разбрасывая состояния по программе. Затем разрозненное состояние беспорядочно делится между разными объектами.

Проблема параллелизма

Беспорядочный доступ к изменяемому состоянию делает параллельность в таком коде невозможной. Для решения этой проблемы был предложен сложный механизм. Блокировка потоков, Мьютекс и другие сложные механизмы. Конечно, они имеют недостатки – взаимные блокировки, нехватка композиционности, а отладка многопоточного кода очень сложна и отнимает много времени.

Не все состояния – зло

Получается, состояния – зло? Нет, изменение состояния, наверное, нормально в случаях настоящей изоляции (не ООП-изоляции).

Вполне нормально иметь неизменяемые объекты передачи данных. Ключевое слово здесь – «неизменяемые». Такие объекты используются для передачи данных между функциями.

Так или иначе, такие объекты сделают методы и свойства ООП абсолютно избыточными. Какой смысл в методах и свойствах объекта, если они не могут быть изменены?

Троянский конь инкапсуляции

ООП провалилось: осваивай функциональные языки прямо сейчас

Нам говорили, что инкапсуляция – одно из главных преимуществ ООП. Она должна защищать внутреннее состояние объекта от внешнего доступа. С этим тоже есть небольшая проблема. Она не работает.

Инкапсуляция – это троянский конь ООП. Он продаёт идею общего изменяемого состояния под предлогом безопасности. Инкапсуляция позволяет (и даже поощряет) проникновение небезопасного кода в наши исходники.

Проблема глобального состояния

Нам говорили, что глобальное состояние – корень всех зол. Нужно избегать его, чего бы это ни стоило. Нам никогда не говорили, что инкапсуляция, по факту, вершина глобального состояния.

Для эффективности кода объекты передают не свои значения, а их ссылки. Вот где «внедрение зависимости» терпит неудачу.

Когда мы создаём объект в ООП, мы передаём ссылку на его зависимости в конструктор. Эти зависимости также имеют собственные внутренние состояния. Вновь созданный объект с радостью хранит ссылки на эти зависимости в своём внутреннем состоянии, а затем с радостью изменяет их любым удобным для него способом. А также он передаёт эти ссылки всему остальному, что он может в итоге использовать.

Это создаёт сложный график беспорядочных общедоступных объектов, которые меняют состояния друг друга. Что, в свою очередь, приводит к огромным проблемам, так как отследить причину изменения состояния программы становится практически невозможно. Можно потратить много дней на отладку подобных изменений состояния. И вам повезло, если вам не нужно разбираться ещё и с параллелизмом.

Методы/Свойства

Методы и свойства, которые предоставляют доступ к определённым полям ничем не лучше прямого изменения полей. Неважно меняете ли вы состояние объекта с помощью красивого свойства метода – результат один: изменённое состояние.

Альтернатива ООП

Спойлер: Функциональное программирование. :)

Если такие термины, как функторы и монады ни о чём вам не говорят, то вы не одиноки! Функциональное программирование не было бы таким пугающим имей оно более интуитивные названиями основных идей. Функторы – это то, что можно преобразовать с помощью функции, например, list.map. Монады – просто цепочка связанных вычислений!

Функциональное программирование сделает из вас лучшего разработчика. Вы наконец-то начнёте писать код, который решает проблемы реального мира, вместо того, чтобы тратить время на обдумывание абстракций и шаблонов проектирования.

Возможно, вы ещё не осознали себя функциональным программистом. Вы используете функции в повседневной работе? Да? Тогда вы уже функциональный программист! Осталось научиться эффективному использованию функций.

Два хороших функциональных языка с лёгким обучением – это Elixir и Elm. Они позволяют разработчику сосредоточиться на том, что действительно важно – на написании надёжного софта и удалении сложности традиционных функциональных языков.

Альтернативы? Ваша организация использует C#? Попробуйте F# – классный функциональный язык, совместимый с .NET. Используете Java? Тогда Scala и Clojure – неплохие варианты для вас. Используете JavaScript? С правильным руководством и линтингом JavaScript превращается в хороший функциональный язык.

А вы готовы отказаться от ООП?

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ