Что так с ООП и ФП, и что не так с программированием

    Так уж завелось на хабре, что на каждую холиварную статью «Pro» всегда найдётся статья «Contra».
    Я тоже решил не оставлять в одиночестве пост «Что не так с ООП и ФП».



    Прямо противоречить написанному смысла не имеет, там ведь описан «вкус», а, как известно, на вкус и цвет… каждый любит свой ЯП.
    Нет никакой проблемы с ООП и ФП. Чистота функций — это всего лишь инструмент, равно как и «всё является объектом».
    Критика права лишь в одном — в борьбе за миллиметры хорошо видны яркие прорехи, а в любых вырожденных методах недостатки ярко проявляются.
    Другое дело, что любые невырожденные методы так же имеют недостатки, только их больше, зато они не так легко заметны.
    Любители С++ гнобят полное ООП, Javа гнобит Си++ за неполное ООП, Haskell гнобит другие ФП за грязные функции, остальные ФП гнобят Haskell за излишне чистые функции.
    Всё имеет свою оборотную сторону медали.
    Тогда почему досталось всё объектам и функциям, а не массивам и указателям?!

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

    Пример 1

    У нас есть и PHP и сайт-визитка. Почти наверняка, там почти отсутствует не только фреймворки, но и вообще внятная структура.
    Если сайт этот будет медленно развиваться, у нас появятся функции, массивы, затем появятся объекты, вплоть до сильно-объектных фреймворков.
    Как мы видим, растущая сложность сайта заставляет вводить сложность в программы. Самое парадоксальное, что сложность вводится для уменьшения сложности (при этом переходя на более высокий уровень абстракции). Грамотно написанный сайт с использованием объектов проиграет, если всё переписать под процедурный подход.

    Пример 2

    Ассемблер. На нём можно написать даже операционную систему, как это делают, например, ребята из КолибриОС, но давайте будем откровенны, используют его там, где без него никак не обойтись.
    Для чего-то простого в основном используют С, значительно реже Паскаль, Бейсик. И на С можно ОС написать (например, тот же Линукс), но сейчас он уже значительно реже используется для крупных проектов. И это при том, что С — лингва-франка многих языков программирования.
    Растущая сложность алгоритмов уже не даёт интенсивно использовать возможности языка, приходится использовать экстенсивно!

    Сейчас для сложных проектов используется Java, С++, C#,… — высоко-объектные языки.
    Функциональное программирование можно рассматривать как альтернативный подход к решению сложности — Лисп и лисп-подобные языки сравнимы по мощности с С, OCaml сравним с С++ и даже выше (модули как функции имеют примерно ту же мощность, что и объекты), Haskell — выше Java (об этом ниже).

    Суть заключается в том, что каждые методы/функции/объекты/… решают лишь свои диапазоны сложности.
    И рассматривать 2+2 как объект(2).метод(+)(объект(2)) — говорит о том, что объект достаточно сложная сущность, и на нём лёгкие вещи просто не программируются. Такие языки, как Ruby помогают синтаксическим сахаром понизить этот порог сложности.

    Объекты


    Утверждается в той статье ни много ни мало, что «ООП, так и ФП неэффективны, если доходить в их использовании до крайности».

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

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

    Пример 3

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

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


    Классы типов


    Есть ли что-то, что более легко масштабируется, нежели объекты? Как я указал в другой статье — есть.
    Это тип-классы (они же классы типов) в Хаскеле, подобный механизм, который ввела Scala (хотя там механизм достаточно сложный), и объектный аналог — роли в Perl.
    Как видим, это далеко не самые популярные языки.
    Если посмотреть, видно, что ныне тип-классы обогнали по мощности объекты и идут по интенсивному пути. Они ещё не упёрлись в потолок.

    Вывод


    Сложность программ нарастает. С ООП и ФП всё в порядке. Не порядок лишь с быстрым масштабированием сложности, поскольку сложность растёт быстрее, нежели позволяют интенсивные возможности.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 35

      +15
      Я вот не понимаю всего этого холивара. Чего хотят добиться авторы?
      В моём понимании это всего лишь проблема масштабирования. Если у нас есть легковесная программа на 10-30 методов, то городить в ней ООП, да с неймспейсами, плюшками, рюшками и конструкторами с деструкторами… ну ведь бессмысленно, правда?
      Но если же у нас есть продукт, у которого по идеологии многие методы нацелены на работу с одинаковыми сущностями — почему бы это счастье не завернуть в класс SomeEntityManager и двигаться дальше. И при этом помнить знать, что за работу с SomeEntity отвечает свой SomeEntityManager и если нужно что-то допилить — пилить надо именно в нём.
      ООП не более, чем иерархия множества методов для удобства их использования. И это головная боль архитектора — выстроить понятную и очевидную систему, с которой сможет разобраться новый человек вовлечённый в проект.
      Может быть я и не прав, но моё видение ситуации сейчас именно такое.

      P.S. Поддерживаю точку зрения автора. С ООП и ФП всё в порядке, что-то не так с программированием и программистами.
        +3
        Скажу на примере веб-приложений. Как правило сейчас большинство веб-приложений пишутся «на коленке», чтобы проверить выстрелит или нет. Никто не хочет вкладывать время в проработку архитектуры проекта на случай, а вдруг будем дальше развивать. А вот если уже выстрелило, то приходится весь этот кошмар приводить в грамотный вид и переосмысливать архитектуру. Это про пример легковесной программы на 10-30 методов. Вот тогда случаются ситуации когда:
        а) некогда разбираться в проекте, а дополнительно накручиваются еще 100-300 методов
        б) проект теряет слишком много времени на рефакторинге и он уже никому не нужен
        Найти золотую середину очень трудно, когда и пишется все сразу легко и правильно.
          0
          А можно завернуть счастье в Erlang-процесс и посылать ему сообщения. Аналогично можно сделать и в Haskell, Scala. Но ООП это будет в Smalltalk'овском понимании, а не как в C++/C#/Java/etc.
            0
            мне кажется, что тут больше говорится о том, что на коленках получаются очень негибкие структуры, и для расширения функционала приходится хорошо попотеть, рефакторизируя код.
          +6
          Программа должна решать поставленную задачу. Эффективность — это способность программы решать задачу, простота в поддержке\доработке, и для бизнеса — стоимость разработки и поддержки.
          Т.е. Эффективность программы = {результат} / {время разработки + время поддержки}

          Те, кто не ставит это правило в базис своей деятельности — не могут считаться профессионалами.

          Если ты ставишь перед собой цель «написать программу ответа на главный вопрос жизни, вселенной и вообще» и на достижение результата кладешь пол жизни — твой подход можно считать эффективным.
          Если тебе надо написать одностраничный сайт-визитку, а на выходе получается одностраничный сайт-визитка, включающий в себя CRM+ORM, php переплетается с явой и всё это работает на кластере из 500-т машин, а код вызывает крепкий стояк, и ты тратишь на это пол жизни — ты не эффективен.

          Без сомнения, код должен быть логичным, правильным, понятным. Но в первую очередь — он должен дать ответ на поставленный вопрос за наименьшее время.
          А использование инструментов ради использования инструментов — это, как сказал один из хабражителей, как онанизм: приносит удовлетворение, но не результат.

            0
            Без сомнения, код должен быть логичным, правильным, понятным. Но в первую очередь — он должен дать ответ на поставленный вопрос за наименьшее время.

            Думаю правильнее назвать «за наименьшее количество ресурсов», где под ресурсами часто понимают деньги-люди-время — три взаимозависимых параметра.
              0
              На самом деле при прочих равных условиях время зависит от людей, а деньги — от времени.
              Но вы правы, да.
              +1
              >… как онанизм: приносит удовлетворение, но не результат.

              Ну так любой секс, не приводящий к зачатию, приносит удовлетворение, но не результат — предлагаете прекратить это бессмысленное занятие? ;)
                +1
                Да, метафора ограниченная. Расходимся.
              +6
              Предвижу статьи «Что так с ООП, ФП и с программированием, и что не так с программистами», затем последует цикл "… и что не так с тестарми", "… и что не так с менеджерами" и как завершающий аккорд "… и что не так с людьми".
                +4
                Что с тобой не так?
                  +1
                  С людьми всегда всё не так ;-)
                  +10
                  Мне кажется, проблема кроется в том, что для многих современных разработчиков ООП — это набор заученных ими правил, шаблонов проектирования и грамматик языков. Они не видят сути.

                  ООП — парадигма разработки, которая отлично привносит в процесс создания программ древнее как мир правило управления крупными системами: «разделяй и властвуй». Главная цель ООП — разбить «решателя» большой задачи на множество мелких и независимых, которые, будучи представленными в виде отдельных исполнителей, взаимодействуют друг с другом в рамках парадигм и протоколов конкретного языка кодирования. Метафорически мы вместо одного умного и хитрого «мастера» получаем «завод» с конвейером и четким разделением обязанностей между сотрудниками.

                  ООП — образ мышления. Инкапсуляция — средство самоограничения, созданное не для того, чтобы проще было написать правильный код, а для того, чтобы усложнить написание сбойного, но многие этого не понимают. Я не раз сталкивался с мнением типа: «зачем делать поле private, сделаем его лучше public — легче к нему добраться будет». Хочется спросить: «а почему у тебя есть личная жизнь, ведь она снижает твою эффективность как работника?» (Сравнение, конечно, ироничное и спорное, но оно раскрывает суть) За поле private отвечает сам объект. За поле public отвечает весь мир.

                  Люди не понимают, что контроллировать весь код одновременно — значит брать всю ответственность на себя. Если подумать о коде как о наборе «личных пространств» объектов с их зонами ответственности, мы сведем любую, самую сложную задачу к набору тривиальных. Из трудностей останутся только алгоритмы.

                  Прописные истины говорю, не так ли?

                  Однако есть куча разработчиков (в том числе очень маститых), для которых класс — это такая «коробочка с функциями и данными». И они занимаются тем, что разделяют данные от функций, разбивая их по разным классам: вот тут у нас класс-сущность, а вот тут у нас класс-работник. Хочется спросить: а зачем вообще использовать классы, если данные и функции — отдельно друг от друга. Эти люди боятся наследования, говоря, что при наследовании код становится неуправляемым. На самом деле просто надо четко понимать, где ставить «final». Эти же самые люди искренне считают, что сделать инкапсулированный объект — значит объявить в нём все поля private и для них всех вывести getter-ы и setter-ы. Зачем? Так надо. И тому подобное…

                  Но чтобы понимать всё это, надо для начала научиться думать в рамках одного класса и его зоны ответственности. Понимать это трудно, да. Но как в старом анекдоте, «никто не говорил, что должно быть легко».
                    +4
                    >Хочется спросить: а зачем вообще использовать классы, если данные и функции — отдельно друг от друга.

                    Потому что в мейнстрим-языках ничего кроме классов нет. Даже тупо статический метод надо в класс класть.
                      +2
                      Никто не против. Пусть пишут себе на функциях. Даже на Java можно писать C-way. Только не нужно при этом притворяться, что это — ООП. Создаём один единственный класс и пишем в нем все методы и поля статическими, чтобы всем было понятно — ООП мы не осилили.

                      Только не нужно так делать в проекте длиной больше 10000 строк кода. Или не нужно никому и никогда такой код показывать. Дурной пример заразителен.
                        –1
                        Мой опыт показывает что ООП в его чистом виде — не работает нормально даже на простых примерах. В чистом виде — это когда у нас у Employee есть метод IncreaseSalary(), ссылка на Department, и ссылка на Manager. И оно как-то само там друг-друга дергает и получается как надо.

                        Подход когда есть классы-сущности без private-состояния, есть сервисы без состояния вообще, есть какое-то AOP, немного функциональщины, и немного метапрограммирования всякого — то жить уже как-то можно.

                        Я этот подход использовал на проектах на порядки больше чем 10000 строк кода. Кому и когда я этот код я буду показывать я буду решать без учета мнения ортодоксальных ООП-шников…
                          +3
                          Я не знаю, что вы называете «ООП в чистом виде». Для меня ООП — это просто такой подход, при котором каждый объект имеет строго определенную зону ответственности и легко, без анализа его внутреннего устройства, а только на основе одной документации может быть перенесён куда угодно (например, вызван из Unit-test-а). Причем каждый объект (исключая компромиссы с целью оптимизации) проверяет, что ему передают и контроллирует, чтобы данные внутри него были корректны.

                          Каждый объект общается на том языке, который относится к его уровню абстракции. Например, если объекту Юзер приходит исключение вида ПолеВБазеПустое(Имя), он поймает его и кинет своё ИмяНеНайдено, таким образом скрывая в себе всё своё устройство.

                          Что из этого вы предлагаете нарушить и с какой целью?

                          Кому и когда я этот код я буду показывать я буду решать без учета мнения ортодоксальных ООП-шников…

                          Я про вас, кстати, ничего не говорил. И не надо такого резкого тона, как будто я вас жизни учу. Если вы на меня намекаете, я не против ФП как такового, тем более что оно прекрасно уживается с ООП в одной программе, если ООП держать на уровне Объект-тип данных.

                          Объекты без внутреннего состояния, разумеется, бывают. И никто не против таких объектов там, где они нужны. О чём вы, собственно? Никто же не говорил, что всегда обязательно нужно внутреннее состояние. Просто мне приходилось сталкиваться с ситуацией, когда внутреннее состояние явно нужно, но его изо всех сил пытаются избежать, вытаскивая наружу, потому что тупо боятся думать в объектной парадигме.

                          И про «никому не показывать код» я говорил, потому что натерпелся кода, написанного «как-бы-в-ООП», но так, что там присбое в уровне данных криво отображался интерфейс и приходилось всю программу насквозь отладкой проходить.
                            +1
                            В классическом ООП объекты — это что-то с внутренним состоянием, прикрытым внешним API, которое позволяет соблюсти какие-то внутренние инварианты этого состояния. В современном программировании на ООП-языках, такие объекты — скорее исключение, нежели правило.

                            Вот я сейчас смотрю на ASP.NET MVC-приложение, и не вижу там ничего такого. Ни контроллеры, ни entity в EF, ни view, ни всякие хелперы, никакие репозитории и фильтры — ничего из этого, с точностью до погрешности, не является объектом в классическом его понимании. Единственный объект — это контекст EF, который по сути — инкапсулированное состояние всей БД. А дальше — разнообразные обертки и проекции этого состояния, операции работающие через обертки и проекции,
                            и много всяких static-методов, с понятным входом и выходом.

                            Еще раз — ни контроллер, ни entity, ни view, ни какой-нибудь somethingManager, для меня не являются ООП-объектами. Во-первых они не проходят нормально под это определение. Во-вторых если перестать думать о них как об объектах, а как о чистых функциях, функциях в каком-то контексте, каких-нибудь линзах или монадах — все начинает играть новыми красками, и становится понятно как правильно строить архитектуру.

                            Возможно это вопрос терминологии. Впрочем, я не понимаю как его избежать в случае ООП — слишком уж пространны определения типа «объект — это кучка из стейта и методов».
                              0
                              Вот именно об этом я и толкую. Сам участвовал в подобном проекте и сам, вместе с другими 30 разработчиками, бродил по многочисленным граблям.

                              Сперва, чтобы вы опять не назвали меня воинствующим ООП-шником, заверю вас — чистого и аккуратного функционального подхода там было еще меньше, чем ООП. Была некоторая неаккуратная смесь.

                              А теперь про ваш пример. Я не говорю, что то, как вы это описываете — плохо. Я говорю, что если размышлять в тех понятиях и подходах, о которых говорите вы, то надо НЕ использовать в проекте язык Java, желательно не использовать C# и (если вы не мазохист), не рекомендуется C++. Берите то, что годится для такого подхода и нет проблем.

                              Вы утверждаете, что в вашем проекте всё построено так, что объектов с внутренним состоянием быть не может (кроме базы). Я поверю вам на слово. А вы мне поверьте, что я знаю, как это организовать так, чтобы эти объекты были, работали, упрощали жизнь (по сравнению с тем, что я видел). Я, например, знаю, как сделать Entity, которая сама обеспечивает актуальность своего содержимого и организовать всё таким образом, чтобы код уровня View вообще не подозревал ни о существовании базы, как таковой, ни о методах взаимодействия с оной, ни о кешировании, ни о сетевом взаимодействии. Для него существуют только объекты бизнес-логики, которые сами (!) обновляются (причем разумно и экономно), легко управляются им и легко передаются, куда следует.

                              Я сейчас работаю в проекте, в котором это внедрено именно в таком виде. Предыдущая версия была «почти функциональна» и чрезвычайно плохо разделена по слоям.

                              Разумеется, наш спор — обсуждения только вопросов вкуса. Но прошу вас, поверьте мне на слово — при должном умении можно писать на ООП очень сложные вещи, не ломая идеологии и они будут хорошо поддерживаемы и надёжны.
                            +2
                            Кстати, вы привели хороший пример плохого ООП.

                            1. В Employee никогда не может быть IncreaseSalary(), так как работник сам себе зарплату не повышает.
                            2. Ссылка на Department и на Manager прекраснейшим образом возможна, только надо учесть ряд ньюансов.

                            И главное:

                            3. оно как-то само там друг-друга дергает и получается как надо

                            Вот тут-то и есть главная ошибка. Если у объекта есть состояние более сложное, чем «имя», «возраст», «пол», для него надо разрабатывать блок-схему состояний с учётом различных особенностей его работы. «как-то само» действительно работать не будет.
                        +1
                        Инкапсуляция — средство самоограничения, созданное не для того, чтобы проще было написать правильный код, а для того, чтобы усложнить написание сбойного, но многие этого не понимают

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

                          +1
                          Ну так на здоровье — самоограничивайтесь, как вам больше нравится. Меня тут выше уже обозвали «ортодоксальным ООП-шником». Я же просто пытаюсь донести, что любой метод разработки сам по себе является самоограничением. А толк от такого самоограничения будет лишь в том случае, если ему удастся следовать строго.

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

                          Если вы можете исключить состояние — исключайте, флаг вам в руки. Но если вы его где-то исключить не сможете, будут вам страшные беды, ибо функциональный код трудно отлаживать в этом случае.

                          А вообще, в одной программе прекрасно можно подружить и ФП, и ООП. У этих подходов фундаментально различная область применимости, так что не подерутся.
                        +4
                        Философские статьи по программировании. Мем «бессмысленные и беспощадные» еще никогда не был более уместен.
                          +1
                          Философские статьи про программирование позволяют увидеть лес за деревьями.
                            0
                            Тому, кто смотрит с высоты. Опыта.
                              +1
                              Так то оно так… Но у каждого лес свой. На каждый пример можно привести контрпример. Спорить и обсуждать можно бесконечно.

                              Это не значит что обсуждать не нужно. Если сам процес приносит моральное удовлетворение участникам — почему бы и нет. Вот только узнать «истину» из такого спора не получится, так как её нет.

                              Любой устоявшийся инструмент, будь то язык или парадигма программирования, имеет свои плюсы и минусы (кстати, тоже не всегда четко определенные: что для одного баг, то для другого фича). А так как количество задач, которые можно решить в любом тьюринг-полном языке бесконечно, количество критериев для оценки тоже бесконечно.
                                +2
                                Полностью с вами согласен. Но я, например, вижу смысл этих споров в том, чтобы во-первых уточнить или пересмотреть собственную точку зрения (периодически заставляют и это хорошо), а во-вторых…

                                Разумеется, любым инструментом можно решить очень широкий класс задач. Можно, например, топором узоры на оконных рамах вырезать, как делали наши не очень далекие предки. А можно стамеской деревья рубить. Но лучше всё же наоборот. И хочется научиться понимать границу, где тот или иной подход/язык применим, а где он буксует.
                            +3
                            У вас хорошо получилась статья, но не хватает примеров. Без них как-то не ощущается солидности, правдивости слов. Без примеров трудно принять именно вашу точку зрения. Так, например, не показано, как классы типов позволяют решить задачу интенсивно, в то время как объекты уже решают проблему экстенсивно. Да везде бы примерами разбавить, и статья из хорошей получилась бы даже шикарной.
                              +1
                              Зачем пытаются отделить одно от другого — не ясно. Это все отлично дополняет друг друга, главное не уходить в крайности. Хороший программист напишет программу как с использованием только ФП, так и только с использованием ООП и все будет работать и все будет понятно. И хороший программист напишет программу с использованием и ФП и ООП, скорее всего это будет лучше, чем использование только одной парадигмы.

                              Главное — писать нормальный, понятный код. Остальное — холивар для тех, кто умеет пользоваться чем то одним и не умеет пользоваться другим.
                                +2
                                И хороший программист напишет программу с использованием и ФП и ООП, скорее всего это будет лучше, чем использование только одной парадигмы.

                                Даже если пишет на чистом С! :)
                                  0
                                  Согласен! Особенно хорошо получается объектная и функциональная декомпозиция на чистом C! ;)
                                  0
                                  Поскольку ООП и функции — это инструменты, то как известно плохому программисту мешают…
                                  А мастер даже на ассемблере напишет хороший код ))
                                  0
                                  Уважаемый Vitter, заинтересовала фраза:
                                  Лисп и лисп-подобные языки сравнимы по мощности с С
                                  Объясните, что вы имели ввиду?
                                  Дело в том, что с одной стороны, по мне, так Лисп намного мощнее, чем С, но с другой он не обладает его переносимостью и маленьким размером бинарника. Лиспы, в силу динамической природы, требуют для работы лисп-машину. Так что либо громадный бинарник, либо требуется другая VM (например JVM).
                                  Увлечённый Лиспом, я как раз искал такую реализацию, которая бы могла в своём результате работы приблизиться к С. Если вы такой знаете, то я бы тоже хотел узнать.
                                    0
                                    Лисп без скобочек — видел.
                                    Readable Lisp S-expressions
                                    Быстрый и компактно-бинарный Лисп — не видел. Но, я и не искал. Так что, может он и существует.

                                    Под мощностью я подразумеваю мощность выражения абстракций. У Лиспа — это функции и макросы, у С — это указатели, функции и сложные данные (массивы и структуры/записи). Более сложными абстракциями в этих языках не работают.
                                    0
                                    дубль

                                    Only users with full accounts can post comments. Log in, please.