Объектно-ориентированное программирование — чрезвычайно плохая идея, которая могла возникнуть только в Калифорнии.

— Эдсгер Вибе Дейкстра

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

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

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

Данные важнее, чем код


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

Этот момент очень важен, так что я повторюсь: цель -> архитектура данных -> код. Здесь порядок менять ни в коем случае нельзя! При проектировании программы нужно всегда начинать с выяснения цели, которой нужно достичь, а затем хотя бы приблизительно представить архитектуру данных: структуры и инфраструктуру данных, необходимые для её эффективного достижения. И только после этого нужно писать код для работы с такой архитектурой. Если со временем цель меняется, то нужно сначала изменить архитектуру данных, а потом код.

По моему опыту, самая серьёзная проблема ООП заключается в том, что оно мотивирует игнорировать архитектуру модели данных и применять бестолковый паттерн сохранения всего в объекты, обещающие некие расплывчатые преимущества. Если это подходит для класса, то это отправляется в класс. У меня есть Customer? Он отправляется в class Customer. У меня есть контекст рендеринга? Он отправляется class RenderingContext.

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

Мотивирование к сложности


При проектировании архитектуры данных в явном виде результатом обычно является минимальный необходимый набор структур данных, обслуживающих цель нашего ПО. Если мыслить в категориях абстрактных классов и объектов, то грандиозность и сложность абстракций сверху ничем не ограничивается. Просто взгляните на FizzBuzz Enterprise Edition — такую простую задачу можно реализовать в столь большом количестве строк кода лишь потму, что в ООП всегда есть место для новых абстракций.

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

Повсюду графы


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

У вашего class Customer есть ссылка на class Order, и наоборот. class OrderManager содержит ссылки на все Order, а потому косвенно и на Customer. Всё стремится ссылаться на всё остальное, потому что постепенно в коде появляется всё больше мест, ссылающихся на связанный объект.

Вам нужен был банан, но вы получили гориллу, держащую банан, и целые джунгли.

ООП-проекты обычно выглядят не как качественно спроектированные хранилища данных, а как огромные спагетти-графы объектов, указывающих друг на друга, и методы, получающие огромные списки аргументов. Когда вы начинаете проектировать объекты Context просто для того, чтобы урезать количество передаваемых туда-сюда аргументов, то понимаете, что пишете настоящий ООП-код уровня Enterprise.

Задачи поперечных срезов


Подавляющее большинство существенного кода не работает всего с одним объектом, а на самом деле реализует задачи поперечных срезов. Пример: когда class Player ударяет при помощи метода hits() class Monster, где на самом деле нужно изменять данные? Величина hp объекта Monster должна уменьшиться на attackPower объекта Player; величина xp объекта Player должна увеличиться на уровень Monster в случае убийства Monster. Должно ли это происходить в Player.hits(Monster m) или в Monster.isHitBy(Player p)? Что если здесь нужно учитывать и class Weapon? Мы передаём аргумент в isHitBy или у Player есть геттер currentWeapon()?

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

Шизофреническая инкапсуляция объектов


Давайте взглянем на определение инкапсуляции:

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

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

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

По моему мнению, классы и объекты слишком дробные, и с точки зрения изоляции, API и т.д. лучше работать в пределах «модулей»/«компонентов»/«библиотек». И по моему опыту, именно в кодовых базы ООП (Java/Scala) модули/библиотеки не используются. Разработчики сосредоточены на том, чтобы соорудить ограждения вокруг каждого класса, не особо задумываясь над тем, какие группы классов в совокупности формируют отдельную, многократно используемую, целостную логическую единицу.

На одинаковые данные можно смотреть по-разному


ООП требует упорядочивать данные негибким образом: разделять их на множество логических объектов, что определяет архитектуру данных — граф объектов с относящимся к ним поведением (методами). Однако часто полезно бывает иметь разные возможности логического выражения манипуляций с данными.

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

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

Низкая производительность


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

Какой же подход использовать вместо ООП?


Я не думаю, что существует «серебряная пуля», поэтому просто опишу то, как это обычно сегодня работает в моём коде.

Первым делом я изучаю данные. Анализирую, что поступает на вход и на выходы, формат данных, их объём. Разбираюсь, как данные должны храниться во время выполнения и как они сохраняются: какие операции должны поддерживаться и с какой скоростью (скорость обработки, задержки) и т.д.

Обычно если данные имеют значительный объём, моя структура близка к базе данных. То есть у меня будет некий объект, например DataStore с API, обеспечивающим доступ ко всем необходимым операциям для выполнения запросов и сохранения данных. Сами данные будут содержаться в виде структур ADT/PoD, а любые ссылки между записями данных будут представлены в виде ID (число, uuid или детерминированный хеш). По внутреннему устройству это обычно сильно напоминает или на самом деле имеет поддержку реляционной базы данных: Vecторы или HashMap хранят основной объём данных по Index или ID, другие структуры используются как «индексы», необходимые для выполнения быстрого поиска, и так далее. Здесь же располагаются и другие структуры данных, например кеши LRU и тому подобное.

Основная часть логики программы получает ссылку на такие DataStore и выполняет с ними необходимые операции. Ради параллелизма и многопоточности я обычно соединяю разные логические компоненты через передачу сообщений наподобие акторов. Пример актора: считыватель stdin, обработчик входящих данных, trust manager, состояние игры и т.д. Такие «акторы» можно реализовать как пулы подпроцессов, элементы конвейеров и т.п. При необходимости у них могут может быть собственный или общий с другими «акторами» DataStore.

Такая архитектура даёт мне удобные точки тестирования: DataStore могут с помощью полиморфизма иметь различные реализации, а обменивающиеся сообщениями экземпляры акторов могут создаваться по отдельности и управляться через тестовые последовательности сообщений.

Основная идея такова: только потому, что моё ПО работает в области, где есть концепции, например, клиентов и заказов, в нём не обязательно будет класс Customer и связанные с ним методы. Всё наоборот: концепция Customer — это всего лишь набор данных в табличной форме в одном или нескольких DataStore, а код «бизнес-логики» непосредственно манипулирует этими данными.

Дополнительное чтение


Как и многое в проектировании программного обеспечения, критика ООП — непростая тема. Возможно, мне не удалось чётко донести свою точку зрения и/или убедить вас. Но если вы заинтересовались, то вот ещё несколько ссылок:

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


  1. faoriu
    17.05.2019 08:06
    +3

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


    1. passer-by
      17.05.2019 13:08

      те языки и подходы, которые в итоге оказывались самыми практичными и популярными

      Популярность — продукт рекламы и продвижения крупными компаниями. Качество при этом и рядом не валялось. Только финансовые интересы.

      А с учетом такой популярности про практичность говорить бессмысленно.

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

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

      А большинству современных программистов до Дейкстры, как до Шанхая…


      1. EvilsInterrupt
        17.05.2019 14:04

        Прикладное программирование — это когда школота лепит быдлокод?

        Ну, если школота прямо сейчас решит задачу, которая позволит уже завтра продать продукт, то почему нет? У меня есть один коллега, все ворчит, что код не тот. Но когда ему говоришь «ОК. У тебя появляются возможность 2 дня писать код без оплаты». Он вдруг задает вопрос, а почему это?

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

        Хотим мы с вами или нет, но мы решаем задачи бизнеса. А пользователь на бизнес стороне, как правило, понятия не имее про ООП или BDD или синглтон. Ему наплевать что там в кишках! Он просто тыкает кнопку и если она не решает его задачу он не платит денег. И хоть вы там идеальную архитектуру наколбасите ему будет пофиг, лишь бы кнопка делала то что надо!


        1. Alexeyslav
          17.05.2019 14:51
          +1

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


          1. khim
            18.05.2019 02:28

            Собственно всё упирается в том, что мы не умеем делать хорошие программы. Не в том смысле, что технологий нет. Нет культуры. Как в 90е, когда «новые русские» ринулись строить свои безумные котеджи.

            Когда вас просят «добавить рюшечку» и вы это делаете — без плана и дизайна, то получается пресловутый Дом Винчестеров.

            Как-то примерно так


            1. xfaetas
              18.05.2019 09:30

              На самом деле методы написания хороших программ были придуманы и стандартизованы ещё во времена первых Space Shuttle. Просто до сих пор мало кто в это умеет: из-за холиварщиков типа Дейкстры программисты уже три десятка лет занимаются поиском самого "правильного" языка программирования и обсиранием "неправильных" языков: это как если бы электронщики каждые 5-10 лет придумывали новый набор обозначений на схемах для отображения всё тех же резисторов, транзисторов и конденсаторов.


              А решение проблемы находится на более высоком уровне: на уровне организации, методик и подходов.


              1. khim
                18.05.2019 16:24

                А решение проблемы находится на более высоком уровне: на уровне организации, методик и подходов.
                … которые не позволяют даже NASA избежать ошибок…

                Обозначения тоже важны, просто в последнее время им уделяется куда больше внимания, чем следует.


              1. NetBUG
                18.05.2019 16:25

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


                1. xfaetas
                  18.05.2019 23:02

                  Это уже скорее аналог добавления новых функций в библиотеку: никто не пересматривает обозначения резистора, например, или сложения в математике.


                  1. playnet
                    21.05.2019 11:42

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


                    1. mayorovp
                      21.05.2019 14:19

                      Всё в порядке с диодами, просто не надо путать направление тока и направление движения электронов!


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


              1. dimm_ddr
                20.05.2019 11:08

                На самом деле методы написания хороших программ были придуманы и стандартизованы ещё во времена первых Space Shuttle.
                И сколько такие методы будут стоить компаниям? В том-то и проблема что хорошесть программ может определяться очень разными способами, не существует универсального параметра качества. И стоимость разработки решения практически всегда вносит определенный вклад в то, насколько программа хороша для ее заказчика.


          1. Nalivai
            18.05.2019 03:08

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


          1. EvilsInterrupt
            20.05.2019 15:10

            Нужно исходить из ситуации. Какие могут вообще ситуации?
            1. Пользователь решил проблему. Больше не пришел
            2. Кроме одного пользователя эту сделанную фичу больше никто не использовал
            3. Пользователь решил проблему. Пришел с багой\предложением

            и еще 100500 вариантов развития будущего.

            Вы уверены, что в написанном коде вам действительно придется ковыряться и что-то переделывать? Да, можно вспомнить закон Мерфи про то что худшее по-любому случится, если оно может случиться. Но на практике вариаций развития будущего масса! Как надо будет переписывать так и не надо, а иногда и оказалось не надо и лучше удалить код, нет кода — нет багов!

            Мы же когда пишем код мы же обычно так поступаем:
            Встретилась задача просто пишем код. Встретилась второй раз тупо копи-пастим. Встретилась в третий — выносим в отдельную функцию. Втретилась в четвертый — выносим в библиотеку.

            Ну так и тут надо также! Сначала вы просто пишете код. Да, стараетесь написать по-лучше. Но слишком заморачиваться не стоит, т.к. «лучшее враг хорошего». И уж если потом потребуется тогда и сделаете как надо, т.е. тогда, когда это действительно будет оправдано.

            Другими словами: Если значимость и ценность кода еще не доказана требованиями бизнеса, то тратить на него слишком много времени и усилий не следует. Исходить нужно из математической фразы «необходимо и достаточно»


            1. VolCh
              20.05.2019 15:21

              Исходить нужно из математической фразы «необходимо и достаточно»

              Я всё же предпочитаю делать небольшой "запас прочности", да хотя бы чтобы удалить больше ненужный код было просто без особых рисков сломать что-то.


        1. scoffer-by
          17.05.2019 22:10

          Все так. Вот в чистом виде конфликт между экономикой для результата и экономикой для прибыли.

          Во второй половине XX века во время холодной войны наука и технология бурно развивались за счет заказов от военно-промышленного комплекса. Ну и в виде побочного продукта доставалось гражданке.

          Сейчас военные расслабились, вместо реальной работы придуряются, активно пилят огромные бюджеты. Результат на лицо.

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

          Правильные языки никому не нужны, энтузиасты без финансирования даже если сделают, без агрессивного продвижения никто не будет такой язык использовать.

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


          1. farcaller
            18.05.2019 12:55

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

            А разве таковым не был Dart который не взлетел?


            1. scoffer-by
              18.05.2019 13:29

              Не знаю, не знаком. А вот то, что взлетело, это как птица в том анекдоте — летит и гадит.


              1. ApeCoder
                18.05.2019 14:28
                +1

                Сейчас уже есть typescript который потихонечку взлетает и webassembly


          1. evocatus
            19.05.2019 00:45

            Думаю, что Джон МакКарти (отец LISP, 50-е годы) и многие другие великие и умные люди сказали бы вам много причин, по которым статический контроль типов не нужен.


            1. khim
              19.05.2019 01:07

              Не нужен — да, обходился же LISP без него. Полезен? Ну я думаю Джон МакКарти не стал бы заниматься LISP 2 с поддержкой статической типизации, если бы он считал эту идею бессмысленной.

              Другое дело, что оказалось не так-то просто добавить статическую типизацию в LISP и не потерять его выразительности…


            1. 0xd34df00d
              19.05.2019 01:17

              Думаю, что многие другие великие и умные люди вроде Пирса, Барендрегта, Мартина-Лёфа или Жирарда с ним бы с интересом побеседовали о нужности типов.


              1. evocatus
                19.05.2019 10:14
                -1

                И Алан Кей и Рич Хики говорят, что ещё не видели системы типов, которая бы помогала, а не мешала, и не причиняла бы боль. Очень возможно, что из Haskell/Rust/F# сообщества появится такая система типов. А до тех пор у нас будут void *, interface {} и пр. в языках, которые имеют статическую типизацию.

                Самая лучшая апологетика динамической типизации (не с ура-пофигистских позиций типа «а нафига типы?»), которую я видел, здесь:
                youtu.be/YR5WdGrpoug
                youtu.be/2V1FtfBDsLU?t=1143 (можно даже отсюда начать: youtu.be/2V1FtfBDsLU?t=1639)


                1. 0xd34df00d
                  19.05.2019 17:04

                  И Алан Кей и Рич Хики говорят, что ещё не видели системы типов, которая бы помогала, а не мешала, и не причиняла бы боль. Очень возможно, что из Haskell/Rust/F# сообщества появится такая система типов.

                  Она, ну, именно что уже есть. Пишу на хаскеле, система типов очень помогает, ни на что её не променяю.


                  А до тех пор у нас будут void *, interface {} и пр. в языках, которые имеют статическую типизацию.

                  Ни разу не использовал void* даже в C++, ЧЯДНТ?


                  Самая лучшая апологетика динамической типизации (не с ура-пофигистских позиций типа «а нафига типы?»), которую я видел, здесь

                  Расшифровка есть?


                  1. evocatus
                    19.05.2019 17:58

                    Расшифровка есть?

                    Текст выступления? Или перевод на русский?
                    Насколько я знаю, ни того, ни другого нет.


                    1. 0xd34df00d
                      19.05.2019 21:08

                      Текст, лучше в оригинале.


                      Жалко, смотреть видео — ну такое.


                  1. Ryppka
                    19.05.2019 18:52
                    -1

                    Ни разу не использовал void* даже в C++, ЧЯДНТ?

                    Прекрасная позиция, ведь за Вас его используют авторы библиотек. )) Вот пусть они и отдуваются)))


                    1. khim
                      19.05.2019 20:34

                      А чем плоха позиция? Goto за нас ставит компилятор — и вроде как уже все смирились с с тем, что это его работа.

                      А void* — да, с ним авторы библиотек пусть возятся. Это их работа.


                      1. Ryppka
                        19.05.2019 20:53
                        -2

                        Ну да, Вы же весь в белом! Зачем Вам руки пачкать? Так?


                    1. 0xd34df00d
                      19.05.2019 21:16
                      +1

                      Какие авторы?


                      Я, если что, писал свой any и свой variant. Даже там как-то удалось обойтись без void*.


                      Единственная библиотека, которую я могу вспомнить и которая использует void* достаточно активно — Qt, да и то потому, что они так реализуют сигналы/слоты для совместимости со старыми компиляторами.


                      1. Ryppka
                        19.05.2019 21:20
                        -1

                        Я, если что, писал свой any и свой variant. Даже там как-то удалось обойтись без void*.

                        Было бы занятно взглянуть. Это возможно?


                        1. 0xd34df00d
                          19.05.2019 21:23
                          +1

                          Это оба раза было на интервью, так что при всём желании тот код я выложить не смогу.


                          Но, если в общем говорить, any делается через type erasure, variant — на самом деле даже проще, через вариадики.


                          1. Ryppka
                            19.05.2019 21:32
                            -1

                            Никогда толком не вникал, что такое type erasure, но интуитивно тот же void*, вид сбоку. А в Вашем variant на вариадики (шаблоны, я верно понял?) можно менять содержимое на другой тип после объявления?
                            Если Вы используете такой срез C++, где не нужно явно использовать просто машинный адрес и/или взаимодействовать с C — почему нет. Но я бы не стал этим гордится)))


                            1. 0xd34df00d
                              19.05.2019 21:37

                              Никогда толком не вникал, что такое type erasure, но интуитивно тот же void*, вид сбоку.

                              Не, ну если под определённым углом...


                              А так — указатель на базовый класс, от которого наследуется шаблонный производный класс. Всё. Никаких void*, всё очень типобезопасно.


                              А в Вашем variant на вариадики (шаблоны, я верно понял?) можно менять содержимое на другой тип после объявления?

                              Вы имеете в виду такое?


                              variant<int, std::string> v { 42 };
                              v = "42";

                              Если такое, то да, можно. Там внутри тоже в каком-то смысле type erasure вылезет, но за счёт того, что список типов известен статически, можно даже в RTTI не лазать.


                              Если Вы используете такой срез C++, где не нужно явно использовать просто машинный адрес и/или взаимодействовать с C — почему нет. Но я бы не стал этим гордится)))

                              Ну, под атмеги я тоже когда-то писал, ещё когда школотроном был. Кстати, тоже void* особо не нужно было, всё очень быстро обмазалось темплейтами, но не суть.


                              А если вы пишете на настолько низком уровне, то потребность в void* обусловлена не какими-то недостатками статической типизации, а самой сутью предметной задачи. И смоллтолк с джаваскриптом тут бы вряд ли помогли.


                              1. Ryppka
                                19.05.2019 22:04
                                -1

                                А так — указатель на базовый класс, от которого наследуется шаблонный производный класс. Всё. Никаких void*, всё очень типобезопасно.

                                И указатель на этот самый базовый класс и оказывается нижней гранью выбранной «подсистемы» типов. Но C++ не ограничивается только этой «подсистемой», поэтому void* в нем необходим.
                                Я понял, что Вы имеете в виду. Согласен, для каждого конкретного случая можно обойтись без void*, зафиксировав нужные типы в параметрах шаблонов.
                                Но не согласен, что void* не является полноценным элементом системы типов. Как и просто void, в шаблонах он тоже вылазит и, по слухам, там без него не обойтись. То, что void* можно использовать для обхода типизации — следствие наличия в языке низкоуровневых средств и особенностей типа «указатель на» (преобразование T*->void*->T* безопасно). Именно это средство C++ делает возможным «бескомпромиссную совместимость» с миром C и с аппаратурой, например.
                                Так что гордится тем, что ни разу не использовал void* не стоит: просто повезло запереть себя в «чистой комнате». Как-то так)


                                1. 0xd34df00d
                                  19.05.2019 22:44
                                  +1

                                  Но не согласен, что void* не является полноценным элементом системы типов.

                                  Является, конечно, обратного никто и не говорил. Наоборот, там выше было утверждение (на которое я и отвечал), что void* прям нужен в C++, иначе никуда.


                                  То, что void можно использовать для обхода типизации — следствие наличия в языке низкоуровневых средств и особенностей типа «указатель на» (преобразование T->void->T безопасно). Именно это средство C++ делает возможным «бескомпромиссную совместимость» с миром C и с аппаратурой, например.

                                  T* -> char* -> T* тоже безопасно, кстати. Или через std::byte теперь.


                                  Впрочем, давайте с другой стороны. Почему при работе с железом нельзя обойтись без void*?


                                  1. Ryppka
                                    20.05.2019 10:39

                                    Впрочем, давайте с другой стороны. Почему при работе с железом нельзя обойтись без void*?

                                    Возможно, это просто историсеское наследие, но думаю, что преобразование беззнаковых целых в указатели так или иначе включает «создание» void* из целого. Указатель ведь, по сути, структура с битовыми полями и кучей атрибутов, и на низком уровне на него накладываются ограничения, которые, ну, скажем, невыразимы в языке, по крайней мере в текущем виде. Нужен какой-то тип данных, который позволит все это делать. Я так думаю. Это может быть и не видно явно, для C и даже C++ такая спрятанная в компилятор неявность — дело обычное.
                                    И да, указатель на байт может заменить указатель на void. Но что это даст в плане типобезопасности? Те же яйца, вид сбоку, в gnuc void* ведет себя, как byte*.
                                    Вон сколько уже ISO/IEC TR 18037 все не примут, так и нужда пройдет.


                                    1. 0xd34df00d
                                      20.05.2019 16:41

                                      Возможно, это просто историсеское наследие, но думаю, что преобразование беззнаковых целых в указатели так или иначе включает «создание» void* из целого.

                                      Зачем? reinterpret_cast<Foo*>(0xd34df00d). Снова никаких void*.


                                      1. Ryppka
                                        20.05.2019 17:27

                                        Если честно, то не задумывался, что можно через reinterpret_cast, пребывал в заблуждении, что все-таки нужен указатель. Не сказал бы, что это чем-то лучше.
                                        Остался в языке для совместимости с C, видимо.
                                        Сколько безобразного кода люди готовы написать только для того, чтобы гордо заявлять «мне void* не нужен!»… )))


                                        1. 0xd34df00d
                                          20.05.2019 17:42

                                          Ну, я стараюсь писать код так, чтобы выбранные средства языка передавали намерение максимально точно.


                                          Да и как вы в C++ из void* получите более типизированный указатель, кроме как кастом? Это в C он сам ко всему приводился.


                                          1. Ryppka
                                            20.05.2019 22:22

                                            В молодости я тоже так думал.)))
                                            А сейчас и (void*) кажется годным, да и пишу больше на C…
                                            Хотя согласен, если уж плюсы, то лучше если все по взрослому, чтобы без слез на код не взглянуть. Более строгая типизация, чего уж там.


                  1. Druu
                    20.05.2019 10:40
                    +1

                    И Алан Кей и Рич Хики говорят, что ещё не видели системы типов, которая бы помогала, а не мешала, и не причиняла бы боль.

                    Ну вы не ровняйте всех программистов с Кеем и Хикки. Им, может, и не помогает, а программистам ниже уровня — помогает вполне.
                    Я вот дурачок, самому думать трудно, по-этому мне часто проще, когда за меня думает тайпчекер.


                    Самая лучшая апологетика динамической типизации

                    Рассуждать о динамике вс статике сейчас имеет смысл исключительно в контексте тайскрипта. С-но, значительную часть проблем тс решает, из оставшейся части значительную часть — не решает, но оно в принципе решаемо.


                    1. 0xd34df00d
                      20.05.2019 16:46

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


                      1. Druu
                        21.05.2019 10:39

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

                        Да, сорри. Запутался в ветках :)
                        Это был ответ на пост evocatus, конечно же.


        1. EvgeniiR
          18.05.2019 11:07

          Ну, если школота прямо сейчас решит задачу, которая позволит уже завтра продать продукт, то почему нет?

          Потому что предпочтительнее считается смотреть не только на один день вперед, но ещё и на два, а иногда и ещё больше, например?

          Если вы конечно не фрилансер, или не аутсорс контора которая не заботиться о своей репутации и стремиться выжать из заказчика денег любым способом и распрощаться


          1. dimm_ddr
            20.05.2019 11:15
            +1

            Потому что предпочтительнее считается смотреть не только на один день вперед, но ещё и на два, а иногда и ещё больше, например?
            Далеко не всегда. Стандартная ситуация с начинающей компанией — есть предположение что наше решение решит чью-то проблему. Можно долго создавать крутое решение, потратить кучу денег на него, а можно быстро накидать МВП и выкатить его. В случае если решение действительно нужное — да, первый вариант, скорее всего, в итоге окажется выгоднее. Если не совсем и нужно допиливать — возможно и так, и так. Если совсем нет, то, очевидно что второй вариант потратит заметно меньше ресурсов. И даже в первом случае выкатить что-то на говнокоде и допиливать потом имеет смысл. Потому что оно принесет денег вот сейчас — тех самых денег, которые нужны чтобы таки сделать хороший продукт. Совсем не факт что такие деньги на старте вообще есть.
            Я что пытаюсь сказать на самом деле — заглядывать в будущее конечно хорошо и правильно, вот только никто не умеет это делать надежно. И в условиях ограниченных ресурсов вариант делать сейчас как кажется правильным, а что будет потом — будет потом, вполне может оказаться выгоднее.


            1. EvgeniiR
              20.05.2019 12:32

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

              А речи про крутое решение за куччу денег не было, можно и MVP накидать нормально, а можно сделать уже абсолютно нерасширяемую программку.

              очевидно что второй вариант потратит заметно меньше ресурсов

              martinfowler.com/bliki/DesignStaminaHypothesis.html

              И даже в первом случае выкатить что-то на говнокоде и допиливать потом имеет смысл

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

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

              А может совсем и не оказаться. Вопрос в том, происходит ли оценка рисков, или higest payed person/product owner считает что «так выгоднее», потому что ни капли не программист и далёк от критериев качества ПО.


              1. VolCh
                20.05.2019 13:09

                Если конкуренты начинают с нуля, то и мы можем начать с нуля, уже имея своих пользователей.


                1. EvgeniiR
                  20.05.2019 14:06

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


                  1. VolCh
                    20.05.2019 14:09

                    Легко можно продать:


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

                    :)


                    1. EvgeniiR
                      20.05.2019 14:58

                      Это на словах легко )
                      Может, как я и писал, можно, но я не слышал о таких success story, возможно потому что объяснять уже нужно будет не только бизнесу, а ещё и юзерам/инвесторам как-то сознаться что в продукте все не так радужно.
                      Возможно потому, что, опять же, дорого, конкурентов надо как-то обгонять.


                      1. VolCh
                        20.05.2019 15:05

                        Я понимаю, потому и смайлик поставил. На самом деле при разработке чего-то вроде MVP и его дальнейшем развитии я стараюсь искать баланс между скоростью и поддерживаемостью/расширяемостью/… Благо веб-разработка позволяет относительно малой кровью совмещать старый и новый код даже на разных фреймворках, языках, ОС и прочих платформах, часто всего лишь изменением конфига реверс-прокси.


              1. dimm_ddr
                20.05.2019 14:20

                За ссылку на Фаулера спасибо (хотя я подозреваю что конкретно это я уже читал), но речь не о том, чтобы меньше тратить на дизайн. Речь о том, что есть множество неизвестных. Можно попробовать долго и упорно уменьшать их количество на стадии дизайна — организовывать опросы, разбирать детально предметную область, может быть заказать парочку исследований даже. И только после этого закончить дизайн. А можно — разработать дизайн с кучей неизвестных, выкатить демо версию и посмотреть на реакцию. Я понимаю что аналогия будет выглядить не очень уместной, но мне кажется она подходит: это как с тестовым запуском ракеты — есть масса неизвестных, которые можно промоделировать за большие деньги и долго, а можно быстрее и дешевле (не всегда конечно) сделать прототип, запустить его и просто все что нужно измерить.

                конкуренты будут делать качественный и стабильный продукт, выкатывая новые фичи в срок и не ломая старый фукционал.
                Чтобы получилось как вы говорите нужно чтобы конкуренты умели сделать быстро и качественно то, что мы умеем либо быстро, либо качественно. В рельности же скорее всего конкуренты после нашего MVP будут все же на шаг позади. И если они начнут долго и упорно пилить идеальный продукт, то о них можно вообще не думать, к тому моменту как они закончат рынок уже будет поделен среди тех кто выкатывал быстро но не очень качественно.
                Вопрос в том, происходит ли оценка рисков, или higest payed person/product owner считает что «так выгоднее», потому что ни капли не программист и далёк от критериев качества ПО.
                А этот вопрос вообще за рамками текущего обсуждения. То же самое может произойти в любом случае.


                1. EvgeniiR
                  20.05.2019 15:03

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

                  Ну и обычно MVP всё же часть будущего продукта, а не вещь которую скоро выкинут


                  1. dimm_ddr
                    20.05.2019 15:56

                    Дело в том что проект и с кучей неизвестных можно сделать более менее качественным и расширяемым, и не иметь необходимости его переписывать.
                    Я в этом не уверен. В каких-то частных случаях — наверняка. Но мне не сложно представить случай когда расширяемость будет сделана не для того и не туда для чего и куда она будет нужна. Можно попробовать запланировать расширяемость во все возможные направления, но результат будет так себе выглядеть и работать будет, скорее всего, совсем не просто. И даже так для меня не очевидно что это в принципе всегда возможно.


                    1. EvgeniiR
                      20.05.2019 16:55

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


                      1. VolCh
                        20.05.2019 18:59

                        Изменения изменениям рознь. И ошибки могут быть не только в проектировании, но и в постановках.


                      1. dimm_ddr
                        21.05.2019 11:19

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


                        1. EvgeniiR
                          21.05.2019 12:07

                          Но не потому что плохо подумали, а потому что предположили другое будущее.

                          Предполагать будущее — так себе затея.
                          Если мы пока что не предполагаем, например, что наше приложение будет использовать другие устройства ввода/вывода, это вовсе не повод забыть про абстрагирование от них, и лепить прямую зависимость из всех модулей что с i/o работают.

                          Я не спорю что ошибки будут всегда, проектированием мы стараемся сократить их количество, и упростить процесс их исправления.

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

                          Ну и время на создание MVP все же оптимизируется сокращением и упрощением фич, а не выкидыванием этапа проектирования/рефакторинга.


            1. playnet
              21.05.2019 12:05

              > есть предположение что наше решение решит чью-то проблему.

              Быстро сделав прототип «тяп-ляп», мы получаем:
              — возможность рекламировать что-то относительно реальное, а не «вот через 10 лет будет вау»
              — Инвесторы гораздо легче одобрят что-то прототипное прямо сейчас, чем графики-картинки и сказки как будет круто
              — можно набирать пользователей, получать реальную обратную связь, действительно полезные замечания внедрять сразу
              — В конце концов, это даст возможность оценивать реальную нужность идеи.

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


      1. Flammar
        17.05.2019 15:17

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


        1. Alexeyslav
          20.05.2019 14:29

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


      1. xfaetas
        17.05.2019 15:57

        Много используете программ, написанных Дейкстрой? Я — ни одной.


        1. megasuperlexa
          17.05.2019 16:08
          +5

          почти как сказать «много ли вы используете компьютеров, сделанных Тьюрингом?»


          1. xfaetas
            17.05.2019 16:31
            +1

            Это примерно как если бы Тьюринг давал советы по пайке и разводке инженерам, делавшим ENIAC.


            1. masai
              17.05.2019 19:09
              +5

              Учитывая, что Тьюринг занимался проектированием компьютера ACE, возможно, он смог бы что-то дельное посоветовать и разработчикам ENIAC.


    1. And390
      17.05.2019 14:03

      те языки и подходы, которые в итоге оказывались самыми практичными и популярными

      А, например, COBOL вы в ту же категорию относите?


      1. yarric
        17.05.2019 16:09
        +2

        На COBOL крутится очень много бизнес-логики, на нём до сих пор пишут ПО. Раз его используют спецы в этой области — значит для своих задач он хорош. Мнение человека вроде Дейкстры, не имеющего опыта в энтерпрайзе, годится лишь для холиваров. Для меня вообще удивительно, что он допускал такие резкие высказывания — видимо в академической среде какая-то своя атмосфера: не шарящим студентам, лижущим тебе задницу ради оценки, всегда можно навешать на уши х*ету.


        1. PsyHaSTe
          17.05.2019 16:10
          +2

          Раз его используют спецы в этой области — значит для своих задач он хорош.

          Нет, не значит. Скорее это значит что так 'исторически сложилось'


          1. xfaetas
            17.05.2019 16:24

            Во всяком случае это решать спецам, а не холиварщикам со стороны.


          1. red_andr
            17.05.2019 17:14

            Насчёт «исторически» возможно отчасти и так, я Кобол не знаю, но я знаю Фортран, ещё более древний язык. По большому счёту, это первый высокоуровневый язык программирования вообще. И я знаю, что в науке и технике он до сих пор широко применяется. Просто потому, что он на самом деле самый удобный и быстрый язык для численных расчётов. Причём даже в новых проектах с нуля, без наследия 60-х годов. И сейчас нет проблемы конвертировать Фортран в другие языки. Первые трансляторы, например на Си, появились как минимум 30 лет назад.


            1. 0xd34df00d
              17.05.2019 20:15

              Просто потому, что он на самом деле самый удобный и быстрый язык для численных расчётов.

              Как там с аналогом плюсовых expression templates?


              То есть, ну, лет 20 назад, когда компиляторы оптимизировали хуже, а паттерны метапрограммирования были не столь распространены, на тех же плюсах писать эффективный код было сложнее, да, охотно верю. Но то 20 лет назад.


              1. Ryppka
                17.05.2019 21:46
                +1

                Последние исследования по expression templates, которые видел, утверждают что они уступают вручную оптимизированному коду. Года 4-5 назад, но уже не помню подробностей.
                Мне кажется, что ET все-таки скорее средство «быстрой разработки» численных расчетов, дающий вполне достойную, но не пиковую производительность. Может быть, требования к срокам и стоимости разработки стали жестче и теперь нельзя шлифовать код до идеала?


                1. 0xd34df00d
                  17.05.2019 22:08

                  Там же не зря ещё «удобный» упоминается. А так-то понятно, что любой результат, полученный с использованием хоть плюсов, хоть фортрана, можно написать на ассемблере.


              1. red_andr
                18.05.2019 20:04

                Аналогов в самом языке ещё нет. Кое-что делается оптимизирующим компилятором, также есть внешние инструменты типа Parametric Fortran и Fypp.
                Я не зря написал «удобный и быстрый». Expression templates в C++ это просто кошмар какой то. Чтобы их использовать в реальных задачах нужен настоящий эксперт в С++, который потратил годы на обучение и изучение всех тонкостей языка. Это реально эзотерическое программирование. А отладка? Сообщения об ошибках в шаблонах способны заставить рыдать даже закалённого бородатого кодера. Нет, это не подходит для программирования научного кода, который обычно пишется учёными, а не профессиональными программистами. Конечно, можно нанять и их. Например у нас в организации на каждый десяток physical scientists есть один или пара computer scientists. Но оказалось, что их усилия гораздо эффективнее направить на другие методы оптимизации, например переписать под многопоточность или векторные операции, ручную оптимизацию кода наконец.


                1. Druu
                  18.05.2019 21:50

                  Expression templates в C++ это просто кошмар

                  А это вообще в реальности кто-то использует? В чем проблема использовать внешний кодогенератор?


                  1. 0xd34df00d
                    18.05.2019 23:16

                    Я использую.


                    Проблемы с кодогенераторами в том, что их интегрировать тяжело. Если у меня есть модуль класс для работы с SQL, то я хочу иметь возможность прям там написать что-то вроде (пример почти из жизни)


                    auto GetRecentRSSNews(FeedID feedId)
                    {
                        return m_myOrm->Select(field<Feed::id> == feedId &&
                                field<Channel::feedId> == field<Feed::Id> &&
                                field<Item::channelId> == field<Channel::id> &&
                                field<Item::date> >= yesterday());
                    }

                    без того, чтобы туда как-то ещё кодогенерацию вкорячивать.


                    А как-то построить дерево выражения, протайпчекать его и, возможно, соптимизировать позволяют именно expression templates.


                    1. Druu
                      19.05.2019 10:31

                      А есть какие-то фундаментальные причины, по которым в язык не могут прикрутить нормальные макросы, с которыми это все было бы ez? Как я понимаю, востребованность есть, в чем затык? Просто ретроградство тех, кто пишет стандарт, или что-то более серьезное?


                      1. 0xd34df00d
                        19.05.2019 17:05

                        Это сложно сделать правильно сразу, а у тех, кто пишет стандарт, есть большое желание делать правильно сразу.


                        Впрочем, метаклассы отчасти помогают, компилтайм-рефлексия поможет, на CppNow вот какой-то чувак аналог гигиенических макросов предлагал.


                1. 0xd34df00d
                  18.05.2019 23:18

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


                  Ну и да, я не считаю себя топовым спецом по C++, но в expression templates нет ничего сложного. И, спасибо clang, сообщения об ошибках уже давно весьма хороши.


                  1. khim
                    18.05.2019 23:27

                    И, спасибо clang, сообщения об ошибках уже давно весьма хороши.
                    Они, кстати, вполне неплохи и в последних версиях gcc… но это потому что их clang заставил…


                    1. 0xd34df00d
                      18.05.2019 23:35

                      Я ченджлог gcc 9 читал не так давно и поймал себя на том, что там как-то прям даже вкусно сделали.


                      Да, спасибо clang'у за то, что возродил конкуренцию среди компиляторов.


                  1. red_andr
                    21.05.2019 07:37

                    Да, я смотрел эти библиотеки, а именно Eigen и Blaze. Увы, они не быстрее давно известных библиотек, написанных на обычном Фортране типа Intel MKL.


            1. scoffer-by
              18.05.2019 23:08
              -2

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


              1. elser
                19.05.2019 03:44

                Всегда поражаюсь людям, стремящимся обосрать js на основе одного лишь предвзятого отношения к языку, который да, не без странностей, но при этом позволяет использовать любую парадигму программирования, а в современных реалиях показывает буквально торжество принципов функционального программирования в противовес тяжелому ООП подходу. Самый яркий пример — React: До 15 версии всё плясало вокруг классов, затем народ, прям удивительно, но всё больше и больше начал отдавать предпочтение функциональным pure компонентам и в итоге в 16 реакте мы наблюдаем феерический разворот от классов к функциональному стилю )


                1. TheShock
                  19.05.2019 05:02

                  в 16 реакте мы наблюдаем феерический разворот от классов к функциональному стилю )
                  Вы хотели сказать «к процедурному»? Тот феерический ужас, который они реализовали с useState не имеет ничего общего с фп, а просто какая-то хрень от современных ооп-хейтеров, чтобы не писать гадкое слово class


                  1. Druu
                    19.05.2019 10:25
                    +2

                    Вы хотели сказать «к процедурному»? Тот феерический ужас, который они реализовали с useState не имеет ничего общего с фп, а просто какая-то хрень от современных ооп-хейтеров, чтобы не писать гадкое слово class

                    Самое смешно в данной ситуации то, что useState — это, по факту, бедная реализация методов классов на замыканиях из SICP :)
                    То есть, вместо того, чтобы использовать нативные классы, ребята просто сделали свою параллельную костыльную ООП-подсистему.


                1. 0xd34df00d
                  19.05.2019 17:05

                  но при этом позволяет использовать любую парадигму программирования

                  Как там с type-driven development в стиле Idris, например?


                  1. khim
                    19.05.2019 20:38

                    Не трогайте их. А то они сейчас компилятор Idris в js напишут и скажут, что его возможности — это возможности js.

                    Люди вечно путают тьюринг-полноту с возможностями языка. Так-то можно сказать что любой язык с eval поддерживает любую, мыслимую и немыслимую, парадигму — просто потому, что он полон по Тьюрингу.


                    1. 0xd34df00d
                      19.05.2019 21:19

                      А то они сейчас компилятор Idris в js напишут и скажут, что его возможности — это возможности js.

                      Поздняк.


        1. And390
          17.05.2019 17:57
          +2

          Как уже написали, аргумент «раз на нем пишут, значит он хорош» очень спорный. Дейкстра может быть прав или нет по поводу COBOL, ваше право с ним не соглашаться, но для критики языка программирования не обязательно иметь какой-то особый «опыт в энтерпрайзе». Скорее, теоретические знания и опыт разработки других языков программирования более релевантны. И, критика, вообще, вещь полезная для индустрии.


          1. xfaetas
            17.05.2019 18:22
            -2

            В энтерпрайзе одни подходы, в академии — другие. А ещё вспоминается поговорка про плохого танцора.


          1. dimm_ddr
            20.05.2019 11:23

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


            1. PsyHaSTe
              20.05.2019 11:28

              На коболе просто написанно много плохого кода, который переписывать очень страшно и чревато большими простоями и рисками. Одновременно с этим на этом не лучшем языке люди не хотят писать, поэтому их заманивают большими деньгами. Ну примерно как кроссовер платит 50 баксов в час за проекты в стиле Delphi + .Net 1.1. Знаю из первых рук.


              Вот так и складывается "стоимость специалистов необходимого уровня".


              1. dimm_ddr
                20.05.2019 12:32

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


                1. PsyHaSTe
                  20.05.2019 12:43

                  Он не хорош для энтерпрайза, просто выхода другого нет особо. По крайней мере для бизнеса, который "не парится", и не хочет инвестировать на сроки в 5-10 лет.


                  1. dimm_ddr
                    20.05.2019 14:22

                    Я с этим и не спорю. Я только говорил что для действительно адекватной и предметной критики языка для энтерпрайза нужно все-таки понимать реалии энтерпрайза. Чисто академические знания здесь не подходят.


            1. And390
              20.05.2019 13:18

              Много занимался корпоративным ПО на Java и все никак не могу понять о каком таком особом энтерпрайзе идет речь. Зато видел тонны плохо кода, подходов и инструментов, которые можно смело критиковать после одного взгляда ни них. К слову, иногда неадекватность какого-то решения легче заметить человеку «со стороны», чем уже привыкшему к нему. ИМХО, энтерпройзу нужен универсальный язык программирования и адеквантные инструменты, по большей части тоже довольно универсальные. То есть, определенные отличия в мире корпоративной разработки, конечно, есть — вспоминается статья Джоэла Спольски «Пять миров» — но все это не существенно для написанного выше. Если я не прав, то тогда что имеется ввиду под словом энтерпрайз и какие там особые задачи, которые не подчиняются общим принципам и для них нужен особый язык?


              1. dimm_ddr
                20.05.2019 14:27

                Много занимался корпоративным ПО на Java и все никак не могу понять о каком таком особом энтерпрайзе идет речь. Зато видел тонны плохо кода, подходов и инструментов, которые можно смело критиковать после одного взгляда ни них.
                Суть в том, что бизнес не очень волнует качество кода само по себе. Его волнует стоимость получения результата и, иногда, стоимость последующей поддержки и развития. И если нужен сайт визитка, который будет таким и оставаться, а при необходимости чего-то более крутого просто перепишется, то совершенно неважно какой там отстойный код напишет нанятый джун. И нанятый джун на «плохом» JS, например, будет гораздо дешевле чем нанятый джун с плюсами. Хотя бы потому что первый получит результат куда быстрее. При этом его код может быть максимально отвратителен — но кого это волнует? То есть приоритеты другие, академичность и правильность by the book могут легко быть неприменимы в бизнесе. Это не значит что обязательно будут, просто нужно понимать когда и почему они все-таки применимы. А это очень сложно сделать без опыта работы на собственно бизнес.


                1. VolCh
                  20.05.2019 14:38
                  +1

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


                  1. dimm_ddr
                    20.05.2019 14:57
                    +1

                    Это нюансы уже. Я всего лишь пытался показать как энтерпрайз может не беспокоиться о качестве кода, я не пытаюсь сказать что так происходит всегда. Суть моего комментария в том, что у бизнеса есть другие параметры кроме красивости кода. И из-за этого лучшим может оказаться дурно пахнущий код про который любой специалист скажет что так писать нельзя.


                    1. VolCh
                      20.05.2019 15:00

                      Это да. Всегда когда речь идёт о красивости и идиоматичности кода вспоминаю про WordPress :)


                1. And390
                  20.05.2019 15:17
                  +1

                  бизнес не очень волнует качество кода само по себе. Его волнует стоимость получения результата и, иногда, стоимость последующей поддержки и развития.
                  Согласен.
                  То есть приоритеты другие, академичность и правильность by the book могут легко быть неприменимы в бизнесе.
                  В некотором смысле да, но в то же время, в бизнесе используются инструменты и подходы рожденные в академической среде. То есть, если кто-то для простой разовой задачи начнет использовать некий академический подход требующий избыточного количества времени, то это конечно ошибка. Но вроде бы подобной массовой проблемы и нет, когда академики ломают бизнес. Зато, например, тот же джун может с пользой для себя и своей продуктивности потратить время на изучение какой-то «академической» литературы.


                  1. dimm_ddr
                    20.05.2019 16:01

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


                    1. EvgeniiR
                      20.05.2019 16:58

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


    1. evocatus
      17.05.2019 18:52
      +4

      Ну, его крестовый поход против goto увенчался успехом…

      А с учётом того, что он внёс значительный вклад в разработку Алгола, который является предком подавляющего большинства современных ЯП…


      1. xfaetas
        17.05.2019 19:37

        Самое смешное, что goto так никуда и не делся, поскольку выходить из вложенного цикла без него ещё не научились.


        1. Space__Elf
          17.05.2019 19:54
          +1

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


          1. OldGrumbler
            17.05.2019 21:11
            +4

            А еще можно сравнить итоговый код в машинных командах и офигеть, насколько короче и быстрее тот некошерный goto, чем Ъ и обобренный брейк… )))
            А потом посчитать, насколько уродливее и тяжелее та кажущаяся «красота» на уровне human-readable абстракций…
            )))


            1. MarazmDed
              17.05.2019 22:01
              +1

              А еще можно сравнить итоговый код в машинных командах и офигеть, насколько короче и быстрее тот некошерный goto, чем Ъ и обобренный брейк… )))

              Борьба с goto велась всегда с оговоркой «не используйте его без надобности». Выход из вложенных циклов — это один из немногих осмысленных кейсов goto.


              1. mayorovp
                18.05.2019 01:01

                Не всегда. Видел я кандидата наук, которая считала break разновидностью goto и снижала за него баллы на ЕГЭ...


                Да и тут на Хабре похожие кадры попадаются.


                1. MarazmDed
                  18.05.2019 20:38

                  Видел я кандидата наук, которая считала break разновидностью goto и снижала за него баллы на ЕГЭ...

                  Это — клиника и не лечится. Я тоже таких к.т.н. видел.
                  Препод зачем-то вела уравнения мат. физики. При этом ни хрена в этом не соображала. Было забавно, когда студенты исправляли косяки в ее лекциях, потому что она не втыкала где у нее ошибка.


                  1. Nimtar
                    20.05.2019 20:47

                    Это — клиника и не лечится.

                    Может, какие светила медицины попробуют? Готов стать подопытным.

                    Чем break не разновидность goto?


                    1. picul
                      20.05.2019 21:52
                      +1

                      Думаю, фундаментом для этого вывода стало «снижала за него баллы на ЕГЭ».


                    1. Ryppka
                      20.05.2019 22:25

                      Тем, что не continue? ;)


                    1. MarazmDed
                      21.05.2019 08:49
                      -1

                      Чем break не разновидность goto?

                      Тем, что бездумное использование goto — резко снижает читаемость кода. Использование break — зачастую необходимость.
                      Но исходно речь шла про то, что занижать оценки за использование break — глупость. Хотя остается вариант, что это интерпретация студента, и оценка была занижена за что-то осмысленное, а не за break.


                      1. mayorovp
                        21.05.2019 09:47

                        Оценка была занижена потому что использование break нарушает теорему Дейкстры. Это не интерпретация, это точная цитата с апелляции.


                        1. MarazmDed
                          21.05.2019 10:27

                          Оценка была занижена потому что использование break нарушает теорему Дейкстры. Это не интерпретация, это точная цитата с апелляции.

                          Ну так вы текст программы приведите — посмотрим. Судя по всему, break как таковой ни при чем.


                          1. mayorovp
                            21.05.2019 11:10

                            Там был обычный цикл с выходом по-середине:


                            repeat
                                {foo}
                                if {some_condition} then break;
                                {bar}
                            until false; 

                            Написать его по-другому без дублирования кода невозможно (а за дублирование кода тоже штрафуют).


                            1. PsyHaSTe
                              21.05.2019 11:23

                              Чтобы не быть голословным пример из моего солидити кода


                              for (uint64 i = uint64(dates.length - 1); ; i--) {
                                  if ((!latestStatusIndex.hasValue || dates[i] > dates[latestStatusIndex.value]) && isDistributionStatus(statusCodes[i])) {
                                      latestStatusIndex = Types.OptionU64(true, i);
                                  }
                                  if (i == 0) {
                                      break;
                                  }
                              }

                              переменная i — unsigned, поэтому проверять на i < 0 в условии цикла нельзя.


                              1. picul
                                21.05.2019 13:04

                                Можно так:

                                uint64 i = uint64(dates.length);
                                do
                                {
                                    --i;
                                    if ((!latestStatusIndex.hasValue || dates[i] > dates[latestStatusIndex.value]) && isDistributionStatus(statusCodes[i])) {
                                        latestStatusIndex = Types.OptionU64(true, i);
                                    }
                                }
                                while (i > 0);
                                

                                Если что, я не против break (и не против goto, честно говоря).


                                1. PsyHaSTe
                                  21.05.2019 13:26

                                  Во-первых в солидити (как и многих других языках) нет do циклов.
                                  Во-вторых этот код сломается если dates пустой массив.


                                  1. picul
                                    21.05.2019 13:47
                                    +2

                                    Ваш вариант вроде тоже ломается, разве нет?


                                    1. PsyHaSTe
                                      21.05.2019 14:32

                                      Вот так мы и нашли баг :)


                                      Начал думать, почему никогда не проявлялось, вспомнил, что я массив инициализирую всегда одним элементом, а потом он только растет. Так что вы правы.


                                      Правда, про do момент остается.


                                      1. picul
                                        21.05.2019 15:18

                                        Про do — OK, для меня просто слово «солидити» ничего не означало тогда — принял его за название проекта, а код за плюсовый. Так то согласен, без цикла с пост-уловием — или дублирование, или break.


                            1. Ryppka
                              21.05.2019 12:07

                              Я что-то не догоняю, паскаль не помню, наверное. until false — это do {...} while(1); или do {...} while(0);?


                              1. mayorovp
                                21.05.2019 14:21

                                until — это противоположность while. Так что until false — это while(1).


                            1. MarazmDed
                              21.05.2019 13:29
                              -1

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

                              использование break нарушает теорему Дейкстры.

                              я могу трактовать как «преждевременный выход из цикла нарушает теорему Дейкстры». Т.е. претензия не к break'у, а к алгоритму. Чтобы это проверить — нужен текст программы, а вы его не привели.


                              1. mayorovp
                                21.05.2019 14:29

                                Вы издеваетесь или как? Как я вам вспомню какую задачу я решал на экзамене 12 лет назад и какой код при этом написал?


                                И ладно бы это был самый сложный экзамен всей моей жизни, так ведь нет, это был очередной экзамен на внимательность при заполнении бланка...


                                я могу трактовать как «преждевременный выход из цикла нарушает теорему Дейкстры». Т.е. претензия не к break'у, а к алгоритму.

                                Во-первых, теорема Дейкстры на самом деле называется теоремой Бёма — Якопини.


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


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


                              1. PsyHaSTe
                                21.05.2019 14:36
                                +1

                                Типичный пример полезного брейка:


                                let user_input: u32 = loop {
                                   if let Ok(user_input) = read_line().parse() {
                                      break user_input;
                                   }
                                   println!("Неверный ввод: ожидалось беззнаковое число");
                                }


            1. bgBrother
              18.05.2019 10:33

              А еще можно сравнить итоговый код в машинных командах и офигеть, насколько короче и быстрее тот некошерный goto, чем Ъ и обобренный брейк… )))
              Даже более. В машинных командах в любом случае будет аналог JMP операнда, который тот же Goto.

              По сути, дело в абстракциях и удобстве.


              1. Alexeyslav
                20.05.2019 14:25

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


                1. mayorovp
                  20.05.2019 15:47

                  А так-то вещь весьма удобная — вжик и вышел. А тут оказывается что надо не просто выйти а ещё и ресурсы освободить, файлы закрыть и т.д.

                  RAII?


                  Break — он не просто вызывает выход из цикла, попутно под капотом он может ещё и переменную/коллекцию освобождать.

                  Как он это сделает и почему в той же ситуации goto этого сделать не сможет?


                  1. Alexeyslav
                    20.05.2019 16:31

                    goto — это безусловный оператор перехода, ничего кроме перехода он не может делать по определению. Break — это внутренняя кухня, и компилятор знает что в случае его вызова нужно вызвать код завершения цикла и выйти с него освободив что положено. Программист тоже может так сделать, но ошибиться на порядки легче и не освободить то что нужно освободить и перейти не туда куда нужно так что компилятор не сможет даже теоретически проверить безопасно ли выходить из цикла или участка кода таким вот образом.


                    1. mayorovp
                      20.05.2019 17:09

                      Странное какое-то определение, учитывая что компилятор C++ таки вставляет вызовы деструкторов перед goto...


                      1. Alexeyslav
                        21.05.2019 09:08
                        -1

                        Ага, прикрутили костыль. Очередной.


        1. Ryppka
          17.05.2019 21:47
          +2

          Без него и освобождение ресурсов в C затруднительно))


          1. xfaetas
            17.05.2019 22:36
            +2

            Да и эксепшены по-сути эмулируют goto.


            1. Ryppka
              17.05.2019 22:40

              Не совсем. Исключения еще дают гарантии выполнения деструкторов, там где релевантно.


              1. Fedorkov
                18.05.2019 08:04
                +1

                goto тоже гарантирует. stackoverflow.com/q/3179936


        1. sumanai
          17.05.2019 21:49
          +1

          PHP умеет, просто после него ставится число, из скольких циклов выходить. А все его ругают.


        1. Bronx
          18.05.2019 10:32

          Да запросто:


          for(...) {
             for(...) {
                if (condition) return;
             }
          }

          Да-да, цикломатическая сложность >= 2 — повод выделить этот код в отдельную функцию


          1. xfaetas
            18.05.2019 10:55

            Во-первых воротить отдельную функцию чисто для подцикла не делает код чище и понятнее, чем с одним goto, во-вторых — может потребоваться перейти на 2 цикла вверх или что-то подобное. А если это какая-то операция с тензорами, то отдельные функции просто для прохода каждого измерения не выглядят сильно оправданными.


            1. Bronx
              18.05.2019 12:04
              +1

              Как правило это именно что делает код чище и понятнее, потому что это двойное-тройное ж-ж-ж-ж-ж обычно неспроста, а является какой-то условно "неделимой" операцией (поиск первого элемента удовлетворяющего условию), и ей обычно можно дать вполне нормальное имя типа FindFirst(coll, condition).


        1. evocatus
          19.05.2019 01:02
          +1

          Я бы в таком случае засунул вложенный цикл в функцию и вместо goto использовал return


    1. ddinochrome
      17.05.2019 23:58

      Если язык популярный, это не всегда значит, что он — практичный, то есть оптимальный для решения практических задач. Как по мне так Дейкстра видел перспективу на десятилетия вперёд, а любители «практического программирования» иногда не видят и на десятилетия назад)


      1. xfaetas
        18.05.2019 09:20

        Просто есть люди, которые пишут большие и некрасивые, но полезные программы, а есть люди, которые пишут маленькие и красивые, но бесполезные программы. То, что выглядит красиво и хорошо работает в последних, не работает в первых.


        Методики и подходы написания надёжных программ были выработаны ещё в 80х при создании софта для SpaceShuttle и к данному моменту стандартизованы. И ключ совсем не в выборе самого лучшего языка программирования, а в организации процесса.


        1. ddinochrome
          18.05.2019 10:02

          Я написал коммент про языки программирования. И про то, что оценивать уровень Дейкстры не каждому из нас под силу. Скажите, пожалуйста, при чём тут красивый и некрасивый код, шаттлы и надёжность?) У меня просто жестокий разрыв контекста сейчас.


          1. xfaetas
            18.05.2019 10:16

            Основная идея в том, что если Дейкстра или кто-либо другой профессионально не писал программы для энтерпрайза и сложных систем, то не ему критиковать подходы, практикуемые там. Вроде бы очевидно, нет? Дальновидность его в области промышленного ПО можно оценить по тому, что он критиковал как раз те подходы, которые до сих пор является широко используемыми (хоть и критикуемыми), и, думаю, здесь дело не в повальной умственной отсталости тех, кто их использует. Проблема же написания качественного софта, которую Дейкстра пытался решать критикой (уровня обычного обсирания) отдельных технических подходов и языков, лежит не в плоскости языка, а в плоскости организационных подходов и методик — например таких, какие были выработаны при разработке ПО для Space Shuttle, а надёжное ПО, используемое десятки лет, можно писать хоть на ассемблере — например тот же MVS.


            1. ddinochrome
              18.05.2019 11:44
              +1

              Википедия про Дейкстру:

              В 2002 году получил ежегодную премию, вручаемую Симпозиумом по принципам распределённых вычислений (англ. Symposium on Principles of Distributed Computing) Ассоциации вычислительной техники «за публикацию, оказавшую наибольшее влияние на область распределённых вычислений»; в знак признания заслуг учёного с 2003 года эта премия носит название премии Дейкстры.

              Действительно, откуда ему что-то знать?)))


              1. xfaetas
                18.05.2019 11:50


                Какую, говорите, премию он там получил?:)


                1. ddinochrome
                  18.05.2019 12:00
                  -1

                  Много — не значит хорошо. Ещё есть аргументы?)


                  1. xfaetas
                    18.05.2019 12:05

                    Премия за научную публикацию — не значит практический опыт программирования в отрасли. И вы же сами первый захотели заняться писькомерством.


                    1. ddinochrome
                      18.05.2019 12:08
                      -1

                      Ооо, вот оно как вы развернули. К сожалению, на этом я вынужден оставить Вас наедине с самим собой. Удачи в измерениях!


                1. EvgeniiR
                  18.05.2019 12:23
                  -1

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


                  1. xfaetas
                    18.05.2019 12:31

                    Не сказал бы, что COBOL кто-то сильно рекламировал. Скорее наоборот: такие, как Дейкстра, вели против него собственную кампанию-холивар.


                  1. ddinochrome
                    18.05.2019 15:57
                    +1

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


                    1. TheShock
                      18.05.2019 18:02

                      Можно не рекламировать плохую технологию, и она всё равно захватит мир, как какой-нибудь jQuery
                      А чем jQuery — плохая технология? Для своего времени он был крайне актуальным и ушел, когда стал не нужным.


                      1. ddinochrome
                        18.05.2019 18:29
                        -1

                        Есть миллион причин почему jQuery плох во все времена. Одна из них — он нереально усложняет отладку кода. Вторая — в динамике разработки; сначала код пишется чуть быстрее, а потом гораздо-гораздо медленнее, чем на чистом JS. В основном это происходит из-за потери гибкости, внесения дополнительных и совершенно ненужных абстракций и правил.
                        На самом деле чистый JS весьма неплохо спроектирован — атомарно и консистентно, на нём можно быстро писать эффектные прототипы. А продакшн-разработку можно вести на TypeScript.


                        1. TheShock
                          18.05.2019 23:14

                          Вы застали веб-разработку в 2009-м?


                          1. ddinochrome
                            19.05.2019 11:06

                            Я перешёл на web в 2013-м. Я не никогда не занимался фронтендами для масс маркета. Мне достаточно, чтобы веб-приложение работало на одном браузере.
                            Но даже если думать о реализации кроссбраузерности через jQuery. Да, он даёт полезный функционал, но при этом также подсовывает очень опасную архитектуру, от которой проект сильно деградирует. Для примитивного лендинга-однодневки это нестрашно. Для сложного web-приложения это большая проблема.


                            1. TheShock
                              19.05.2019 13:51

                              но при этом также подсовывает очень опасную архитектуру
                              Не подсовывает он никакую архитектуру. Это библиотека, а не фреймворк. Он:
                              1. Делает прозрачную кроссбраузерность
                              2. Дает более короткий, чем в браузере API

                              Всё. Эти вещи никак не влияют на архитектуру. На архитектуру могли влиять некоторые плагины. Но всё зависит от программиста


                        1. sys_int64
                          19.05.2019 03:53

                          То что у вас код со временем пишется гораздо-гораздо медленнее говорит лишь о плохом проектировании софта, тоже самое лего получить и с чистым JS. Сейчас то конечно да, jQuery уже не актуальный и вплоне хватает чистого ванильного JS, но в свое время он был просто жизненно необходим, чтобы нормально писать кроссбраузерные сайты, потому-что раньше браузеры не следовали стандартам и делали все по своему.


                          1. ddinochrome
                            19.05.2019 11:00

                            То, что Вы делаете выводы о качестве проектировании софта, который не видели, говорит лишь о плохом воспитании.


                        1. mayorovp
                          19.05.2019 11:02

                          А что не так с отладкой кода на jQuery?


                          1. ddinochrome
                            19.05.2019 11:09
                            -2

                            Я попробовал, мне не понравилось. Если Вам нравится — не смею мешать — наслаждайтесь процессом)


              1. faoriu
                18.05.2019 12:44
                -1

                Если я буду выбирать инструмент для получения премии — я поучусь у Дейкстры, если мне понадобиться инструмент для реализации обработки транзакций — я поучусь у тех, кто её реализует.


                1. EvgeniiR
                  18.05.2019 13:01

                  Если я буду выбирать инструмент для получения премии — я поучусь у Дейкстры, если мне понадобиться инструмент для реализации обработки транзакций — я поучусь у тех, кто её реализует.

                  Боюсь спросить, а какие-нибудь Дядя Боб да Фаулер для вас тоже так, академики, далекие от кода?)


                  1. xfaetas
                    18.05.2019 13:19

                    Не правда ли забавно, что разработка ПО воспринимается некоторыми не как инженерная дисциплина, а как некий культ со своими еретиками и гуру? :) При этом софт всё равно часто приходится писать с использованием «плохих» (но эффективных) подходов, но открыто в этом сознаваться не принято. В тоже время никто не хочет читать давно придуманные стандарты по организации процесса разработки и методике написания качественного кода, которые существуют ещё со времён первых Шаттлов, вместо это все бросаются резкими цитатами своих любимых гуру.


                    1. EvgeniiR
                      18.05.2019 15:31

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

                      Большинством программистов что я встречаю разработка ПО является сферой где можно работать по 4 часа в день и кучу денег получать. Благо если знают кто такие эти «гуру», и хоть краем уха слышали о чем они вещают.
                      А еретики это все кто вещают не про те подходы, о которых преподы в институте/на курсах, или не дай-бог какой-нибудь парень с хабра вещал, не признавать же что они чего-то не знают.

                      часто приходится писать с использованием «плохих» (но эффективных) подходов, но открыто в этом сознаваться не принято

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

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


                    1. Neftedollar
                      19.05.2019 20:21

                      Я прошу прощения, но дочитал до этого места становится сложно не видеть обилия Шаталов а комментариях. Шатлы изначально потрясный продукт, но шатлы это же шатлы, а шатлы шатлы шатл шатлы? Шатлы! Шатлы шатл. Шатл!


                    1. tmteam
                      19.05.2019 22:22

                      Я не использую плохие подходы, и все ок. ЧЯДНТ?


                      1. vintage
                        20.05.2019 13:07

                        Скажите, какие походы в используете, и я объясню вам, почему они плохи.


                1. tmteam
                  19.05.2019 22:24

                  Но если вы не учились у Дейкстры, то поймете все превратно. Ведь они то учились у него.


              1. glestwid
                18.05.2019 17:31
                -1

                Премия может называться как угодно и даваться какому угодно профессору кислых щей. Вопрос в том, в каком количестве проектов (в штуках и в $$$$$$, которые через этот проект проходят) все эти его заумные фантазии нашли применение. Равно как и в том, за какие внедрения он в своей жизни лично отвечал. Это так, к вопросу может ли человек что-то посерьезнее трепа на кафедре делать.


  1. AxisPod
    17.05.2019 08:08
    +3

    Я правильно понимаю, что автор оригинальной статью путает DDD с ООП? Ибо все доводы скатились до отображения реальных сущностей в программном коде.


    1. EvgeniiR
      17.05.2019 08:38
      +2

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


      1. avost
        17.05.2019 09:20

        а примеры когда люди неправильно используют инструмент пытается выставить как недостатки инструмента.

        О, раз уж вы знаете, не могли бы привести правильное использование инструмента в задаче с побитием монстров? А то у меня, навскидку, похожая же лапша получается (но хоть от объектов типа "контекст" удаётся уберечься, кроме случаев, когда они приходят извне, из фреймворков).


        1. Mnemonik
          17.05.2019 09:38
          +1

          Вообще изи.
          либо Player->hits(Monster $monster), и weapon существует тогда как property внутри player, там же можно изменить xp, либо Monster->isHitBy(Player $player), тогда наоборот, в этом методе можно получить параметры Player, и оружие и изменить его xp.
          Как вы решаете в вашем проекте лучше, так и делаете, либо сверху вниз отношения, либо наоборот.
          ООП — инструмент и концепция, он позволяет сделать так или эдак. Как его применить — архитектура приложения.
          Если бы инструмент диктовал неизменные правила для каждого случая, это был бы жуткий черный кошмар из миллиона правил которые уже после первых тысяч трех давно противоречили друг другу и являлись списком исключений. И никто бы не пользовался таким инструментом.


          1. SergioSergio
            17.05.2019 14:03

            Мне как тотальному любителю-новичку в голову сразу пришел класс Figt, которому будут переданы player и monster, и там уже все произойдет. Это совсем неправильно?


            1. Alexeyslav
              17.05.2019 14:59
              -1

              Кстати, это зависит ещё и от уровня необходимой изоляции объектов, уровня доверия объектам. Доверять нападающему расчет повреждений жертвы? ну как-то самонадеянно… или жертве доверять расчет повреждений от применяемого к нему оружия? тоже не то. Нападающий может выставить произвольно максимальный уровень атаки, и перед ним не устоит ни одна жертва… кроме той которая выставила абсолютный уровень защиты.
              Представьте себе такую ситуацию, когда нападающих разрабатывает одна команда со своими «читами» а жертву — другая. Поэтому идея отдельного класса Figt, который разрабатывается третьей нейтральной стороной не так уж плоха. Теперь правила боя определяют не заинтересованные стороны и легко проверить соблюдение правил.


              1. sumanai
                17.05.2019 21:48
                +5

                У вас пользователи игры пишут код?


                1. Anarchist
                  19.05.2019 03:28

                  А почему бы и нет? Сделать игру для программеров, в которой они будут писать поведение своих персонажей :)


                  1. Cerberuser
                    19.05.2019 09:38

                    Screeps?


            1. 123150
              17.05.2019 17:49

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


            1. H_I_R_U_R_G
              17.05.2019 22:10

              Нет, неплохой подход.
              Я бы сделал там метод attack(Unit attacker, Unit victim), возможно, еще передавал бы тип атаки (физическая, удаленная, магическая), возможно, оружие.
              метод проверяет, что оба юнита на момент начала атаки живы, что атакующий вообще может атаковать таким типом атаки и что жертва к ней чувствительна.
              Если да, то производим атаку (отнимаем у агрессора силу, у жертвы — здоровье) и сетим в них новые значения этих параметров. Если жертва убита — то тут же и сообщаем об этом. Если может контратаковать — то в конце вызываем метод снова с обновленными параметрами.
              Но, по большому счету, все это можно сделать и создав метод атаки (или получения атаки) у юнита… Все зависит от конкретного техзадания и от вашего чувства перкрасного,
              главное, чтобы работало правильно, хорошо читалось и не просирало ремурсы зря.


              1. TheShock
                18.05.2019 02:11

                Если да, то производим атаку

                А что значит «производим атаку»? У вас просто меняется количество хитов у противника? Запускается анимация того, как персонаж ударяет мечом? У вас в этом методе происходит управление и моделью, и вьюшкой?


                1. H_I_R_U_R_G
                  21.05.2019 13:58
                  -1

                  Нет, у меня подгружается 5-гигабайтный фреймворк, создается фабрика фабрик, которая создает фабрику, которая сгенерирует модели, засетит в них нужные параметры, сгенерит цепочку популяторов чтобы передать колл на юай, ну и в отдельном фреймворке поднимет сам юай… (sarcasm off)
                  я лишь отвечал на вопрос, в котором была только самая общая инфа. поэтому и в ответе самая общая. детали надо обсуждать отдельно, причем детали требований — раньше деталей реализации.


          1. Flammar
            17.05.2019 14:32
            +1

            либо Player->hits(Monster $monster), и weapon существует тогда как property внутри player, там же можно изменить xp, либо Monster->isHitBy(Player $player), тогда наоборот, в этом методе можно получить параметры Player, и оружие и изменить его xp.
            Необходимы оба метода. Player->hits(Monster $monster), кроме расходования патронов, вызывает внутри себя Monster->isHitBy(Player $player), в котором учитывается урон от оружия. Дальше приходим к шине событий и event-driven system.


            1. Belikov
              18.05.2019 13:33

              Хоть что кто-то еще это понимает, а то я думал что люди умеющие ООП совсем вымерли и поэтому такие статьи появляются. ) Столько советов в комментах про разные сторонние классы про Environment и CombatManager. Так и появляются анемичные объектные модели.


              1. michael_vostrikov
                18.05.2019 18:29

                А как вы без Environment или CombatManager будете рассчитывать урон 10 монстрам от взрыва гранаты? Кто будет вычислять, какие 10 из 20 монстров на карте надо передать в Player->hits(Monster $monster)? Вот он и будет этим CombatManager.


                1. Belikov
                  18.05.2019 21:41

                  В данной задаче предполагалось что тут как в классических rpg есть target. Но если нет, то модель все равно легко расширить не вводя псевдоклассы вида «SomethingManager». Если это граната, то вместо монстра через ITarget подставляется точка на карте и там уже граната (currentWeapon) через какой-то движок отвечающий за координаты получает координаты задетых целей и наносит урон. И вообще, когда появляются классы с суффиксом Manager уже стоит задуматься что что-то идет не так: blog.codinghorror.com/i-shall-call-it-somethingmanager


                  1. michael_vostrikov
                    18.05.2019 22:01

                    граната (currentWeapon) через какой-то движок отвечающий за координаты получает координаты задетых целей и наносит урон

                    Ну вот у вас и появился CombatManager. Только совмещенный с ответственностью гранаты. А в какое-нибудь заклинание по площади надо будет код скопировать? Наследовать заклинание от гранаты или наоборот как-то неправильно же.


                    1. Belikov
                      19.05.2019 11:44

                      А наносить урон целям это по-вашему не ответственность гранаты? И это не CombatManager т.к. он за combat не отвечает. Скорее уже какой-то Playfield у которого есть метод — дай мне игровые объекты в радиусе X от указанной точки. Что я с ними делаю — это уже не его ответственность. Например, точно также можно какие-то массовые баффы союзников имплементировать, действующие в радиусе от игрока или указанной точки.
                      Про заклинания вы правильно написали — для этого есть наследование. Только не заклинание от гранаты, а их обоих от какого-нибудь AreaEffectWeapon, куда можно вынести общий код, а различия имплементировать в дочерних классах.


                      1. michael_vostrikov
                        19.05.2019 12:09

                        А наносить урон целям это по-вашему не ответственность гранаты?

                        Нет, потому что граната просто разбрасывает осколки, а есть там цели или нет, и как они на осколки реагируют, ее не должно волновать. А у вас какая-то умная граната получилась, сама точку указывает, объекты запрашивает, про Playfield знает, который надо туда пробросить. Естественно, при изменениях в такой архитектуре может понадобиться все заново перепроектировать, с чем и связывают проблемы ООП. Это проблемы не ООП, а неправильной модели.


                        а их обоих от какого-нибудь AreaEffectWeapon

                        Ага, я этого и ожидал. Не делать третий класс, чтобы сделать третий класс. Клёво)


                        1. Belikov
                          19.05.2019 12:31

                          А как цели реагируют на осколки гранату и не волнует. Она вызывает все тот же Monster.IsHitBy. Точку указывает не граната сама, а игрок, когда ее кидает. То есть есть четкое разделение ответственности, а не CombatManager, который знает все обо всех и спокойно меняет состояние любых объектов нарушая инкапсуляцию. И кстати, в вашей модели любое изменение будет затрагивать его. Не спорю, перепроектировать ничего не придется, т.к. тут нечего перепроектировать — есть куча процедурного кода в одном огромном классе плюс куча классов без поведения. Вот только будет ли такой код понятным? Мне кажется что не очень.
                          Playfield пробросить можно разными способами — это не проблема. IoC легко это решает. Пока что вам не удалось придумать изменения которые требуют все заново перепроектировать, так что модель видимо неплохая. ) И развитие модели это вполне нормально.

                          Ага, я этого и ожидал. Не делать третий класс, чтобы сделать третий класс. Клёво)

                          Цель не в том чтобы сделать меньше классов, а в том, чтобы каждый класс отвечал за свою часть работы и работу системы в целом можно было легко и наглядно увидеть (это называется ОО-декомпозиция). Есть игрок — он кидает гранаты. Есть поле, оно выдает координаты. Есть граната — она взрывается и наносит урон. Есть монстры, они получают урон. Это ООП.
                          Подход «у нас есть классы Player и Monster, но на самом деле каждый из них это просто набор полей, а вся логика их взаимодействия находится в классе CombatManager» по-моему не совсем ООП. Или может быть я чего-то не понимаю? Можете написать код, как вы бы в своем подходе описали пример с гранатой?


                          1. akryukov
                            19.05.2019 12:41

                            Пока что вам не удалось придумать изменения которые требуют все заново перепроектировать, так что модель видимо неплохая. )

                            Давайте я попробую)
                            Допустим у нас есть отдельные классы человек и кроган. Человек крогану наносит урон 0.5х. Все остальные комбинации (человек-человек, кроган-человек, кроган-кроган) наносят урон 1х.


                            1. Belikov
                              19.05.2019 13:12
                              +1

                              Элементарно. В базовом классе:

                              protected virtual int GetAttackPower(ITarget target)
                              {
                                     // При желании тут можно учесть разные эффекты от оружия и т.д.
                                     return AttackPower;
                              }
                              public void Hit(ITarget target)
                              {
                                 var attackPower = GetAttackPower(target);
                                 // В IsHitBy можно учесть противокрогановое оружие 
                                 // и сопротивляемость разным видам урона
                                 if (target.IsHitBy(CurrentWeapon, attackPower) <= 0) 
                                 {
                                      // За разрушение ящиков можно XP не давать - это легко проверить если надо
                                      XP += target.XP;
                                 }
                              }


                              В классе человек:
                              protected override int GetAttackPower(ITarget target)
                              {
                              var attackPower = base.GetAttackPower(target);
                               if (target is Krogan)
                                 {
                                      attackPower *= 0.5;
                                 }
                              return attackPower;
                              }


                              Если нужна большая гибкость, то эти правила легко вынести в какой-то отдельный файл ресурсов и брать их оттуда в методах GetAttackPower для игрока, монстра и оружия.


                              1. akryukov
                                19.05.2019 13:21
                                -1

                                Что будете делать если добавим стрелковое оружие с теми же правилами? Если развивать вашу модель, то надо заводить класс Bullet, AbstractProjectile и выносить логику туда.


                                1. Belikov
                                  19.05.2019 14:30

                                  В смысле противокрогановое оружие? Я же написал комментарий — это можно легко обработать в IsHitBy в классе корган.


                                  1. akryukov
                                    19.05.2019 14:34

                                    Насколько я понял, вы методы GetAttackPower и Hit описываете в гранате. Поэтому и вопрос, что будете делать, когда гранаты не будет, а правила останутся.


                                    1. Belikov
                                      19.05.2019 15:03

                                      Нет, GetAttackPower и Hit — это методы Unit — базового класса для Human и Krogan.


                                      1. akryukov
                                        19.05.2019 15:26

                                        Покажите, пожалуйста, код, который вы имели в виду в случае с гранатой.
                                        Вы вроде защищали точку зрения "наносить урон — ответственность гранаты", а теперь заявляете что метод Hit (который и вызывает нанесение урона) находится в базовом классе для Human и Krogan.


                                        1. Belikov
                                          19.05.2019 15:40

                                          Метод Hit — правильнее назвать Attack — это еще не нанесение урона. Для AOE оружия вроде гранаты ITarget это будет игровое поле и туда можно передавать координаты взрыва. И он будет реализовывать IsHitBy как-то так, например (убийство монстров тогда надо будет по-другому немного сделать, но это не принципиально): В классе Playfield

                                          void IsHitBy(IAoeWeapon weapon, int attackPower, Point point)
                                          {
                                               weapon.Hit(attackPower, point)
                                          }


                                          В классе граната, реализующей IAoeWeapon
                                          void Hit(int attackPower, Point point)
                                          {
                                                var targets = Playfield.GetTargets(point);
                                                foreach (var target in targets)
                                                {
                                                       target.IsHitBy(this, attackPower);
                                                }
                                          }
                                          


                                          1. michael_vostrikov
                                            19.05.2019 20:14

                                            В классе граната, реализующей IAoeWeapon

                                            Но там нет ничего, что хоть как-то зависит от параметров гранаты, все передается снаружи, в том числе и PlayField, который этот метод вызывает. Почему PlayField не может вызывать свой собственный метод GetTargets? Он ведь уже знает, что оружие это IAoeWeapon. Хотя там радиус еще должен быть, который характеристика гранаты, но почему тогда attackPower передается в гранату снаружи?


                              1. Acuna
                                20.05.2019 05:28

                                Извиняюсь, а что это за язык? Или метаязык, как это часто бывает в случаях с красивым кодом?)


                                1. PsyHaSTe
                                  20.05.2019 07:33
                                  +1

                                  C# это, и в нем не юзается ничего магического вроде, то же самое на котлине или прости господи джаве можно написать точно так же.


                                  1. Acuna
                                    20.05.2019 21:49
                                    -1

                                    Ах C# же, конечно, а я все судорожно вспоминал что это может быть за ява такая с нестрогой типизацией. Люблю такой человекопонятный синтаксис просто) Я сам явист кстати, вот такой вот нежданчик, и этот код выглядил бы на ней совершенно аналогично, прям символ в символ, но если бы нужно было записать/прочитать файл, то тут бы содомия и началась. Но лично я обвертки свои писал для часто используемых функций еще до Котлина, получился свой Котлин по сути. Он же вроде только для Винды, да? В свое время встал перед выбором на чем писать под Андройд, показалась просто невероятной идея писать под две платформы одновременно на Ксамарине, но много встречал на SO вопросов, в комментах которым писали, мол, вот если бы это решение да на Ксамарине реализовать — цены бы ему не было бы… Думаю не, не надо это мне такое, в самый ответственный момент подведет…


                                    1. mayorovp
                                      20.05.2019 22:03

                                      Простите, но где вы тут слабую типизацию увидели?..


                                      1. Acuna
                                        20.05.2019 22:14
                                        -1

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


                                        1. VolCh
                                          20.05.2019 22:39

                                          Статичность-динамичность и строгость-слабость ортогональны друг другу. Python например язык с сильной динамической типизацией.


                                          1. Acuna
                                            20.05.2019 23:00

                                            Возможно в каких-то ЯП и да, по факту различия имеются именно в практической области, и именно C# и Java прекрасно это иллюстрируют, вроде бы оба они статически типизированы, но в рамках именно этой типизации один ЯП позволяет то, что не позволяет другой, а остальное — уже частные случаи.


                                            1. PsyHaSTe
                                              20.05.2019 23:12

                                              Где мы тут динамическую типизацию увидели? Очень надеюсь, что не в var. И при чем тут windows only?


                                              Я перечитал 3 раза сообщение, и понял, что нихрена не понял. Затем пошли раговоры про питон и лекция про отличия слабой и динамической типизацией… вааат?


                                              1. Acuna
                                                20.05.2019 23:30

                                                Вы веткой ошиблись. По поводу лекций про Питон обращайтесь к автору лекций про Питон (это выше). Windows only — это вопрос был. Я спросил на нем можно только под Windows писать или еще подо что-то. Конец дня, я понимаю что все устали.


                                                1. PsyHaSTe
                                                  21.05.2019 00:05

                                                  Да нет, не ошибся, это в продолжение https://habr.com/ru/post/451982/#comment_20177778, просто чуть дальше читанул.


                                              1. Acuna
                                                20.05.2019 23:37

                                                Кстати, тут развенчивание мифа про онтогональность понятий рассматривается.

                                                Не все слабо типизированные языки являются динамически типизированными: PHP — это динамически типизированный язык, но вот C — тоже язык со слабой типизацией — воистину статически типизирован.


                                              1. Acuna
                                                21.05.2019 00:57

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


                                                1. Alexeyslav
                                                  21.05.2019 09:13

                                                  Особенно после недавнего полнолуния… откат уже пошел.


                                                  1. Acuna
                                                    21.05.2019 09:41

                                                    Типа того…


                                          1. Acuna
                                            21.05.2019 01:44

                                            Нашел эту статью, в которой я читал что эти понятия различаются. Я вообще-то на Вики читал, но не нашел, эта тоже сойдет. Не буду повторять свой коммент, просто ссыль кину на него.


                                        1. mayorovp
                                          21.05.2019 05:26

                                          В C# ключевое слово var точно так же назначает переменной конкретный тип, который в дальнейшем переназначить не получится.


                                          1. Acuna
                                            21.05.2019 06:24
                                            -1

                                            Типизация — это про присвоение типов. В языках со строгой статической типизацией его нужно назначать вручную, с нестрогой — достаточно ограничиться словом var и ему подобными. Если на пальцах — то в рамках даже одного вида казалось бы статической типизации виды присвоения этих типов отличаются, С# это делает сам, яве нужно пихать все в рот (нестрогая и строгая статическая типизация соответственно). Выше кидал ссыль на статью, в которой объясняется различия в рамках одной и той же типизации. В каком-то смысле это азы, которые, к сожалению, некоторые путают, считая себя умнее других, хотя тут у нас не телевикторина, чтобы соревнования устраивать.


                                            1. VolCh
                                              21.05.2019 06:51

                                              Вот по ссылке:


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

                                              Тем не менее, Ruby, Python и JavaScript (все они обладaют динамической типизацией) также являются сильно типизированными, хотя разработчику и не нужно указывать тип переменной при объявлении.

                                              Сильная типизация она не про var vs int, она, грубо говоря, про невозможность вычислить 1+"1"


                                              1. Acuna
                                                21.05.2019 07:05

                                                Нет-нет, ниже читайте:

                                                Тем не менее, Ruby, Python и JavaScript (все они обладaют динамической типизацией) также являются сильно типизированными, хотя разработчику и не нужно указывать тип переменной при объявлении.

                                                Речь идет именно о присвоении (объявлении) типов, а не дальнейших операциях с ними (которые «вычислить 1+«1»»). Кстати это, к слову, раздел почему понятия статики/динамики и силы/слабости не дождественны как иногда считается.


                                                1. Cerberuser
                                                  21.05.2019 07:25

                                                  Когда JavaScript называют сильно типизированным языком, моя рука тянется к {} + [] === 0.


                                                1. PsyHaSTe
                                                  21.05.2019 08:53
                                                  +1

                                                  Хаскель у вас тоже динамический?


                                                  x = 5
                                                  x = 6
                                                  
                                                  foo :: Integer -> Integer
                                                  foo a =
                                                    b where
                                                      b = x * 2

                                                  Это динамика? Я про первые 2 строки


                                                1. mayorovp
                                                  21.05.2019 09:57

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


                                            1. alsii
                                              21.05.2019 14:42

                                              строгой статической типизацией его нужно назначать вручную

                                              Нет. Как я понимаю в C# как и в Котлине компилятор может сам определять типы переменных, если используется объявление с инициализацией. В данном случае тип переменной targets будет соответствовать типу результата возвращаемого методом Playfield.GetTargets(). Тип переменной определяется во время компиляции и в дальнейшем не меняется. Считайте это просто синтаксическим сахаром, который позволяет не указывать повторно то, что и так ясно.


                          1. michael_vostrikov
                            19.05.2019 14:33
                            -1

                            Она вызывает все тот же Monster.IsHitBy.

                            Но в реальности граната не знает ничего ни про каких монстров. Зачем делать заведомо неправильную модель, и потом решать проблемы, которые из-за этого возникают?


                            Точку указывает не граната сама, а игрок, когда ее кидает.

                            Ну а как же она тогда будет вызывать метод объекта Playfield? Она знает координаты, и по вашим словам туда их передает. Неважно, кто ей их сообщил, важно что она с ними работает.


                            Пока что вам не удалось придумать изменения которые требуют все заново перепроектировать, так что модель видимо неплохая.

                            Так я и не придумывал, мы все еще про гранату говорим) У вас надо добавлять общие интерфейсы у точки на карте и гранаты, делать кучу иерархий AreaEffectWeapon, BulletWeapon, ClubWeapon, по которым будет разбросана физика, пробрасывать Playfield во все из них. И все для того, чтобы не делать выделенный класс с нужной ответственностью. А теперь допустим появляется требование добавить спецэффекты типа "граната не взорвалась, но попала монстру по башке, и он потерял сознание". А у нас в ней повреждение по area рассчитывается, а повреждение при прямом контакте рассчитывается в иерархии ClubWeapon. Теперь надо гранату от ClubWeapon наследовать?


                            а не CombatManager, который знает все обо всех и спокойно меняет состояние любых объектов нарушая инкапсуляцию

                            Стоп, я нигде не говорил, что он меняет состояние любых объектов. В моем примере объект Environment отправляет сообщения своим под-объектам, которые являются наследниками класса Environment (или более общего) и потому знают физику игры и меняют свои параметры в соответствии с ними, и возможно передают модифицированное сообщение своим под-объектам.


                            Например, игрок отправляет сообщение в Environment "Бросаю гранату типа G в точку X". Environment определяет радиус через G.calculateRadius(), определяет все объекты MonsterBody в этом радиусе от точки X, опеределяет силу взрыва через G.calcualtePower(), для каждого объекта расчитывает P в зависимости от расстояния и отправляет сообщение "Осколок гранаты силой P", каждый MonsterBodу меняет свои характеристики в соответствии с законами физики. Environment может иметь в составе компоненты для разных стихий или типов оружия, которые и делают конкретные расчеты, у объектов Monster есть ссылка на MonsterBody с доступом на чтение характеристик и к методам управления, Monster и MonsterBody могут иметь разные иерархии наследования или вообще их не иметь, а делать все через композицию. Возвращаясь к примеру с невзорвавшейся гранатой, если G.calcualtePower() вернула 0, то Environment берет массу гранаты G.getMass(), вызывает код, которые рассчитывает урон для холодного оружия, проверяет, есть ли какой-то объект конкретно в точке X, если есть отправляет ему сообщение "Камень массой M". Инкапсуляция не нарушена, нет никаких ограничений на наследование, новые правила боя добавляются без изменений архитектуры.


                            1. Belikov
                              19.05.2019 15:26

                              Но в реальности граната не знает ничего ни про каких монстров. Зачем делать заведомо неправильную модель, и потом решать проблемы, которые из-за этого возникают?

                              Граната знает радиус своего поражения и влияет на объекты, которые в него попадают — монстры, здания и любые другие ITarget. По-моему это более естетственная модель чем некий CombatManager, который всем управляет. Разве что это бог войны какой-то. )

                              Ну а как же она тогда будет вызывать метод объекта Playfield? Она знает координаты, и по вашим словам туда их передает. Неважно, кто ей их сообщил, важно что она с ними работает.

                              Не понял в чем проблема. Граната получает координаты взрыва от игрока, когда он ее кидает и окружающие объекты от Playfield когда взрывается.

                              Так я и не придумывал, мы все еще про гранату говорим) У вас надо добавлять общие интерфейсы у точки на карте и гранаты, делать кучу иерархий AreaEffectWeapon, BulletWeapon, ClubWeapon, по которым будет разбросана физика, пробрасывать Playfield во все из них. И все для того, чтобы не делать выделенный класс с нужной ответственностью.

                              Общий интерфейс с точкой на карте возможно не лучшее решение — в этом случае в принципе можно и добавить Environment для AOE эффектов, его унаследовать от ITarget и туда передавать координаты, например. Но общая идея именно в том, чтобы разбросать логику по тем классам к которым она относится. Правила взрыва — в гранате, получение урона — в монстре и т.д. вместо одного огромного класса который через некоторое время станет очень болезненно поддерживать. В зависимости от изменений модели — можно делать рефакторинг и добавлять классы, переносить логику из одного класса в другой и т.д. Но по крайней мере есть модель из которой понятно что как работает.

                              А теперь допустим появляется требование добавить спецэффекты типа «граната не взорвалась, но попала монстру по башке, и он потерял сознание». А у нас в ней повреждение по area рассчитывается, а повреждение при прямом контакте рассчитывается в иерархии ClubWeapon. Теперь надо гранату от ClubWeapon наследовать?

                              Зачем? Если она такое умеет — можно в ней же эту логику и описать.

                              Например, игрок отправляет сообщение в Environment «Бросаю гранату типа G в точку X». Environment определяет радиус через G.calculateRadius(), определяет все объекты MonsterBody в этом радиусе от точки X, опеределяет силу взрыва через G.calcualtePower(), для каждого объекта расчитывает P в зависимости от расстояния и отправляет сообщение «Осколок гранаты силой P», каждый MonsterBodу меняет свои характеристики в соответствии с законами физики. Environment может иметь в составе компоненты для разных стихий или типов оружия, которые и делают конкретные расчеты, у объектов Monster есть ссылка на MonsterBody с доступом на чтение характеристик и к методам управления, Monster и MonsterBody могут иметь разные иерархии наследования или вообще их не иметь, а делать все через композицию. Возвращаясь к примеру с невзорвавшейся гранатой, если G.calcualtePower() вернула 0, то Environment берет массу гранаты G.getMass(), вызывает код, которые рассчитывает урон для холодного оружия, проверяет, есть ли какой-то объект конкретно в точке X, если есть отправляет ему сообщение «Камень массой M». Инкапсуляция не нарушена, нет никаких ограничений на наследование, новые правила боя добавляются без изменений архитектуры.

                              Ок, можно сделать большой Composite как вы предлагаете или какую-то шину сообщений, но засовывать туда всю логику неправильно. Если у вас Environment активно вызывает G.calculateRadius(), G.calcualtePower(), а потом G.getMass(), то по-моему это явный признак того, что этой логике место в G (сорри за каламбур).


                              1. michael_vostrikov
                                19.05.2019 16:56

                                Граната знает радиус своего поражения и влияет на объекты, которые в него попадают — монстры, здания и любые другие ITarget. По-моему это более естетственная модель

                                Она не знает ни про какие объекты, она просто разбрасывает осколки. Есть там объекты, или нет, или они были на границе радиуса и сдвинулись после взрыва и осколки до них не долетели, ей без разницы. Какая же это естественная модель, если поведением куска железа вдруг становится передача координат и получение целей.


                                Не понял в чем проблема. Граната получает координаты взрыва от игрока, когда он ее кидает и окружающие объекты от Playfield когда взрывается.

                                В том, что в реальности так не происходит. У вас неправильная модель, которая сразу ведет к усложнению кода и потенциально к проблемам при изменениях.


                                Правила взрыва — в гранате, получение урона — в монстре

                                Так взрываться могут не только гранаты. Бензобак автомобиля может взорваться, его тоже от оружия наследовать? Получение урона у меня тоже в монстре, вернее в его физической части, а не в самом объекте Environment.


                                вместо одного огромного класса

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


                                Зачем? Если она такое умеет — можно в ней же эту логику и описать.

                                Так копипаста же. В ClubWeapon уже есть этот код.


                                Если у вас Environment активно вызывает G.calculateRadius(), G.calcualtePower(), а потом G.getMass(), то по-моему это явный признак того, что этой логике место в G

                                Так она и к другим объектам обращается, про которые G ничего не знает. В том-то и дело, мы же с этого и начинали. А еще могут быть всякие расчеты, связанные с формой окружающих предметов (монстр в радиусе поражения, но спрятался за бетонной стеной), с защитными полями определенного типа на поверхности карты (которые не связаны с конкретным субъектом) и т.д. Это, как я понимаю, должно быть в Playfield, который список целей отдает? Ну вот и появился Environment.


                              1. VolCh
                                20.05.2019 11:31

                                Правила взрыва — в гранате,

                                Граната ещё от PlayField получает информацию о том, где она взорвалась — на земле, в воздухе, в воде, в вакууме, в закрытом или открытом пространстве, при какой гравитации и атмосферном давлении и всё это обсчитывает?


                          1. Bronx
                            19.05.2019 23:46

                            CombatManager, который знает все обо всех и спокойно меняет состояние любых объектов нарушая инкапсуляцию.

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


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


                            1. khim
                              20.05.2019 00:08

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

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

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


                              1. Bronx
                                20.05.2019 01:12

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


                      1. Druu
                        19.05.2019 12:18

                        А наносить урон целям это по-вашему не ответственность гранаты?

                        Ответственность гранаты — взорваться, то есть создать соответствующий АОЕ-эффект и благополучно исчезнуть.


                        1. Belikov
                          19.05.2019 12:34

                          Ну да, так и есть. Вызывать Monster.IsHitBy в окрестностях и все. Можно даже разный урон наносить в зависимости от расстояния до цели.


                          1. Druu
                            19.05.2019 13:22

                            Ну да, так и есть. Вызывать Monster.IsHitBy в окрестностях и все.

                            Нет, граната монстров не бьет, она создает АОЕ. А потом уже все монстры, попавшие в АОЕ { actor: playerId, source: grenadeId, position: {...}, area: {...}, effects: [{ type: Effects.DAMAGE, damageType: Damage.STAB, target: Targets.ALL,' value: X, ticks: 1, delayBefore: 0, delayBetween: 0,… }], ...} получают эффект в соответствии с этим АОЕ.


                            1. Belikov
                              19.05.2019 15:01
                              +1

                              Если нужны какие-то более сложные эффекты можно и так сделать. Если нет, то сама граната вполне может быть AOE.


                1. Belikov
                  18.05.2019 21:44
                  -1

                  Да и вообще классы вроде Environment, Core, Engine Manager зачастую быстро превращаются в god-object, а сами классы становятся просто структурами данных без поведения и весь смысл ООП пропадает.


          1. michael_vostrikov
            17.05.2019 22:36

            Я думаю, правильно будет, если объект Player передает сообщение с заданными параметрами объекту Environment, а Environment уже сама распределяет чего и куда. В простом случае это сообщение целиком уходит объекту Monster, в более сложном можно например рассчитывать урон взрыва от расстояния. Environment это не столько окружающее пространство, сколько законы физики (игры) в целом.


            Лучше если физические параметры Monster cможет менять только Environment, а не сам Monster. Можно сделать отдельный объект MonsterBody, который является частью Environment, а у Monster доступ только на чтение. Environment распределяет сообщение по всем своим составляющим в виде сообщений с меньшими характеристиками, составляющие тоже знают законы физики, возможно даже являются наследниками класса Environment, и при получении сообщения меняют свои параметры или в свою очередь передают своим составляющим.


          1. khim
            18.05.2019 02:42

            А вы вообще учитываете, что ООП — это не только C++? Почитайте про CLOS, на досуге, что ли.

            Тот факт, что ущербная версия, которая была реализована в Simula 67 из соображений эффективности… считается единственно правильной версией… ну не знаю…


          1. mrguardian
            18.05.2019 03:32

            Вы повторили то же самое что написано в статье и точно также не дали ответа, а только запутали. И нет, не существует иерархии между monster и player в общем случае. А с точки зрения данных и их преобразования и понятий-то таких нет.


        1. akryukov
          17.05.2019 09:48
          +6

          Не знаю, насколько мой вариант "правильный" с точки зрения ООП.
          На мой взгляд лучше завести класс CombatManager у которого нужно вызывать условный attackEventHandler(attacker, victim). Ну а внутри уже учитывать хитпойнты, силу атаки, защиту, опыт, оружие и все остальное. Можно даже вызвать какие-нибудь методы класса Player и Monster вроде onAttackSucceed, onAttackMisses.


          Проблема ООП в том, что методы пытаются представить как действия субъекта. В то время как методы это дейстия над объектом.


          1. EvgeniiR
            17.05.2019 10:13

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


            Если у нас есть класс менеджер который принимает в себя два объекта и что-то рассчитывает на основе этих объектов, объекты превращаются просто в структуры данных (поле+геттеры), а логика утекает а менеджер. Это нарушение инкапсуляции


            1. akryukov
              17.05.2019 10:21
              +1

              В вашем первом предложении, тезис


              Методы это не действия над объектом

              противоречит тезису


              , объект это штука которая обладает каким-либо поведением, вот его методы это описание того, что мы можем сказать ему сделать.

              Инкапсуляция это когда объект знает только про себя и не дает никому другому знать о себе. Вы ведь так это понимаете?


              Если мы будем говорить объекту (допустим Player) "сделай ка что-нибудь над другим объектом (допустим Monster)", то для этого объекту Player нужно что-то знать об объекте Monster. То есть Monster даст возможность кому-то другому знать о себе. Вот это нарушение инкапсуляции.


              Вместо "CombatManager" можно описать класс "Rule". Потому что только правила игры знают как правильно менять объекты, участвующие во взаимодействии.


              1. EvgeniiR
                17.05.2019 11:43
                -1

                Инкапсуляция это объединение данных и функций для работы с этими дданными в одном месте(в нашем случае в классе).

                Пример нарушения:

                class Manager{
                    fun someFunc(Target t, Player p) {
                        if(p->getWeaponDamage >= t.getDefenceValue) {
                            // do smth...
                        }
                    }
                }
                


                Логика в классе менеджере зависит от данных находящихся в объектах.

                Если мы будем говорить объекту (допустим Player) «сделай ка что-нибудь над другим объектом (допустим Monster)», то для этого объекту Player нужно что-то знать об объекте Monster. То есть Monster даст возможность кому-то другому знать о себе. Вот это нарушение инкапсуляции.

                Объект Player может сказать что-то объекту Monster, а объект Monster решает что ему с этим уже делать. Напрямую изменить состояние, данные внутри объекта Monster, объект Player не может


                1. akryukov
                  17.05.2019 11:50

                  Почему нарушение то?


                  функция getWeaponDamage у вас ведь находится в классе Player, а не в Manager.
                  Вот если бы вы написали


                  class Manager {
                  fun getWeaponDamage(Player p){return p.damage;}
                  fun getDefenceValue(Target t){ return t.defence;}
                  fun processAttack(Target t, Player p){
                  if(getWeaponDamage(p) >= getDefenceValue(t)) {
                  // do smth...
                  }}}

                  Вот тогда мы бы нарушили инкапсуляцию потому что функция по работе с данными (полями классов Player и Target) были бы отделены от этих данных.


                  1. EvgeniiR
                    17.05.2019 11:58
                    -1

                    Почему нарушение то?

                    Потому что геттер возвращает данные из объекта, а логика работы с этими данными находится в другом объекте.
                    weaponDamage это не данные принадлежащие классу-менеджеру. Это данные принадлежащие, в примере выше, объекту Player.


                    1. akryukov
                      17.05.2019 12:08
                      -1

                      Вы путаете логику работы С данными (т.е. чтение, использование их) от логики работы НАД данными (т.е. изменение, модификацию).
                      Если ни один объект вообще никак не будет давать читать свои данные никому другому, у вас просто программы не получится. Если вы перенесете логику боя из CombatManager в Player, то в вашем понимании неизбежно нарушите инкапсуляцию класса Monster. Он ведь отдает свои данные кому-то.
                      Вопрос в том, насколько внешний мир способен модифицировать внутренние данные объекта.


                      Когда мы пишем геттер Player::getWeaponDamage, то можем как угодно вычислять этот самый урон. Варианты: хранить в поле, проходиться по списку шмоток и искать сумму бонусов на урон.
                      Внешний мир об этом не узнает и напрямую повлиять не сможет. Только через специальные методы, которые мы можем контролировать.


                1. alsii
                  17.05.2019 13:00
                  +3

                  А давайте немного исправим ваш код.


                  class Manager{
                      fun someFunc(Target t, Player p) {
                          t.receiveDamage(p.getDamage)
                      }
                  }

                  И все сразу стало на свои места. Player может сообщить об уровне наносимого урона, Target сам решает, что ему делать с полученным уроном, а Manager осуществляет передачу урона от игрока к монстру.
                  Зачем тут нужен Manager? Затем, что логика передачи урона может быть более сложной. Например может учитывать состояние других объектов (фазу луны например). Может распределять урон на другие цели (цепной, площадной урон), вызывать дополнительные эффекты (лечение себя/союзников) и т.п.
                  Если все это запихать в Player.hit или Monster.receiveDamage то придется потом все это многократно дублировать во всех потомках.


                  Тогда получается так


                  class Manager{
                      fun processDamage(Target t, Player p) {
                          val damage = p.getDamage;
                          val modifiedDamage = modifyDamage(damage); 
                          t.receiveDamage(modifiedDamage);
                  
                          // ну или в одну строчку:
                          // t.receiveDamage(modifyDamage(p.getDamage));
                      }
                  }


                  1. red75prim
                    17.05.2019 17:23

                    Ну и теперь уже можно и класс Manager выкинуть, и cделать свободную функциию processDamage(Target p, Player p, const GameState s), чтобы не рефакторить Manager каждый раз когда для расчета урона понадобится новая фаза луны.


                    Заодно и голову не надо ломать кому же эта функция должна (должна ли?) принадлежать.


                    1. alsii
                      17.05.2019 17:52

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


                      чтобы не рефакторить Manager каждый раз когда для расчета урона понадобится новая фаза луны.

                      А не надо рефакторить. modifyDamаge может вызывать сколько угодно damageModifier который можно хоть инъектировать в наш менеджер, хоть выбирать из из GameState или какой-нибудь currentLocation. А на currentLocation они могут быть навешаны как статически, так и динамически вычисляться (скажем в зависимости от наличия в ней монстров/других игроков). И вот как раз с фкнкциями это все придется рефакторить при минимальных изменениях. А с объектами, благодаря инъекциям и внутреннему состоянию все существенно проще.


                      p.s. Функции как объекты первого класса конечно несколько улучшают дело, но без полиморфизма все равно сложно получается. Можно конечно использовать композицию, функции высших порядков… И получим полноценное ФП :) Я не против ФП, у него свои плюсы (и минусы), но если уходить вглубь то там будет ничуть не проще чем в ООП.


                      1. 0xd34df00d
                        17.05.2019 20:50
                        -2

                        Я не против ФП, у него свои плюсы (и минусы), но если уходить вглубь то там будет ничуть не проще чем в ООП.

                        На самом деле проще. Вы просто пишете свои данные, и вы просто пишете свои функции. Есть игрок? Супер,


                        data Player = Player
                          { name :: String
                          , xp :: Int
                          , level :: Int
                          }

                        Есть монстр? Можно тайпклассами, но проще тупо тоже


                        data Monster = Monster
                          { name :: String
                          , hp :: Int
                          , icon :: Image
                          }

                        Оружие? Аналогично Weapon.


                        И всё со всеми кишками наружу.


                        Ну а потом отдельная функция


                        combatStep :: Player -> Monster -> (Player, Monster)
                        combatStep player monster = ({- modified player -}, {- modified monster -})


                        1. TheShock
                          18.05.2019 02:46

                          Изменился мир, а как это корректно проанимировать?


                          1. 0xd34df00d
                            18.05.2019 23:21

                            А в императивном коде как?


                            Тут тоже будет ещё какой-то кусок кода, который отвечает за анимацию.


                            Более того, в отличие от императивного кода, вы можете правила анимации написать тоже в удобном декларативном виде, и положить задачу матчинга правил с реальными изменениями на компилятор. Можно вывести набор функций, которые ставят некую анимацию в соответствие изменению каждой переменной. Будет анимация на изменение level, на изменение xp, и так далее, и почти автоматически порождённый (теми же дженериками) код для сравнения нового Player'а со старым.


                            1. TheShock
                              18.05.2019 23:33

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

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

                              Вот представьте, вы делаете удар игроком.

                              В модели это выглядит так:
                              1. У игрока запустился кулдаун до следующего удара
                              2. У монстра отнялись жизни и он умер
                              3. Из него на землю выпал лут
                              4. Игроку начислились монеты

                              Конечно, ваш подход подойдет для какого-то рогалика без анимаций. Ты нажал кнопку и вот уже новый стейт. Но в нормальной игре на такое действие будет следующая реакция:
                              1. Сначала игрок замахивается мечом в течении 10-30 кадров зависимо от оружия
                              2. Потом в определенный момент запускается анимация нанесения урону персонажа, а так же из него вылетают цифры, которые указывают, сколько урона ты ему нанес
                              3. Потом еще кадров 10 спустя — запускается анимация смерти, в этот момент (и ни кадром раньше) у игрока запускается анимация начисления денег, а так же из монстра начинает вылетать и падает на землю лут.

                              При этом модель обновилась, провалидировала и отправила все изменения на сервер ещё в первый кадр этой анимации.


                              1. 0xd34df00d
                                18.05.2019 23:39

                                Но, обещаю, когда вы предложите свое — я вам расскажу, как это обычно делается.

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


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

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


                                1. TheShock
                                  18.05.2019 23:46

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

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

                                  отвечающей за расчёт состояний, притягивать что-то там про анимации, вообще очень многое говорит о типичном месиве ООП.

                                  Проблема вашего предложения, что оно сугубо теоретическое и неюзабельное на практике. Нет никакой сложности написать monster.hp -= 100 и даже на обычном процедурном программировании оно будет работать. Вот только как вьюшка потом узнает, почему у этого монстра хп изменилось? Вот изменилось хп и все. А почему? То ли из-за яда? Или заклинание на него наложили? Или мечом ударили?

                                  Вы, как типичный плохой коллега говорите:
                                  — Это не моя область, в модели хп меняется, я уехал в отпуск а вы…


                                  А программисты, пока вы в отпуске, выкидывают ваш теоретический код и пишут код, который работает.


                                  1. PsyHaSTe
                                    18.05.2019 23:58

                                    А программисты, пока вы в отпуске, выкидывают ваш теоретический код и пишут код, который работает.

                                    Были у нас такие "погромисты", которые навертели ADO.Net поверх энтити фреймворка пока автор кода был в отпуске, потому что не поняли, нахрена там все эти атрибуты, модели и UoW.


                                    1. TheShock
                                      19.05.2019 00:01

                                      О! А у нас был программист, он на гитаре одной рукой играть умел.

                                      Теперь снова ваша очередь говорить вещи, которые не несут никакой полезной информации и вообще не в тему.


                                      1. PsyHaSTe
                                        19.05.2019 00:03

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


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


                                        1. TheShock
                                          19.05.2019 00:10

                                          Ну будет вместе с хп передаваться тип урона, разница-то какая?
                                          И что, что тип урона? Ну вот урон от спелла. И что это даст? Кто этот спел наложил? Вот изменилось количество монет у пользователя — в результате какого действия? Из-за удара? Или смерти персонажа? После смерти какого персонажа? А если от гранаты умерло 5 персонажей и от каждого капнуло 20 монет и нам надо это отобразить — вот вылетели с монстра эти монеты.

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


                                          1. 0xd34df00d
                                            19.05.2019 00:17

                                            А если от гранаты умерло 5 персонажей и от каждого капнуло 20 монет и нам надо это отобразить — вот вылетели с монстра эти монеты.

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


                                            И дело не в том, что какой-то фичи не хватает, у вас ядро гнилое.

                                            Потому что в реакте, который до вас никто не упоминал, криво сделали, или почему?


                                            1. TheShock
                                              19.05.2019 00:28

                                              Потому что в реакте, который до вас никто не упоминал, криво сделали, или почему?
                                              Тот подход, который вы описали активно используется в реакте и его я привел только как пример. В любом фреймворке, где вьюшка реактивно реагирует на модель будут такие проблемы.

                                              как нам на ООП-подходе с этими плеерами и монстрами организовать походы в пати, чтобы на всех участников капало.
                                              А в чем проблема? Вы о сложностях работы по сети? Они есть независимо от парадигмы.


                                              1. 0xd34df00d
                                                19.05.2019 00:31

                                                А в чем проблема? Вы о сложностях работы по сети? Они есть независимо от парадигмы.

                                                Не знаю, у ООП-товарищей спросите, как они теперь hit вместе с пабсабом для изменения состояний обобщат на пати.


                                                1. TheShock
                                                  19.05.2019 00:35

                                                  Не понимаю вас. Можете дать ссылку на сообщение?


                                                  1. 0xd34df00d
                                                    19.05.2019 00:39

                                                    На какое из нескольких сотен сообщений на этой странице с обсуждением атак монстров и игроков?


                                                    1. TheShock
                                                      19.05.2019 00:45

                                                      hit вместе с пабсабом для изменения состояний обобщат на пати
                                                      Вот про это


                                                      1. 0xd34df00d
                                                        19.05.2019 00:46

                                                        А не вы писали про раскидывание монеток по всем игрокам?


                                                        1. TheShock
                                                          19.05.2019 00:48

                                                          Не


                                                          1. 0xd34df00d
                                                            19.05.2019 00:49

                                                            А, да, пати монстров, не игроков:


                                                            А если от гранаты умерло 5 персонажей и от каждого капнуло 20 монет и нам надо это отобразить — вот вылетели с монстра эти монеты.

                                                            Ну не суть разница.


                                                            1. TheShock
                                                              19.05.2019 00:52

                                                              А да, это я писал. А что за хит с пабсабом?


                                                              1. 0xd34df00d
                                                                19.05.2019 00:57

                                                                Хит — событие удара. Пабсаб — паттерн такой, publish/subscribe.


                                        1. TheShock
                                          19.05.2019 00:15

                                          Ну будет вместе с хп передаваться тип урона, разница-то какая?
                                          О! И кстати, куда будет передавать тип урона? Записываться в модель? Только последний? А если вьюшке, то как туда будет передаваться тип урона? Там просто поменялись хитпоинты. Это даже непонятно от урона ли. Может просто на персонаже висел бафф на +100 к хп и теперь он пропал. Это ведь не урон уже.


                                          1. 0xd34df00d
                                            19.05.2019 00:17

                                            Я, наверное, глупенький, но где вот в том коде с монстрами и плеерами модели и вьюшки?


                                            1. TheShock
                                              19.05.2019 00:25

                                              Я, наверное, глупенький, но где вот в том коде с монстрами и плеерами модели и вьюшки?
                                              В вашем подходе с монстрами есть только модель, очевидно.


                                              1. 0xd34df00d
                                                19.05.2019 00:27

                                                Так чего мы наворачиваем сложности и дополнительных требований здесь, в ФП, а в ООП не наворачиваем? Там, типа, всё очевидно, но при этом неочевидно, у кого лежит метод hit?


                                                1. TheShock
                                                  19.05.2019 00:28

                                                  Метод hit точно так же не будет работать — он обладает теми же принципиальными недостатками)


                                  1. 0xd34df00d
                                    19.05.2019 00:15

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


                                    Да ещё модели какие-то, валидации, сервера (это если ваш предыдущий комментарий читать), о которых и речи не было.


                                    1. TheShock
                                      19.05.2019 00:16

                                      Я просто хочу сказать, что подход attack(hero, monster) на практике совершенно не работает. Ну то есть функция, которая просто напрямую меняет модель — совершенно неюзабельная в данном контексте.


                                      1. 0xd34df00d
                                        19.05.2019 00:19

                                        То есть, функции, которая по герою и монстру вычисляет новое состояние, в предлагаемом вами подходе нет, и она размазана лапшой по коду с анимациями, валидациями и отправками на сервер?


                                        Ясно.


                                        1. TheShock
                                          19.05.2019 00:30

                                          То есть, функции, которая по герою и монстру вычисляет новое состояние, в предлагаемом вами подходе нет, и она размазана лапшой по коду с анимациями, валидациями и отправками на сервер?
                                          Конечно же у меня ничего подобного нету. У меня — крайне четкое разделение ответственности, модель и вьюшка совершенно независимы. А вот у вас, когда появится вьюшка — будет лапша


                                          1. 0xd34df00d
                                            19.05.2019 00:32

                                            Почему будет-то? Вычисление нового состояния снова в одном конкретном месте. Вычисление последовательности анимаций по изменению состояния — в другом. Инкапсуляция ж.


                                            1. TheShock
                                              19.05.2019 00:34

                                              Ну вот смотрите. В результате выполнения функции hit у вас появился новый мир. В старом мире у игрока было 100 монет. В новом мире — 150 монет. Как вы проанимируете это изменение?


                                              1. 0xd34df00d
                                                19.05.2019 00:38

                                                Ну, начнём с того, что анимация — это моноид.


                                                У меня будет функция


                                                makeAnimation :: Player -> Player -> Animation

                                                которая по двум состояниям игрока даёт анимацию между ними, композируя анимации типа


                                                makeAnimationXP :: XP -> XP -> Animation
                                                makeAnimationXP old new = {- создаём анимацию для XP -}
                                                
                                                makeAnimationCoins :: Coins -> Coins -> Animation
                                                makeAnimationCoins old new = {- создаём анимацию для монеток -}

                                                и отдавая результирующую анимацию куда надо.


                                                1. akryukov
                                                  19.05.2019 00:43

                                                  Немного оффтоп.
                                                  Как вы понимаете, что из XP -> XP -> Animation это аргументы функции, а что — результат?


                                                  1. 0xd34df00d
                                                    19.05.2019 00:46

                                                    Ну это просто — аргумент слева от ->, результат справа, -> правоассоциативно.


                                                    Если чуть серьёзнее и забывая про каррирование, то результат — самый крайний тип справа, всё остальное — аргументы.


                                                1. TheShock
                                                  19.05.2019 00:44

                                                  И как вы сможете тут узнать, что над только что умершим монстром необходимо отрисовать монетки?

                                                  makeAnimationCoins :: Coins -> Coins -> Animation
                                                  m


                                                  Вот к примеру нагуглил первый попавшийся пример — крайне простая казуальная игра, смотрите где-то на 2:00


                                                  1. 0xd34df00d
                                                    19.05.2019 00:47

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


                                                    1. TheShock
                                                      19.05.2019 00:49

                                                      Ах, у монстра есть свои монетки, неплохой костыль, может частично помочь решить эту проблему.


                                                      1. 0xd34df00d
                                                        19.05.2019 00:50

                                                        А что, у монстра нет свойства, сколько у него есть монет, чтобы корректно распределить их между несколькими игроками?


                                                        Если нет, то всё даже упрощается: одна функция будет создавать анимацию что для монстра, что для игрока.


                                                        1. TheShock
                                                          19.05.2019 00:53

                                                          Так откуда эта функция знает из какого монстра выпали монетки? У вас в изменившемся стейте просто нету этой информации.


                                                          1. 0xd34df00d
                                                            19.05.2019 00:56

                                                            Не понял, как это нету? Вот у меня значение, представляющего старое состояние монстра, вот — новое. Этого недостаточно?


                                                            Если у вас в монстре нет достаточной информации о том. что это за монстр, чтобы к нему анимацию привязать, то вам и ООП не поможет.


                                                            1. TheShock
                                                              19.05.2019 01:42

                                                              Вот у меня значение, представляющего старое состояние монстра, вот — новое. Этого недостаточно?
                                                              Конечно недостаточно. У вас не хватает информации о причинно-наследственной связи, что деньги добавились из-за появления монстра. У вас два независимых факта — монстр умер. У игрока изменились деньги. Это факты совершенно независимы.

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


                                                              1. 0xd34df00d
                                                                19.05.2019 01:49

                                                                Если у вас анимации над монстром зависят от игрока, то вы просто ещё передаёте игрока в ту функцию, которая занимается созданием анимаций. В чём проблема-то?


                                                                И я по-прежнему не понимаю, какое это отношение имеет к расчёте изменения состояний игрока и монстра.


                                                                1. TheShock
                                                                  19.05.2019 02:01

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


                                                                  1. 0xd34df00d
                                                                    19.05.2019 02:16

                                                                    Так, я совсем перестал понимать. Почему этой информации у меня нет, если у меня есть всё состояние системы, что старое, что новое?


                                                                    1. TheShock
                                                                      19.05.2019 02:46

                                                                      Смотрите. Падает граната, умирает 3 существа. Вот сабсет старого и нового состояний (только интересные для нас части)

                                                                      {
                                                                      	player: { gold: 200 },
                                                                      	monsters: [
                                                                      		{ id: 1, isDead: false },
                                                                      		{ id: 2, isDead: false },
                                                                      		{ id: 3, isDead: false },
                                                                      		{ id: 4, isDead: false },
                                                                      		{ id: 5, isDead: false },
                                                                      	]
                                                                      }
                                                                      
                                                                      // ==>
                                                                      
                                                                      {
                                                                      	player: { gold: 256 },
                                                                      	monsters: [
                                                                      		{ id: 1, isDead: true },
                                                                      		{ id: 2, isDead: true },
                                                                      		{ id: 3, isDead: true },
                                                                      		{ id: 4, isDead: false },
                                                                      		{ id: 5, isDead: false },
                                                                      	]
                                                                      }
                                                                      
                                                                      


                                                                      У вас есть независимые факты:
                                                                      1. Умер монстр 1
                                                                      2. Умер монстр 2
                                                                      3. Умер монстр 3
                                                                      4. Изменилось количество золота у игрока

                                                                      Но у вас нет информации о том, сколько над каждым из монстров голды необходимо нарисовать


                                                                      1. Bronx
                                                                        19.05.2019 04:29

                                                                        Если монстры равноценные, то информация есть: 3 монстра умерло, 6 монет прибавилось, итого 2 монеты с каждого монстра.


                                                                        Если монстры имеют разный "вес", то у вас состояния не полны, не хватает стоимостей жизни монстров.


                                                                        UPD: Сорри, там +56 монет, и на 56 на 3 не делится. Значит вам наверняка придётся использовать monster.id чтобы получить информацию о "весе" каждого монстра.


                                                                        1. TheShock
                                                                          19.05.2019 04:59

                                                                          Да, я понимаю, это классический костыль программистов, которые не знают, как решать эту задачу. Он работает, но работает плохо. Ну вот к примеру за kill-streak вы получаете за каждого следующего монстра на 10 монет больше. И вот уже id'шника одного монстра недостаточно ;)

                                                                          Или другой вариант. У вас в стейте на монстре висит эффект из-за которого монет именно и дается больше, но при смерти эффект с монстра спадает. Да, это эффект был в старом стейте, но в новом — уже нету. Вы будете проверять и старый и новый стейт?

                                                                          Что, если вы получаете на 10 монет больше за каждого 10 монстра?

                                                                          Или или вопрос значительно проще, который я уже задавал — что, если это была цепная молния — как вы её отобразите? Ну нету в стейте инфы, почему умерли эти персонажи — то ли файрбол в них попал, то ли молния цепная, а может и от старости умерли. Просто в прошлом стейте они были живы, а сейчас — они мертвы.


                                                                          1. Bronx
                                                                            19.05.2019 06:14

                                                                            Это "костыль" лишь потому, что вы не удосужились положить в стейт достаточно информации, и её приходиться довычислять. Нужно учитывать kill-streak или давать бонус за каждого 10-й kill? Сделайте эту информацию доступной, например, храните в состоянии историю побед игрока. Нужно учитывать, как умерли персонажи — сохраняйте информацию об обстоятельствах смерти. Ну, например, слегка переделав ваш пример:


                                                                            {
                                                                              player: { 
                                                                                gold: 256,
                                                                                xp: 9001,
                                                                                hp: 100500,
                                                                                kills: [
                                                                                    <... previous kills ...>,
                                                                                    { id: 1, with: "lightning", time: 12345 },
                                                                                    { id: 2, with: "lightning", time: 12345 },
                                                                                    { id: 3, with: "lightning", time: 12345 }
                                                                                  ],
                                                                                },
                                                                              monsters: [
                                                                                { id: 1, lvl: 25, hp: 0 },
                                                                                { id: 2, lvl: 25, hp: 0 },
                                                                                { id: 3, lvl: 6, hp: 0 },
                                                                                { id: 4, lvl: 50, hp: 100 },
                                                                                { id: 5, lvl: 20, hp: 500 },
                                                                              ]
                                                                            }

                                                                            — и вот внезапно у нас есть всё, чтобы вычислить и монетки, и способ убийства (равно как и связать несколько убийств в цепочку), и бонус за стрейк, и бонус за общее число побед.


                                                                            Вы будете проверять и старый и новый стейт?

                                                                            Блин, ну а зачем нам оба стейта, если мы их не собираемся сравнивать? Конечно же будем! Смысл анимации — плавная интерполяция между двумя состояниями, значит нам по-любому нужно из нового состояния вычесть старое (что и означает "сравнить"), и все полученные отличия так или иначе интерполировать.


                                                                            1. TheShock
                                                                              19.05.2019 06:27

                                                                                  kills: [
                                                                                      <... previous kills ...>,
                                                                                      { id: 1, with: "lightning", time: 12345 },
                                                                                      { id: 2, with: "lightning", time: 12345 },
                                                                                      { id: 3, with: "lightning", time: 12345 }
                                                                                    ],
                                                                                  },


                                                                              Стоп. А они умерли от одной цепной молнии, или от трех разных? А если персонаж этой цепной молнией убил первого и третьего, а второго только ранил. У вас к массиву killed ещё появится массив wounded? Time — это вообще интересное. Откуда эта вьюшная деталь взялась в модели?


                                                                              1. Bronx
                                                                                19.05.2019 07:20

                                                                                А они умерли от одной цепной молнии, или от трех разных?

                                                                                Они умерли в одно и то же время от одного и того же оружия, и это оружие позволяет "убийство по цепочке", так что нетрудно вывести, кто от чего помер. Если этого недостаточно — кто вам запрещает добавлять в стейт больше деталей, например, добавить id оружия/удара?


                                                                                А если персонаж этой цепной молнией убил первого и третьего, а второго только ранил

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


                                                                                Time — это вообще интересное. Откуда эта вьюшная деталь взялась в модели?

                                                                                Time — не вьюшная деталь, она позволяет 1) коррелировать события между собой, 2) вычислять бонусы за стрики (скажем, 5 убийств за 10 секунд => streak bonus), 3) вычислять или ограничивать kill rate и т.п.


                                                                          1. 0xd34df00d
                                                                            19.05.2019 17:09

                                                                            как вы её отобразите? Ну нету в стейте инфы

                                                                            Добавлю в стейт инфу. Если в предметной области необходима некоторая инфа в стейте, то представляется разумным её там хранить.


                                                                        1. TheShock
                                                                          19.05.2019 05:14

                                                                          Давайте я вам приведу реальный пример из реальной пошаговой игры.

                                                                          На игроке висит «проклятие» — за каждое действие, которое он совершает — он получает spellLevel*10 урона. Ну если он не джин, который получает от проклятия в два раза меньше урона. А ассасин получает от спеллов в два раза больше урона.

                                                                          А дракон отражает 20% урона от спеллов (но только от спеллов) назад в кастующего

                                                                          Если вы думаете, что это какой-то бред, то посмотрите на MTG, Heartstone и т.п. — я затронул только верхушку айсберга

                                                                          Значит, игрок играет спелл, который его юнит лечит на 100, а противнику наносит 100 урона.

                                                                          ГеймОунер просит следующее отображение действий:
                                                                          1. Играется анимация спелла
                                                                          2. Юнит игрока получает свой хил (+100 хп)
                                                                          3. Противник получает свой урон (-100хп)
                                                                          4. Срабатывает абилка — играется анимация
                                                                          5. Юнит игрока получает урон (-20хп)
                                                                          6. Противник, если у него закончились хиты, умирает
                                                                          7. (Тут может ещё что-то произойти из-за смерти противника, к примеру начислятся деньги)
                                                                          8. В этот момент игрок получает урон от проклятия (-10хп)

                                                                          Что же мы будем иметь в стейте? Всё, что есть — у игрока +70 хп. И как нам с этой информацией работать? Как развернуть то одно изменение модели в эти 8 последовательных анимаций?


                                                                          1. Bronx
                                                                            19.05.2019 06:21

                                                                            Что же мы будем иметь в стейте? Всё, что есть — у игрока +70 хп.

                                                                            Не понял. По вашим же словам выше, "в результате выполнения функции hit у вас появился новый мир.". Т.е. для вашей функции отрисовки доступно две вселенных — старая и новая. И вы говорите, что всё, что в этой вселенной есть — это hp игрока? Там нет информации о проклятиях, спеллах и прочем? Как это может быть?


                                                                            1. TheShock
                                                                              19.05.2019 06:35

                                                                              Там нет информации о проклятиях, спеллах и прочем? Как это может быть?

                                                                              Конечно есть. Давайте постараемся понять, что есть в вашем стейте, синхронизируемся с вами.

                                                                              У вас есть юнит с id=123, джин, его владелец — игрок с id=5, на джине висит проклятие. Ещё есть юнит с id=42, он дракон.

                                                                              Нам пришёл новый стейт, в котором у юнита с id=123 hp увеличились на 70, а у юнита с id=42 hp упали на 100. Ну и вся та остальная информация.

                                                                              Ладно, на базе предыдущего обсуждения давайте добавим, что у джина ещё в каком-то массиве wounded появился дракон.

                                                                              Как вы всё это проанимируете? Видите ли, причинно-следственные связи у вас потеряны и, предполагаю, во вьюшке вы хотите их восстановить. Правильно ли я понимаю, что вы предлагаете вьюшке залезть в джина, проверить, лежит ли на нём проклятие и если лежит, то понять, что в результате него была получена часть урона. Так же вьюшка должна посмотреть, что целью атаки был дракон, знать, что дракон отражает часть спелла. Для этого вьюшка должна узнать, что за спелл был применен и высчитать, сколько дамаги дракон отразил. Всё остальное — это лечение.

                                                                              Может я неправильно понял, вы такой видите вьюшку?


                                                                              1. Bronx
                                                                                19.05.2019 08:28

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


                                                                                Видите ли, причинно-следственные связи у вас потеряны

                                                                                Если вы можете легко восстановить информацию о связях, имея прошлое и настоящее состояние, это не называется "данные потеряны". Ваша цель — продумать структуру состояния так, чтобы вам всегда было достаточно данных. Если вам лень лазить по графу персонажей и восстанавливать события из разницы их состояний — ну заведите тогда "лог событий" как часть состояния и пишите в него все действия всех персонажей явным образом. Это может упростить анимацию, но может усложнить другие вещи.


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

                                                                                Это всё нужно для анимации джина? Не проще посчитать урон через разницу hp на текущем и предыдущем шаге? И зачем вьюшке знать причину урона, как это отражается на анимации? Показываете сообщение? Ну тогда да, вьюшке нужна эта информация, как иначе-то?


                                                                                То же самое про дракона: если для анимации вьюшке нужно знать, какой спелл был применён — значит ей нужно получить этот спелл. Если "отразил часть спелла" как-то влияет на анимацию — значит и это нужно знать. Урон можно посчитать просто как разницу между двумя числами, непонятно зачем там сложная логика вычислений.


                                                                                1. PsyHaSTe
                                                                                  19.05.2019 10:18
                                                                                  -3

                                                                                  Я поражен терпением вашим и 0xd34df00d. По 100 кругу переходить от "Да не, не будет работать, давайте усложним модель" к "Да пожалуйста, вот так вот будет работать", и опять к "давайте снова усложним".


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


                                                                                  1. michael_vostrikov
                                                                                    19.05.2019 10:32
                                                                                    -2

                                                                                    какой-то менеджер, который должен разрулить кто кому чего нанес

                                                                                    Потому что в реальности он есть. Игрок бросил камень в противника, а пока он летел, противник увернулся. Кто здесь считает, кто кому чего нанес? Точно не игрок и не его противник. Если мы делаем модель боя, значит и модель этой части должна быть. Если вы ее убираете по каким-то причинам, значит у вас неточная модель, и если будут проблемы, то это из-за неточной модели, а не из-за ООП.


                                                                                    1. PsyHaSTe
                                                                                      19.05.2019 10:36

                                                                                      Потому что в реальности он есть. Игрок бросил камень в противника, а пока он летел, противник увернулся. Кто здесь считает, кто кому чего нанес? Точно не игрок и не его противник.

                                                                                      Ну вот я и говорю об этом. ООП уже в самом мышлении. "Где-то ДОЛЖЕН быть объект, который все считает". Почему должен и почему именно объект, это очень любопытный вопрос.


                                                                                      1. akryukov
                                                                                        19.05.2019 10:48

                                                                                        Проблема на самом деле не в том, что это объект, а в том, как правильно выбирать способ взаимодействия в случае, когда у нас иерархия Player и иерархия Monster.


                                                                                        1. 0xd34df00d
                                                                                          19.05.2019 17:14

                                                                                          А на что влияет иерархия Player и иерархия Monster, и почему её нельзя выразить свойствами единого типа данных Player и Monster соответственно?


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


                                                                                          1. akryukov
                                                                                            19.05.2019 17:23

                                                                                            В общем то, ни на что. Я принял это как одно из ограничений задачи. Интересно было, как сделать именно с иерархией.


                                                                                      1. michael_vostrikov
                                                                                        19.05.2019 11:03

                                                                                        Почему должен

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


                                                                                        1. PsyHaSTe
                                                                                          19.05.2019 11:31

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


                                                                                          1. akryukov
                                                                                            19.05.2019 11:33

                                                                                            Где вы увидели предложение делать менеджера фабрик ударов?


                                                                                            1. PsyHaSTe
                                                                                              19.05.2019 11:35

                                                                                              Это гипербола. На текущем этапе пока что предлагается только иметь менеджера, который с помощью паттерна команда посылает запросы о нанесении ударов. Чуть выше об этом писалось. Но я уверен, что чуть-чуть обсуждения (особенно в описанных ситуациях с анимациями/killing spree/...), и мы к ним придем.


                                                                                              1. akryukov
                                                                                                19.05.2019 11:40

                                                                                                Но я уверен, что чуть-чуть обсуждения (особенно в описанных ситуациях с анимациями/killing spree/...), и мы к ним придем.

                                                                                                Ну так давайте идти. Конкретные примеры обсуждать полезнее, чем гиперболы про менеджера фабрик ударов.
                                                                                                Какие конкретно правила нам нужно добавить, чтобы вводить такие классы? Покажете ваш вариант кода, где не нужно их вводить?


                                                                                              1. michael_vostrikov
                                                                                                19.05.2019 14:52

                                                                                                На текущем этапе пока что предлагается только иметь менеджера, который с помощью паттерна команда посылает запросы о нанесении ударов.

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


                                                                                                Нет, нам надо моделировать удар игрока по монстру

                                                                                                Да, который в физической реальности происходит через посредника в виде физических законов и окружающей среды, которые в удар игрока вносят свои коррективы.


                                                                                                особенно в описанных ситуациях с анимациями

                                                                                                В описанных ситуациях с анимациями, насколько я понимаю, и подразумеваются сообщения о событиях как способ рендерить все это асинхронно.


                                                                                          1. Druu
                                                                                            19.05.2019 13:27
                                                                                            +1

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

                                                                                            Алгоритм, который принимает описание эффекта удара и монстра и применяет данный эффект к монстру и будет де-факто "менеджер ударов", как он по факту будет реализован (в виде класса со вспомогательными ф-ми или просто в виде одной ф-и или еще как) — не важно, вообще.


                                                                                      1. EvgeniiR
                                                                                        19.05.2019 11:36

                                                                                        Ну вот я и говорю об этом. ООП уже в самом мышлении. «Где-то ДОЛЖЕН быть объект, который все считает»

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

                                                                                        Какой подход проще и лучше в вашей ситуации решать вам. Геймдев довольно специфичная облась где популярны другие подходы.

                                                                                        А под этим постом явно происходит заблуждение о котором Страуструп писал
                                                                                        «X — это хорошо. Объектная ориентированность — это хорошо. Следовательно, X является объектно-ориентированным»

                                                                                        Люди заявляют что им больше нравится класс менеджер и отсутствие инкапсуляции, и на основе того что это удобно заявляют что это ООП


                                                                                      1. ApeCoder
                                                                                        19.05.2019 11:40

                                                                                        С точки зрения модульности, которая есть ив ФП эта фнукция должна принадлежать какому-то модулю. Почему нельзя этот модуль роассматривать как объект (Game, CollisionProcessingRules, etc..)


                                                                                        Подобно тому, как в смолтоке классы тоже объекты


                                                                                        1. 0xd34df00d
                                                                                          19.05.2019 17:15

                                                                                          Именно. И не надо пытаться из классов сделать модули, получается ерунда.


                                                                                    1. michael_vostrikov
                                                                                      19.05.2019 14:56
                                                                                      -1

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


                                                                                      1. Druu
                                                                                        19.05.2019 15:10

                                                                                        какой объект

                                                                                        God Object.
                                                                                        Да, реальность писал какой-то говнокодер.


                                                                                      1. VolCh
                                                                                        20.05.2019 11:54

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


                                                                                        1. michael_vostrikov
                                                                                          20.05.2019 13:42

                                                                                          Ну так и я о том же. Среда передает сообщения, расчеты делегируются коду с законами физики. У меня в примере он в родительском классе, но можно и через стратегию сделать.


                                                                                    1. khim
                                                                                      19.05.2019 22:33

                                                                                      Кто здесь считает, кто кому чего нанес?
                                                                                      Компьютер считает, компьютер.

                                                                                      Точно не игрок и не его противник.
                                                                                      Вы из игрока и противника каких-то самостоятельных личностей сделали. Но зачем?


                                                                                      1. michael_vostrikov
                                                                                        20.05.2019 01:11

                                                                                        Компьютер считает, компьютер.

                                                                                        Я же написал, что вопрос про нашу физическую реальность. Различные аспекты которой мы моделируем.


                                                                                        Вы из игрока и противника каких-то самостоятельных личностей сделали. Но зачем?

                                                                                        Ну дак в реальности они самостоятельные личности. Низачем, это просто факт.


                                                                                  1. TheShock
                                                                                    19.05.2019 14:06

                                                                                    «давайте снова усложним».
                                                                                    В смысле «усложним»? Я вам рассказываю о базовых требованиях в геймдеве. Все эти «усложнения» — это начало разработки, фичи, которые появляются в первом прототипе. То, как многие игры должна работать изначально. И я говорю о крайне простых играх.

                                                                                    Хотите усложнение? Посмотрите Хартстоун или МТГ


                                                                                  1. 0xd34df00d
                                                                                    19.05.2019 17:12

                                                                                    Непонятно, почему с вами несогласны, ведь именно так всё и выглядит.


                                                                                    С другой стороны, пока в ООП-подходе обсуждают, чья ответственность взрываться у гранаты, в Виллабаджо ФП уже обсуждают, как обрабатывать киллстрики и цепные молнии.


                                                                                    Впрочем, ту дискуссию про гранаты выше я не осилил, если честно, слоожна как-то. Вот про functor functors проще как-то с утра зашло, чем это вот.


                                                                                    1. TheShock
                                                                                      19.05.2019 17:26

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


                                                                                    1. TheShock
                                                                                      19.05.2019 17:38

                                                                                      С другой стороны, пока в ООП-подходе обсуждают, чья ответственность взрываться у гранаты, в Виллабаджо ФП уже обсуждают, как обрабатывать киллстрики и цепные молнии.

                                                                                      Кстати, это как раз претензия скорее к ФПшникам. Ведь люди, которые практически этим занимаются — задают практические вопросы. А ФПшники классически теоретизируют — :
                                                                                      — А вот ответственность…
                                                                                      — А вот теоретически…
                                                                                      — А вот у вас не очень подходящее имя у метода…
                                                                                      — А вот у меня есть стереотип про фабрику фабрик
                                                                                      — А вот мне не нравится слово «менеджер» в названии

                                                                                      Вот на уровень вопросов вы и обратили внимание


                                                                                      1. 0xd34df00d
                                                                                        19.05.2019 21:20

                                                                                        А, то есть, все те люди — они ФПшники, потому что теоретизируют?


                                                                                        Ну ок.


                                                                                1. TheShock
                                                                                  19.05.2019 14:52
                                                                                  +1

                                                                                  Не проще посчитать урон через разницу hp на текущем и предыдущем шаге?
                                                                                  Не проще, я же выше показывал, что в результате удара урон и хил должен отображаться раздельно:

                                                                                  ГеймОунер просит следующее отображение действий:
                                                                                  1. Играется анимация спелла
                                                                                  2. Юнит игрока получает свой хил (+100 хп)
                                                                                  3. Противник получает свой урон (-100хп)
                                                                                  4. Срабатывает абилка — играется анимация
                                                                                  5. Юнит игрока получает урон (-20хп)
                                                                                  6. Противник, если у него закончились хиты, умирает
                                                                                  7. (Тут может ещё что-то произойти из-за смерти противника, к примеру начислятся деньги)
                                                                                  8. В этот момент игрок получает урон от проклятия (-10хп)


                                                                                  Над джином мы должны показать +100, через некоторое время -20, потом -10. И это всё в рамках реакции на один удар. Вы же понимаете, что в наивном стейте у нас будет просто +70.

                                                                                  ну заведите тогда «лог событий» как часть состояния
                                                                                  Во, вы начинаете понемногу приходить к понимаю. Спасибо вам за ответы, давайте я теперь расскажу, как это делается на самом деле. Вся суть в паттерне Команда. Я его, правда, немного изменил под себя.

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

                                                                                  Не должно быть метода Player.hit или функции hit. Для каждого действия заводится команда. У неё есть паблик-ридонли аргументы, которые характеризуют её и метод exec. Далее — TypeScript

                                                                                  class HitCommand extends Command {
                                                                                    public constructor (
                                                                                      readonly source: Unit,
                                                                                      readonly target: Unit,
                                                                                    ) {}
                                                                                    
                                                                                    public exec () {
                                                                                      // тут действия, которые выполняются, когда необходимо сделать hit.
                                                                                    }
                                                                                  }
                                                                                  


                                                                                  Тут можно вспомнить туповатую статью, которая говорит никогда так делать. Но она на то и туповатая, чтобы её не слушать.

                                                                                  Дальше, команды могут вызывать другие команды. У меня это делается через `addChild` — таким образом строится дерево, кто и почему.

                                                                                  class HitCommand extends Command {
                                                                                    public constructor (
                                                                                      readonly source: Unit,
                                                                                      readonly target: Unit,
                                                                                    ) {}
                                                                                    
                                                                                    public exec () {
                                                                                      this.addChild(new DamageCommand(target, source.getPower()));
                                                                                    }
                                                                                  }
                                                                                  
                                                                                  class DamageCommand extends Command {
                                                                                    public constructor (
                                                                                      readonly target: Unit,
                                                                                      readonly value: number
                                                                                    ) {}
                                                                                  }
                                                                                  
                                                                                  class TryKillCommand extends Command {
                                                                                    public constructor (
                                                                                      readonly target: Unit
                                                                                    ) {}
                                                                                    
                                                                                    public exec () {
                                                                                      if (this.target.hp <= 0) {
                                                                                        this.addCommand(new KillCommand());
                                                                                      }
                                                                                    }
                                                                                  }
                                                                                  
                                                                                  class KillCommand extends Command {
                                                                                    public constructor (
                                                                                      readonly target: Unit
                                                                                    ) {}
                                                                                    
                                                                                    public exec () {
                                                                                      this.target.isDead = true;
                                                                                    }
                                                                                  }
                                                                                  
                                                                                  


                                                                                  Когда мы выполняем root-команду — у нас получается приблизительно такое дерево (скрин из консоли реальной игры):



                                                                                  Это чистая модель и с этим очень приятно работать. Но как с этим работает вьюшка?
                                                                                  Она получает дерево команд и у неё есть маппинг Command => Reaction. Она знает, как проиграть удар, как — нанесение дамаги.

                                                                                  Вьюшка получается крайне тонкой. Что-то вроде такого:

                                                                                  class HitReaction extends Reaction<HitCommand> {
                                                                                  
                                                                                    constructor (
                                                                                      readonly command: HitCommand,
                                                                                      readonly units  : UnitsContainer,
                                                                                    ) {}
                                                                                  
                                                                                    async play () {
                                                                                      const { source, target } = this.command;
                                                                                      const sourceView = units.find(source);
                                                                                      const targetView = units.find(target);
                                                                                    
                                                                                      await sourceView.rotateTo(targetView);
                                                                                      await sourceView.launchHitAnimation();
                                                                                    }
                                                                                  }
                                                                                  
                                                                                  class DamageReaction extends Reaction<DamageCommand> {
                                                                                  
                                                                                    constructor (
                                                                                      readonly command: DamageCommand,
                                                                                      readonly units  : UnitsContainer,
                                                                                      readonly bubbles: BubblesView,
                                                                                    ) {}
                                                                                  
                                                                                    async play () {
                                                                                      const { target, value } = this.command;
                                                                                      const targetView = units.find(target);
                                                                                    
                                                                                      await bubbles.create(
                                                                                        value,
                                                                                        BubblesView.DamageStyle,
                                                                                        targetView.position
                                                                                      );
                                                                                    }
                                                                                  }
                                                                                  
                                                                                  


                                                                                  Видите? У меня вьюшка вообще не заморачивается какими-то деталями модели. Ей плевать, висит ли на юните проклятье. Пришел дамаг? Она отобразила дамаг. Вьюшка получается крайне тонкой и очень легко поддерживается. Модель — отвечает только за свою часть и легко расширяется. Все действия могут композироваться друг с другом. И если в результате демеджа должно что-то произойти — оно произойдет, кто бы этот демедж не нанес.

                                                                                  И я понимаю, что всё это можно повторить на ФП. Создать рекорды, вручную их заносить в лог. Сделать, чтобы команда вместо стейта возвращала [ ActionsTree, NewWorld ]. Но это не так удобно. В системе с деревом команд оно всё просто работает, само по себе и её крайне удобно использовать. Любое действие можно сделать в любой момент. Хотите написать чит, который убивет лишнего персонажа? Элементарно!

                                                                                  game.exec( new KillCommand(unit) );
                                                                                  


                                                                                  И вьюшка это корректно проанимирует. Все, кому необходима эта информация — её получат.

                                                                                  Хотите дать денег игроку? Просто new GiveMoneyCommand(player, value) и оно заработает!

                                                                                  Хотите сделать реплей для вашей игры? Проще простого — сохраняете начальный стейт и дерево команд.

                                                                                  У вас должен вестись подсчёт убитых и отображаться в конце боя? Никаких проблем, вам не нужно где-то в функции kill вызывать другую функцию. Просто StatisticsSystem ждет все KillCommand.

                                                                                  И так далее. Оно работает. Это очень просто поддерживать. Любой приходящий программист с этим разберется за пару дней. Оно позволяет написать тесты на любом уровне.

                                                                                  У вас произошёл баг? Открываете консоль и видите все изменения стейта в крайне удобочитаемом виде.

                                                                                  0xd34df00d, вот и мой ответ, как я обещал выше.


                                                                                  1. 0xd34df00d
                                                                                    19.05.2019 17:23

                                                                                    Над джином мы должны показать +100, через некоторое время -20, потом -10. И это всё в рамках реакции на один удар. Вы же понимаете, что в наивном стейте у нас будет просто +70.

                                                                                    Я чё-то не понял ничего, тут уже какое-то совсем притягивание решения к ответу происходит, по-моему.


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


                                                                                    В наивном стейте у меня будет как минимум четыре изменения состояния (я столько насчитал в вашей последовательности действий), которые приведут к тому количеству анимаций, которое вы и ожидаете.


                                                                                    Вся суть в паттерне Команда.

                                                                                    Так никто не мешает и в ФП хранить список необходимых действий (или анимаций, скажем), и в части кода, ответственной за рендер, тупо их отображать.


                                                                                    Просто это происходит настолько естественно, что никому не приходит в голову называть это паттерном, выделять это в отдельный объект в ментальной модели (pun intended) и писать про это статьи.


                                                                                    1. TheShock
                                                                                      19.05.2019 17:27

                                                                                      Просто это происходит настолько естественно, что никому не приходит в голову называть это паттерном
                                                                                      И, конечно, в примерах про это забывается и нигде не отображается


                                                                                      1. 0xd34df00d
                                                                                        19.05.2019 21:22

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


                                                                                        1. TheShock
                                                                                          19.05.2019 22:30

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


                                                                                          1. 0xd34df00d
                                                                                            19.05.2019 22:45

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


                                                                                            1. Druu
                                                                                              20.05.2019 10:47
                                                                                              +1

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

                                                                                              Ну смотрите, тот же наивный подход можно и в "ООП" сделать, просто сунув этот сраный hit или hitBy или как вы там его обзовете куда угодно. Решение будет полностью эквивалентно ФП варианту. И в обоих случаях проблемы начинаются, когда: "а что если мне потом надо Х?".
                                                                                              И если вы сделаете все расширяемо, то с-но и опять же в обоих случаях что в ООП что в ФП будет одинаково — представление некоего глобального стейта, представление акторов (монстры/игроки), представление эффектов и алгоритм апдейта стейта на тик.


                                                                                              1. 0xd34df00d
                                                                                                20.05.2019 17:07

                                                                                                Ну смотрите, тот же наивный подход можно и в "ООП" сделать, просто сунув этот сраный hit или hitBy или как вы там его обзовете куда угодно.

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


                                                                                                ФП тут, наверное, да, неправильный определяющий критерий.


                                                                                                1. Druu
                                                                                                  21.05.2019 10:46
                                                                                                  +1

                                                                                                  Нельзя по определению. Потому что у вас нет одного сраного hit как самодостаточной сущности, вам обязательно надо, чтобы объекты, и кому-то оно принадлежало

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


                                                                                                  В чистом ФП тут проблемы "как бы" нет, потому что такого понятия как "засунуть в" нет, ведь нету классов.
                                                                                                  Но если например добавить тайпклассы то проблема сразу возвращается — сделаем ли мы тайплкасс тех кто бьет или тех кого бьют?


                                                                                                1. dimm_ddr
                                                                                                  21.05.2019 11:26

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


                                                                                    1. TheShock
                                                                                      19.05.2019 17:45

                                                                                      что это последовательность действий, а потом про это резко забываете и пишете

                                                                                      всмысле забываю? Я как раз прекрасно об этом помню. Если у меня будет в результате удара три независимых урона — это отобразится на дереве и оно отрисует три независимых урона.

                                                                                      В наивном стейте у меня будет как минимум четыре изменения состояния
                                                                                      Откуда? Если у вас newState = hit(oldState, player, monster). Я тут вижу ровно одно изменение состояния.

                                                                                      хранить список необходимых действий
                                                                                      И как это делать? Вручную формировать и заполнять?

                                                                                      Почему вы вечно рассказываете, как это просто, а когда дело доходит до кода, то всего этого в коде — нету?


                                                                                      1. 0xd34df00d
                                                                                        19.05.2019 21:26

                                                                                        Если у меня будет в результате удара три независимых урона — это отобразится на дереве и оно отрисует три независимых урона.

                                                                                        Ну и у меня будет три независимых анимации (которые потом склеятся в одну как вам угодно будет).


                                                                                        Если у вас newState = hit(oldState, player, monster). Я тут вижу ровно одно изменение состояния.

                                                                                        А если это


                                                                                        hit :: MonadState SomeStateType m => m ()
                                                                                        hit = do
                                                                                          heal
                                                                                          attack
                                                                                          damage
                                                                                          spell

                                                                                        или какие там имена подходить будут?


                                                                                        И как это делать? Вручную формировать и заполнять?

                                                                                        Не более вручную, чем у вас.


                                                                                        Почему вы вечно рассказываете, как это просто, а когда дело доходит до кода, то всего этого в коде — нету?

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


                                                                                        1. TheShock
                                                                                          19.05.2019 22:29

                                                                                          hit = do
                                                                                            heal
                                                                                            attack
                                                                                            damage
                                                                                            spell


                                                                                          Не, так не пойдет.

                                                                                          hit — наносит урон. Ну вот псевдокодом:

                                                                                          hit (source, target) {
                                                                                            damage(target, source.getPower());
                                                                                          }


                                                                                          А вот уже в damage происходит убийстов:

                                                                                          damage (target, value) {
                                                                                            target.hp -= value;
                                                                                            if target.hp < 0 {
                                                                                              kill(target);
                                                                                            }
                                                                                          }


                                                                                          У вас не получится всё это причинно-следственное дерево в каждом действии вручную развернуть.

                                                                                          ваши примеры для меня по выразительности не особо отличаются от моих примеров
                                                                                          Ваши примеры достаточно выразительны (учитывая, что Хаскель я не знаю). Но не всегда могут корректно работать в реальном приложении, увы.

                                                                                          Реакт в своих примерах я не использую.


                                                                                          1. 0xd34df00d
                                                                                            19.05.2019 22:47

                                                                                            Не, так не пойдет.

                                                                                            Это перевод действий из вашего списка. Исцеление, атака монстра, дамаг от монстра, дамаг от проклятия.


                                                                                            Можно не hit назвать, а cycle, или ещё как-то. Я игры мало разрабатываю, не знаю, как это назвать в терминах предметной области.


                                                                                            У вас не получится всё это причинно-следственное дерево в каждом действии вручную развернуть.

                                                                                            Что значит «вручную развернуть»?


                                                                                            1. TheShock
                                                                                              19.05.2019 22:59
                                                                                              -1

                                                                                              Это перевод действий из вашего списка. Исцеление, атака монстра, дамаг от монстра, дамаг от проклятия.
                                                                                              Знаете пословицу «вассал моего вассала — не мой вассал»?
                                                                                              Ну то есть death — это вассал damage.
                                                                                              А damage — вассал death.

                                                                                              hit создает damage, damage создает death. Но hit ничего не знает о том, что будет создано в damage, соответственно не знает о death.


                                                                                              1. 0xd34df00d
                                                                                                20.05.2019 00:30
                                                                                                -1

                                                                                                Ну и хорошо, не знает, в моём коде он тоже про это не знает, он именно что просто вызывает damage.


                                                                                  1. Bronx
                                                                                    20.05.2019 00:54

                                                                                    Над джином мы должны показать +100, через некоторое время -20, потом -10. И это всё в рамках реакции на один удар. Вы же понимаете, что в наивном стейте у нас будет просто +70.

                                                                                    Нет, я не понимаю, почему стейт должен быть настолько наивным? Просто чтобы сделать вас правым? :) Кто запрещает вам положить в стейт всё, что необходимо? Вы считатете, что стейт — это только мгновенные значения координат игроков и их атрибутов (здоровья, экспы), и не может включать "память" персонажей о событиях? Это как если бы ваше собственное состояние включало бы только ваше местоположение, пульс, температуру, мгновенный слепок мысли и эмоций, но не включало бы ваши воспоминания и опыт.


                                                                                    В нормальном стейте будет не только изменение здоровья, но также и информация о применении спелла по противнику, а также о наличии абилки и проклятия, что даёт всю необходимую информацию. Функция анимации получает два стейта — прошлый и настоящий — делает дифф, видит в диффе, что был применён спелл, видит, по кому он был применён, видит в стейте абилку и проклятие, вычисляет, какие кадры и в какой последовательности нужно показать (прямо по вашему списку), выплёвывает эту последовательность движку и забывает о ней. Эта последовательность может быть в виде ваших команд, или в виде набора готовых спрайтов — это уже неважно. У вас есть, по сути, две чистых функции для обсчёта игры:


                                                                                    makeMove(past: World, input: GamerInput) => World;
                                                                                    makeEffects(past: World, present: World) => AnimationFrame[]

                                                                                    и две грязных для ввода-вывода:


                                                                                    getInput() => GamerInput;
                                                                                    applyEffects(AnimationFrame[]) => void;

                                                                                    Вы вольны положить в структуру World всё, что вам необходимо, чтобы восстановить события между двумя ходами. Функция getInput получает пользовательский ввод с игрового контроллера. ФункцияmakeMove применяет пользовательский ввод и высчитывает новое состояние мира, включая историю применения вооружений и артефактов, makeEffects вычисляет чистую последовательность визуальных эффектов на основе всех этих изменений. applyEffects проигрывает сгенерённую последовательность на дисплее.


                                                                                    1. mayorovp
                                                                                      20.05.2019 08:37

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


                                                                      1. 0xd34df00d
                                                                        19.05.2019 17:07

                                                                        А где эта информация в ООП-мире возьмётся? У монстра не было голды в состоянии?


                                                                        1. TheShock
                                                                          19.05.2019 17:28

                                                                          Высчитается на базе нескольких параметров


                                                                          1. 0xd34df00d
                                                                            19.05.2019 21:21

                                                                            А кто мешает в ФП-стиле это высчитать на базе нескольких параметров?


                                                                            1. TheShock
                                                                              19.05.2019 22:23

                                                                              В модели — ничего не мешает. В модели оно просто вычесляется в любой парадигме.


            1. adictive_max
              17.05.2019 10:29

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


              1. EvgeniiR
                17.05.2019 11:34

                И ничего страшно, что один и тот же объект может и содержать свои инкапсулированные методы, и выступать структурой данных для внешних функций

                То есть другие объекты будут доставать данные из этого объекта, и работа над данными будет отделена от самих данных. Это самое, что ни на есть, нарушение инкапсуляции, и возвращение к процедурному программированию.


                1. adictive_max
                  17.05.2019 11:45

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


                  1. EvgeniiR
                    17.05.2019 16:37

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

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


                    1. adictive_max
                      17.05.2019 17:21

                      Но ведь он при вызове методов других объектов будет использовать значение внутренних переменных. Например

                      class Player {
                        val strength = 10
                        fun dealDamage(Target t){
                          t.takeDamage(strength)
                        }
                      }
                      Или если объект «по своей инициативе» выкинул состояние наружу, это не считается?

                      А если самую чуточку усложнить, и урон будет зависеть ещё и от параметров экипировки? Просто банальная сумма модификаторов из всех надетых на персонажа шмоток. Эти статы ведь как-то надо из шмоток извлечь, чтобы просуммировать.


                      1. EvgeniiR
                        17.05.2019 17:46

                        Эти статы ведь как-то надо из шмоток извлечь, чтобы просуммировать.

                        Вот, вы хотите из шмоток достать данные, а суммировать снаружи. Логика отдельно от данных :)

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


                        1. 0xd34df00d
                          17.05.2019 20:54

                          Теперь объект снаряжения знает что-то про ведение боя и, например, про монстров (тыкать палкой в водяного монстра глупо). Хорошая инкапсуляция, да. Зато все переменные защищены.


                          1. EvgeniiR
                            17.05.2019 22:15

                            Теперь объект снаряжения знает что-то про ведение боя и, например, про монстров (тыкать палкой в водяного монстра глупо). Хорошая инкапсуляция, да. Зато все переменные защищены.

                            Объект снаряжения знает как он защищает от разных типов атаки, при необходимости какие-нибудь коэффициенты можно выносить в конфиг, мне кажется это логичным, почему нет?

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


                            1. evocatus
                              19.05.2019 01:36

                              Возможно есть какие-то подходы лучше этого


                              Вы в шаге от удивительного открытия.


                              1. EvgeniiR
                                19.05.2019 11:39

                                Возможно есть какие-то подходы лучше этого
                                Вы в шаге от удивительного открытия.

                                Вы уже изобрели идеальный подход для любой ситуации?


                        1. michael_vostrikov
                          19.05.2019 10:10

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

                          Так, а каким образом остальные объекты будут ему эти результаты расчетов сообщать? То есть что имеем, главный объект говорит всем объектам снаряжения "посчитайте результат", у них доступа к другим объектам нет, значит они считают на основании только своих характеристик, что превращается просто в возврат значения, а главный объект их суммирует или еще как-то еще объединяет. Вау, так мы же пришли к тому от чего пытались уйти.


                1. VolCh
                  19.05.2019 13:46

                  Доставать данные из объекта ООП совсем не запрещает. Запрет, если так можно выразиться, есть на прямое изменение состояния объектов. Ваша "работа с данными" — это работа по изменению данных.


                  1. EvgeniiR
                    19.05.2019 14:16

                    Доставать данные из объекта ООП совсем не запрещает. Запрет, если так можно выразиться, есть на прямое изменение состояния объектов.

                    Работа с данными нарушающая инкапсуляцию в примере что я приводил — это логика находящаяся в одном модуле, зависящая от данных из другого модуля.

                    А если говорить что ООП запрещает лишь прямое получение изменение переменных из объекта, получится что класс реализующий интерфейс
                    getA();
                    getB();
                    setA();
                    setB();
                    полноценный объект


                    1. VolCh
                      20.05.2019 13:07

                      В ООП — это точно нормально, когда логика одного объекта анализирует состояние другого через его публичный API для изменения своего состояния или вызова методов других объектов.


                      В принципе, да — полноценный объект формально. Который, кстати, не гарантирует, что вызовы getA будут возвращать один и тот же результат, даже если не было вызовов SetA между ними.


            1. MarazmDed
              17.05.2019 10:50
              +1

              Если у нас есть класс менеджер который принимает в себя два объекта и что-то рассчитывает на основе этих объектов, объекты превращаются просто в структуры данных (поле+геттеры), а логика утекает а менеджер.

              Это совершенно нормально для действий, которые выполняются над всеми(некоторыми) объектами коллекции.

              Это нарушение инкапсуляции

              Нет. У объектов остается свое поведение. Например, объект Player умеет переходить из одного состояния в другое, умеет рисовать себя, и многое-многое другое.

              Кстати, как я понимаю, логика расчета атаки вообще будет выкинута из кода и окажется в скриптах.


              1. EvgeniiR
                17.05.2019 11:18

                Это совершенно нормально для действий, которые выполняются над всеми(некоторыми) объектами коллекции.

                Речи ведь не шло о коллекциях.

                Нет. У объектов остается свое поведение. Например, объект Player умеет переходить из одного состояния в другое, умеет рисовать себя, и многое-многое другое.

                Данные лежат в объекте, а логика которая зависит от этих данных находится в другом классе, классе-менеджере, это ли не нарушение инкапсуляции?


                1. adictive_max
                  17.05.2019 11:32

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


                  1. EvgeniiR
                    19.05.2019 11:40

                    Нарушением это будет, когда внешняя логика будет описывать внутренне поведение объекта.

                    Как вы себе вообще это представляете? Что за внешняя логика описывающая внутреннее поведение?


                    1. akryukov
                      19.05.2019 11:43

                      Как вы себе вообще это [внешняя логика описывает внутреннее поведение объекта] представляете?

                      Вот так:


                      class CombatManager {
                      public static void hit(Player p, Monster m) {
                             m.hp -= p.power;
                          if (m.hp <= 0) {
                              p.xp += m.level;
                          }
                      }
                      }


                1. akryukov
                  17.05.2019 11:37

                  Логика боя может и не зависеть от данных. CombatManager может получить просто два интерфейса: Attacker и Victim с набором методов типа Attacker.getAttackPower, Victim.takeDamage.
                  Как именно каждый конкретный attacker будет вычислять свою силу атаки и как именно каждый конкретный victim будет получать урон, определяют они сами. При этом CombatManager знает лишь общие правила, Attacker ничего не знает о Victim и наоборот.


                  Мы сможем подсунуть вместо экземпляра Monster, экземпляр Gazebo, который в методе takeBowDamage никак не будет менять свое состояние.


                  1. 0xd34df00d
                    17.05.2019 20:55

                    А если урон зависит от их обоих? Если у меня есть несколько разных видов монстров, и дамаг зависит от комбинации экспы атакующего с каждым из этих видов, а коэффициенты в этой комбинации зависят от конкретного (вида) монстра?


                    1. akryukov
                      17.05.2019 22:25

                      Вроде бы просто. Делаем у Attacker и Victim соответствующие интерфейсы для получения экспы и чего-угодно из них. Плюс в менеджере можно воспользоваться каким-нибудь внешним справочником для определения коэффициента по виду.


              1. Flammar
                17.05.2019 14:40
                -1

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


                1. mayorovp
                  17.05.2019 14:45

                  «Паттерн проектирования Visitor» не про это.


            1. Hardcoin
              17.05.2019 12:09

              Так это логика взаимодействия. То есть как конкретно монстр истекает кровью — это его внутреннее дело (поэтому я бы сделал sethitBy(weapon)), а вот общая логика (изменения атакующего и атакуемого) не могут быть ни в атакующем, ни в жертве. То есть у нас условно


              1. changeMonster
              2. changeAttaker
                И оба этих метода нужно где-то вызвать. Скорее всего в менеджере боя.


            1. Flammar
              17.05.2019 14:02

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


              1. VolCh
                19.05.2019 14:02
                +2

                Удар игроком монстра — поведение игрока. Реакция на удар — поведение всей среды. Объект игрока сообщает всей среде: я ударил монстра, а среда отвечает: в результате твоего удара и изменилась вот так, обработай это изменение.


          1. Rsa97
            17.05.2019 12:01
            +1

            Четыре actor'а: Герой, Меч, Злодей, Система.
            Герой: Меч, не будешь ли ты так любезен, нанести Злодею удар в левую пятку, в который я вложу всю свою силу (10) и ловкость (3). Система, зафиксируй удар.
            Меч: Злодей! Мною нанесён по тебе удар, который (с учётом моих бонусов) имеет силу 13 и ловкость 3. Система, запиши себе в журнал.
            Злодей: Система. Имею сообщить, что в результате подлой атаки Мечом Героя, от которой я слегка уклонился, мне была нанесена рана в мягкое место и я потерял 5 единиц здоровья.


            1. Cerberuser
              17.05.2019 14:36

              Система: Слизняк, участвующий в бою, Злодей отпрыгнул на тебя с силой 20.
              Слизняк: Система, я потерял 25 единиц здоровья от рукопашного удара Злодея и умер.
              Система: Слизняк умер от атаки Злодея. Злодей получает 1 опыта и лечебное зелье.
              Герой:… твою мать.


              1. Rsa97
                17.05.2019 16:02

                Главное, Злодей повышает уровень и полностью восстанавливает здоровье.


              1. Bronx
                18.05.2019 11:41

                Система: ну что, бойцы, к бою готовы? Сообщите мне ваши цели и типы атак.
                Герой: Цель — Бешеный Бобр, атака — удар бревном, вложено 5 единиц силы и 2 ед ловкости
                Оруженосец: я ничё, я тут так, посмотреть только и бревно подать.
                Бешеный Бобр: цель — Герой, атака — удар хвостом, вложено 3 ед силы, 10 ед ловкости
                Система: Ок, тааак… посмотрим… Оруженосец встал на линии атаки Герой--Бобр и создаёт помеху, снимаем 1ед ловкости с атаки Героя, а атаку Бобра частично направляем на Оруженосца. Теперь рандомизируем, взвешиваем по ловкости… Ок, все приготовились к раздаче люлей! Герой — прими атаку бобровым хвостом 1.5 ед, Оруженосец — прими атаку бобровым хвостом 0.6 ед, Бобр прими удар бревна 6 ед. Герой, поздравляю, удачный удар, даже при том, что Оруженосец тебе мешался.
                Герой: принял
                Оруженосец: ну ё… меня-то за что?
                Бобр: гхххххх....


          1. V1RuS
            17.05.2019 12:27

            Я сейчас задам максимально тупой вопрос, но… Зачем для этого класс? Может ограничиться функцией?
            (Предположим, что язык позволяет, например C++ или python)


            1. akryukov
              17.05.2019 12:35

              Можно обойтись функцией, если возможно. Просто в ОО языках вроде Java и C#, эти функции должны где-то находиться.


              Еще не понятно, кто именно должен вызывать эту функцию. Этот некто должен знать всех игроков и всех монстров, обрабатывать действия игрока и разбираться кто кому на самом деле нанес урон.


              1. alekseysinos
                17.05.2019 16:55

                А что меняется в случае использования класса вместо функции? Возможность сохранять в себе информацию обо всех игроках и монстрах?


                1. Ryppka
                  17.05.2019 17:07

                  Ну, например классы в C++ создают дополнительные возможности для оптимизации. Во всяком случае создавали, сейчас, возможно, компиляторы C не хуже в этом смысле.


                  1. 0xd34df00d
                    17.05.2019 20:57

                    Какие возможности?


                    1. Ryppka
                      17.05.2019 21:41

                      А то столь опытный и плодовитый хабровчанин не знает)))
                      Например, инлайнинг приватных методов и/или замена вызовов на переходы. Не уверен, как сейчас, но лет 10 назад компиляторы C справлялись с оптимизациями вызовов статических вызовов все-таки похуже.


                      1. 0xd34df00d
                        17.05.2019 22:09

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


                        1. Ryppka
                          17.05.2019 22:16

                          Суслика тоже не видно, а он есть)
                          Где-то в районе 2004 года сравнивал реализацию через класс с приватными методами и на C с непрозрачной структурой и б/м таким же кодом в статиках. Суть — принимаются входящие отсчеты и регистрируется начало и завершение определенных событий в преобразованном сигнале с адаптивными порогами, определением артифактов и т.д. Много внутреннего состояния, соответственно.
                          Результат на C++ получался компактнее и где-то на 10-15% быстрее. В ассемблере на C++ не было вызовов приватных методов: или прямое встраивание, или джампы. А на C были.
                          Как-то так.


                1. VolCh
                  19.05.2019 14:05

                  Возможность сохранять в себе всю свою информацию.


          1. Flammar
            17.05.2019 14:34
            -1

            Проблема ООП в том, что методы пытаются представить как действия субъекта. В то время как методы это дейстия над объектом.
            Хорошо было в Visual Basic: действия объекта — это события, которые он генерирует, действия над объектом — это методы.


            1. mayorovp
              17.05.2019 14:46

              Что значит "было"? И в чём тут заключается особенность VB?


              1. Flammar
                17.05.2019 15:35

                Что значит «было»?
                Я не знаю насколько широко он используется сейчас.
                И в чём тут заключается особенность VB?
                В нём, по крайней мере в COM, это на уровне языка. Под события «выделяются» не методы и не свойства источника событий, а метода со специальным названием у «приёмника»-обработчика событий. Т.е. не у кнопки есть свойство «onclick» и не метод кнопки «click», для чего требовалось бы на каждую кнопку создавать специальный класс-потомок, а у формы, на которой есть эта кнопка, должен быть метод с именем типа CancelButton_click, который вызывается при нажатии на кнопку с именем (как это делается на объектном уровне, не помню, вроде как это имя поля класса формы, на которой есть эта кнопка) «CancelButton».

                по крайней мере в 1998 я читал хелп к VB5, и там было написано что-то вроде «метод — это то, что вы делаете с объектом, событие — это то, объект говорит, что с ним сделали».


                1. mayorovp
                  17.05.2019 16:02

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


                  И да, делать наследника для источника событий (т.е. для кнопки) не требуется ни в одном языке.


            1. VolCh
              19.05.2019 14:08

              Методы — это не действия, это способ приёма объектом каких-то сообщений от внешней среды и возможность отреагировать на них изменением своего состояния и(или) посылкой сообщений другим объектам, выступая для них внешней средой.


          1. Kwisatz
            17.05.2019 20:50

            И таким образом мы плавно переходим от ООП к классовому програмированию. А зодно порешаем к чертям удобство IDE.

            Я все понимаю, правда, но у меня возникает ощущение, что все забывают о том зачем собственно вся эта возня.


            1. Anarchist
              19.05.2019 04:08

              Тут должен быть коммент о том, что хороший язык программирования должен быть одинаково неудобен как в vim'е, так и в IDE. :D


              1. Kwisatz
                19.05.2019 10:05

                Да имхо давно уже всем плевать. С тех пор как пошла дурная мода на параметры конструктора массивом. Все страшно увлечены обсуждением парадигм. Только потом приходится держать в голове или рыть недра документации в поисках 100500 вспомогательных классов, а так конечно SOLID, ООП итд итп. Жалко про KISS и YAGNI никто не вспоминает.


          1. khim
            18.05.2019 02:44
            -1

            Проблема ООП в том, что методы пытаются представить как действия субъекта. В то время как методы это дейстия над объектом.

            Это не «проблема ООП». Это «проблема Simula 67 и его потомков».


            1. akryukov
              18.05.2019 07:49

              Напишите, пожалуйста, вариант решения этой задачи на ООП из Smalltalk. Мне правда интересно, как все изменится.


              1. khim
                18.05.2019 16:53
                -2

                Рука-лицо. Причём тут Smalltalk? Он ещё более ограничен, извините.

                Посмотрите на нормальную реализацию ООП — CLOS или GOOPS. Последний у вас на компьютере имеется, скорее всего, можете поэкспериментировать. CLOS появился он после того, как в MIT познакомились со Smalltalk, поняли — насколько он ущербен, и придумали как сделать правильно.

                Классы там описываются вот так, методы вот так — и они, что характерно, классам не принадлежат.

                Что и логично: методы описывают, как правило, взаимодествие двух или нескольких классов — с какого перепугу они должны быть в одном их них? Потому что кто-то когда-то решил, что выбирать в одной табличке быстрее, чем в графе?

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

                Вы Чем хуже, тем лучше читали? Вот это — всё оттуда. Вопрос: «принадлежит ли метод „hit“ монстру или игроку» в нормально реализованном ООП просто не возникает. Но поскольку у нас такой не используется, то да, приходится выбирать. И иногда в результате очень вычурные конструкции получаются.

                Но тут ничего не поделать: у нас нет времени всё сделать правильно. К тому моменту, когда вы сделаете «самый правильный язык» критерии правильности поменяются — и вам придётся всё начать с начала (см. Кнута с его MIX и MMIX). И написать на «хорошем» языке реальную систему не получится никогда.

                Потому всё, что у нас есть — это плохие реализации ООП, плохие реализации ФП — и так далее. Спорить по поводу принципиальных проблем в ООП глядя только на плохие реализации — глупо и бессмысленно.


                1. akryukov
                  18.05.2019 17:08

                  Я и не собирался спорить.
                  Прошу вас просто написать реализацию задачи "игрок бьет монстра" на хорошей реализации ООП или ФП.


                  1. khim
                    18.05.2019 18:25
                    +1

                    Ну поскольку CLOS у вас, скорее всего, нет, а GUILE, скорее всего, есть… пусть будет GUILE:

                    (use-modules (oop goops))
                    (use-modules (oop goops describe))
                    
                    (define-class <player> ()
                      (attack-power #:init-keyword #:attack-power)
                      (xp #:init-keyword #:xp))
                    (define-class <monster> ()
                      (hp #:init-keyword #:hp)
                      (level #:init-keyword #:level))
                    (define-method (is-monster-alive (m <monster>))
                      (> (slot-ref m 'hp) 0))
                    (define-method (player-hits-monster (p <player>) (m <monster>))
                      (slot-set! m 'hp
                       (- (slot-ref m 'hp) (slot-ref p 'attack-power)))
                      (if (not (is-monster-alive m))
                       (slot-set! p 'xp (+ (slot-ref p 'xp) (slot-ref m 'level)))))
                    
                    (define p (make <player> #:attack-power 5 #:xp 0))
                    (define m (make <monster> #:hp 10 #:level 3))
                    
                    (player-hits-monster p m)
                    (player-hits-monster p m)
                    (player-hits-monster p m)
                    
                    (describe p)
                    (describe m)
                    


                    $ guile test.scm 
                    #<<player> 55e24a5f01a0> is an instance of class <player>
                    Slots are: 
                         attack-power = 5
                         xp = 6
                    #<<monster> 55e24a5f0020> is an instance of class <monster>
                    Slots are: 
                         hp = -5
                         level = 3
                    

                    Это выглядит, фактически, как плохой и нехороший «процедурный» подход… но на самом деле — это полноценный ООП. Вы можете сделать, например, «святого игрока» и «монстра-нежить» и сделать так, что при их столкновении урон будет в два раза больше:
                    (define-class <holy-player> (<player>))
                    (define-class <unholy-monster> (<monster>))
                    (define-method (player-hits-monster
                        (p <holy-player>) (m <unholy-monster>))
                      (next-method)
                      (next-method))
                    
                    (define p2 (make <holy-player> #:attack-power 5 #:xp 0))
                    (define m2 (make <unholy-monster> #:hp 9 #:level 3))
                    
                    (player-hits-monster p2 m2)
                    
                    (describe p2)
                    (describe m2)
                    

                    #<<holy-player> 55f2e383aca0> is an instance of class <holy-player>
                    Slots are: 
                         attack-power = 5
                         xp = 3
                    #<<unholy-monster> 55f2e3923620> is an instance of class <unholy-monster>
                    Slots are: 
                         hp = -1
                         level = 3
                    


                    Через MOP вы можете динамически менять тип объекта, так что вам не нужно решать — куда девать «обвес» игрока.

                    Вот все эти фабрики-фабрик-фабрик и прочее — это попытка обойти ограничения Simula 67, где нет ни возможности менять классы в рантайм, а методы обязательно нужно куда-то засовывать… но зачем эти ограничения приписывать «ограничениям ООР» в целом?


                    1. akryukov
                      18.05.2019 20:45

                      Напишите, пожалуйста, ваше определение ООП.


                      Я просто не пойму чем


                      (define-method (player-hits-monster (p <player>) (m <monster>))
                        (slot-set! m 'hp
                         (- (slot-ref m 'hp) (slot-ref p 'attack-power)))
                        (if (not (is-monster-alive m))
                         (slot-set! p 'xp (+ (slot-ref p 'xp) (slot-ref m 'level)))))

                      существенно отличается от метода в CombatManager.
                      Термин "метод" в вашем примере применяется для концепции, которая в ФП называется функцией. UPD: сначала написал "чистой", но не уверен. У вас в ней меняется состояние того, что вы передали через аргументы.
                      Вы взяли логику по взаимодействию и вынесли ее в отдельную единицу исполнения. player, holy-player, monster, unholy-monster у нас все так же ничего не знают друг о друге и модифицируются чем-то снаружи.


                      В случае с holy-player и unholy-monster в традиционном ОО языке мы сделаем перегруженный метод в том же CombatManager и логику по выбору метода в том месте, где мы будем вызывать атаку.


                      Если вас смущает необходимость писать метод в каком-то классе (в частности CombatManager), то можно относиться к нему просто как к namespace. Чтобы метод был не в глобальной области видимости, а подключался только тогда, когда мы его используем.


                      Любопытно, что в вашем случае нужно будет делать, чтобы реализовать комбинации "player атаковал unholy-monster" и "holy-player атаковал monster"?


                      1. khim
                        18.05.2019 21:16
                        -1

                        Я просто не пойму чем

                        существенно отличается от метода в CombatManager.
                        Отсутствием CombatManager'а, очевидно.

                        Термин «метод» в вашем примере применяется для концепции, которая в ФП называется чистой функцией.
                        Ну какая ж она, нафиг, чистая, если она меняет и состяние игрока и состояние монстра?

                        В случае с holy-player и unholy-monster в традиционном ОО языке мы сделаем перегруженный метод в том же CombatManager и логику по выбору метода в том месте, где мы будем вызывать атаку.
                        И тем самым, криво и неуклюже, сделаете имитацию нормальных multiple dispatch методов.

                        Любопытно, что в вашем случае нужно будет делать, чтобы реализовать комбинации «player атаковал unholy-monster» и «holy-player атаковал monster»?
                        Рука-лицо.

                        (define-method (player-hits-monster
                            (p <player>) (m <unholy-monster>))
                            ...)
                        (define-method (player-hits-monster
                            (p <holy-player>) (m <monster>))
                            ...)
                        
                        При их отсутствии, разумеется, всё сведётся просто к методу для обычного игрока и обычного монстра.

                        Если вас смущает необходимость писать метод в каком-то классе (в частности CombatManager), то можно относиться к нему просто как к namespace. Чтобы метод был не в глобальной области видимости, а подключался только тогда, когда мы его используем.
                        А если у нас одна команда заведует святыми, а другая — демонами? Где они будут соотвествующие методы размещать?

                        Появится HolyCombatManager и UnholyCombatManager. Дальше появятся комбинаторы. Фабрики. Фабрики фабрик. Ну и всё то, о чём статья.

                        Но это всё — не проблемы ООП! Это свойство одного конкретного языка… который, к сожалению, раздублировали везде и всюду.

                        Говорить, что это всё «проблемы ООП»? Ну блин. ООП — не панацея, у него есть свои проблемы… но то, что описывается в статье — не есть проблема ООП.


                        1. khim
                          18.05.2019 21:51

                          Если из контекста непонятно:

                          А если у нас одна команда заведует святыми, а другая — демонами? Где они будут соотвествующие методы размещать?
                          Команда разработчиков. Которые делают соотвествующие модули.


                        1. akryukov
                          18.05.2019 22:07

                          Ну какая ж она, нафиг, чистая, если она меняет и состяние игрока и состояние монстра?

                          Тут вы правы, она не чистая.


                          Я правильно понимаю, что в вашем языке можно сделать что-то вроде


                          allPlayers = (shuffle (make<holy-player>, make<player>, make<player>, make<holy-player>)) //создать коллекцию разных игроков и перемешать ее
                          
                          currentPlayer = (getByIndex allPlayers, 0) //взять первого игрока
                          (define m2 (make <unholy-monster> #:hp 9 #:level 3))
                          
                          (playerHitsMonster currentPlayer m2)

                          и рантайм сам поймет какую именно комбинацию playerHitsMonster выполнять?


                          1. khim
                            18.05.2019 22:40
                            +1

                            Да, конечно. Как я сказал: это полноценный ООП. Инкапсуляция-наследование-полиморфизм. Какой бы это был ООП без полиморфизма?

                            И это не мой язык, как я сказал. Это guile. Если у вас Linux, то наберите в комадной строке guile и нажмите Enter — с большой вероятностью он у вас уже есть.

                            Ну или на tio.run загляните, если его у вас всё-таки нет.


                            1. akryukov
                              19.05.2019 10:52

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


                              (define-method (player-hits-monster (p <player>) (m <monster>))
                                (slot-set! m 'hp
                                 (- (slot-ref m 'hp) (slot-ref p 'attack-power)))
                                (if (not (is-monster-alive m))
                                 (slot-set! p 'xp (+ (slot-ref p 'xp) (slot-ref m 'level)))))

                              и


                              (define-method (player-hits-monster
                                  (p <holy-player>) (m <unholy-monster>))
                                (next-method)
                                (next-method))

                              Эти методы помещаются в список и из них выбирается какой-то первый?
                              Ищется такой метод, аргументы которого ближе всего к реальным типам p и m?
                              Какой метод выберется, если у нас будет corrupted-holy-player — наследник holy-player, который должен наносить урон как обычный player?


                              1. Druu
                                19.05.2019 12:28

                                Какой логикой руководствуется рантайм, когда выбирает из

                                Вызывается наиболее специфичный метод, подходящий под типы аргументов. Если нормально специфичность сравнить нельзя, то добавляются костыли (ну типа специфичность первого аргумента > специфичности второго и т.п.).


                              1. khim
                                19.05.2019 22:46

                                Какой метод выберется, если у нас будет corrupted-holy-player — наследник holy-player, который должен наносить урон как обычный player?
                                Если у вас corrupted-holy-player не ведёт себя так же, как holy-player, то он, очевидно, не должен являться его наследником. Если у вас часть характеристик идёт и туда и туда (скажем у обоих есть нимб), то у вас будет иерархия, где player-with-halo — и уже от него будут наследоваться holy-player и corrupted-holy-player.

                                Как я уже сказал: тип объекта со временем может меняться и, более того, типы могут генерироваться автоматически при необходимости (то есть у вас нет проблемы «комбинаторного взрыва», которую решают 100500 уровней индирекции в Java: если у вас 100 типов и может быть 2100 подтипов, которые зависят от того, какие типы включены, какие нет — то вам вовсе не обязательно реализовывать все 2100 подтипов статически...)


                        1. Bronx
                          19.05.2019 05:33

                          А если у нас одна команда заведует святыми, а другая — демонами? Где они будут соотвествующие методы размещать?

                          Эти команды будут отвечать за святых и за монстров (внутренние данные, отрисовка, анимация и т.п.), но не за динамику их боевого взаимодействия друг с другом. Точно так же как за (player-hit-monster ... ...) будет отвечать тот, кто реализует бой, а не персонажей.


                          Появится HolyCombatManager и UnholyCombatManager.

                          Зачем? В том же самом CombatManager появится код, обслуживающий столкновения дополнительных персонажей. Это могут быть перегруженные методы вроде .PlayerHitMonster(HolyPlayer, UnholyMonster) (аналогично приведённому вами), или один-единственный метод, который выясняет трейты каждого персонажа и матчит их возможные взаимные комбинации:


                          var factor = 
                               (p is Holy) &&  (m is Unholy) ? 2.0 :
                               (p is Holy) && !(m is Unholy) ? 1.5 :
                              !(p is Holy) &&  (m is Unholy) ? 0.5 : 1.0;
                          var power = p.AttackPower * factor;


                          1. khim
                            19.05.2019 06:29

                            Эти команды будут отвечать за святых и за монстров (внутренние данные, отрисовка, анимация и т.п.), но не за динамику их боевого взаимодействия друг с другом.
                            А кто будет отвечать за динамику?

                            В том же самом CombatManager появится код, обслуживающий столкновения дополнительных персонажей.
                            А как он там появится, извините? Если, скажем, игру выпускают одни люди, а аддоны к ней к наборами монстров — другие?

                            Это могут быть перегруженные методы вроде .PlayerHitMonster(HolyPlayer, UnholyMonster) (аналогично приведённому вами), или один-единственный метод, который выясняет трейты каждого персонажа и матчит их возможные взаимные комбинации:
                            Один-единственный метод, очевидно, работать не будет, а чтобы перегружать методы — как раз и потребуются HolyCombatManager, UnholyCombatManager, CombiningCombatManager и всё прочее, что отсюда будет следовать…


                            1. Bronx
                              19.05.2019 09:35

                              А кто будет отвечать за динамику?

                              Тот, кого поставили отвечать за неё и за баланс игры в общем — т.е. тот, кто в вашем случае добавляет все эти перегрузки player-hits-monster.


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


                              Если, скажем, игру выпускают одни люди, а аддоны к ней к наборами монстров — другие?

                              Т.е. у вас player-hits-monster перегружается авторами аддонов с монстрами? А если добавляется новый тип игрока — то авторами аддонов с игроками? А если два аддона выпущены независимо, то возникает дыра в том месте, где новые игроки взаимодействуют с новыми монстрами, так как никто об этом не подумал? А потом обе команды спохватываются и решают заполнить дыру, и выпускают новые версии, каждая из которых перегружает одну и ту же комбинацию игрок-монстр?


                              1. khim
                                19.05.2019 22:56

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

                                Вы в настолки когда-нибудь играли? Там регулярно выходят новые допы. В диких количествах. В некоторых случаях — их вообще разные фирмы выпускают к одной игре. И да, иногда сочетания разных паков приводят к странным эффектам — тогда игроки (а не разработчики!) такое сочетание договариваются не использовать.

                                А потом обе команды спохватываются и решают заполнить дыру, и выпускают новые версии, каждая из которых перегружает одну и ту же комбинацию игрок-монстр?
                                Иногда бывает, но очень редко. Обычно меняется описание базовых эффектов, из которых строятся все персонажи, меняется вместо этого.


                            1. akryukov
                              19.05.2019 10:01

                              А как он там появится, извините? Если, скажем, игру выпускают одни люди, а аддоны к ней к наборами монстров — другие?

                              Для этих целей делают всякие скриптовые движки, где можно подписаться на событие и проверить, хотим ли мы его обрабатывать.
                              Например https://wiki.wesnoth.org/Eventwml


                        1. akryukov
                          19.05.2019 11:24

                          И тем самым, криво и неуклюже, сделаете имитацию нормальных multiple dispatch методов.

                          Если решать задачу с holy-player "в лоб", то действительно получится некрасивая имитация.


                          Заголовок спойлера
                          public static void hit(Player p, Monster m) {
                              m.hp -= p.power;
                              if (m.hp <= 0) {
                                  p.xp += m.level;
                              }
                          }
                          
                          public static void hit(HolyPlayer p, EvilMonster m){
                              m.hp -= p.power * 2;
                              if (m.hp <=0 ){
                                  p.xp +=m.level;
                              }
                          }
                          
                          public static void selectHit(Player p, Monster m){
                              if (p.getClass().equals(HolyPlayer.class) &&
                                      m.getClass().equals(EvilMonster.class)){
                                  HolyPlayer hp = (HolyPlayer) p;
                                  EvilMonster em = (EvilMonster) m;
                                  CombatManager.hit(hp, em);
                              } else {
                                  CombatManager.hit(p, m);
                              }
                          }


                          1. khim
                            19.05.2019 22:49

                            В этом варианте бизнес-логика по выбору между holy-hit и просто hit не будет утекать в диспетчер языка.
                            А какую проблему вы этим решите?

                            Нет, я верю: можно гланды вырезать и автогеном через задницу. Но… зачем?


                            1. akryukov
                              19.05.2019 23:16

                              А какую проблему вы этим решите?

                              "Этим" это чем? Я попытался реализовать задачу на java и пока что решение выше — лучшее, что я придумал. Я ещё на scala попробовал, но показать нечего.
                              Я бы вас понял, если бы на java было решение с динамической диспетчеризацией и без нее и вы бы спрашивали "зачем без нее". Но решение пока всего одно.


                              1. khim
                                19.05.2019 23:37

                                Я бы вас понял, если бы на java было решение с динамической диспетчеризацией и без нее и вы бы спрашивали «зачем без нее».
                                А причём тут Java? Я в самом начале этой ветки сказал, что обсуждаемая проблема — это Simula головного мозга.

                                Да, в Java нет динамической диспетчеризации и из-за этого отношение многие-ко-многим моделируются через промежуточные, искусственные, структуры.

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

                                А вот говорить, что раз вам при использовании Java нужен костыль, то теперь давайте весь ООП отменим — это перебор.

                                На Java и её подходах ООП не заканчивается…


                                1. akryukov
                                  20.05.2019 08:08

                                  Проблема ООП в том, что методы пытаются представить как действия субъекта. В то время как методы это дейстия над объектом.

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



                                  Но ведь вы показали такой пример, где методы это как раз действия над пассивными объектами, а не действия активных субъектов. Я тот же самый тезис защищаю.
                                  Разница моего и вашего подхода только в диспетчеризации этих методов, которая в GUILE из коробки и динамическая, а в моем решении на java самописная.


                1. EvgeniiR
                  18.05.2019 19:10

                  Рука-лицо. Причём тут Smalltalk? Он ещё более ограничен, извините.

                  Потому что Smalltalk ООП язык, а C++, какой, например, нет?

                  Потому всё, что у нас есть — это плохие реализации ООП

                  Ну идеи Кея частчино представлены в виде actor model в erlang или akka, или, например, микросервисы выступающие в роли объектов.


                  1. khim
                    18.05.2019 20:25

                    Ну идеи Кея частчино представлены в виде actor model в erlang или akka, или, например, микросервисы выступающие в роли объектов.
                    Речь не об этом. Речь о том, что вы либо постоянно меняете язык несовместимым образом, когда в нём обнаруживаются проблемы (Algol ? Pascal ? Modula-2 ? Operon) — и тогда вы рано или поздно теряете всех программистов, потому что никому не интересно переписывать то, что уже один раз написано 100500 раз, либо у вас язык, оказывается плохим… но, возможно, востребованным…

                    Просто нужно понимать, что «ООП» и «то, что умеет C++ (Java, ObjectiveC, Swift, C#, да даже и GUILE — неважно)» — это разные вещи.

                    Любой язык реализует некое ограниченное решение. От этого никуда не деться.


                1. 0xd34df00d
                  18.05.2019 23:35

                  методы вот так — и они, что характерно, классам не принадлежат

                  Это на самом деле ключевое. Спасибо.


                  Эта наша ML-ная функциональщина не так уж далеко по каким-то фундаментальным идеям от этой вашей CL Object System.


                  1. khim
                    19.05.2019 01:43

                    Есть ещё одно место, которое на них похоже, на самом деле. Удивительным образом это даёт очень приличный полиморфизм и неплохой ООП в C++ — только не там, где его обычно ищут.

                    Статическией шаблонный полиморфизм в C++ (CRTP и вот это вот всё) — гораздо ближе к CLOS и ML, чем виртуальные функции и виртуальные классы.

                    И, что характерно, в последнее время всё больше библиотек в C++ используют именно этот объектный подход, а не ФабрикуФабрикФабрик, критикуемую в статье… но что это тоже ООП — авторам статей, подобных обсуждаемой, в голову не приходит…

                    P.S. Десятое правило Гринспена в действии, блин…


        1. Belikov
          17.05.2019 09:48

          С монстром как раз можно очень красиво показать преимущества ООП. Например в классе Player:

          void Hit(Monster monster)
          {
             if (monster.IsHitBy(CurrentWeapon, AttackPower) <= 0) 
          // например можно возвращать количество оставшихся hp
          // у монстров может быть immunity к немагическому оружию например
          // или разный уровень дамага от slaching/piercing/crushing - все это легко тут учитывается
             {
                  XP += monster.XP;
             }
          }

          А используя наследование можно, например заменить monster на ITarget и тогда подставлять туда сундуки, других игроков, NPC и т.д.
          Можно чтобы Hit возвращал bool если нужно и т.д.
          AttackPower нужно передавать, т.к. одним и тем же оружием разные персонажи разный damage нанесут. Это может быть например, специалист по убийству зомби с доп. дамаком против undead и т.д. И все это очень легко и красиво укладывается в модель.


          1. EvgeniiR
            17.05.2019 10:18

            Только замена монстра на ITarget это полиморфизм, там не обязательно наследование в понимании "наследование классов", просто все цели реализуют единый интерфейс


        1. techus
          17.05.2019 10:14

          Mnemonik описал нормальный вариант. Я могу еще предложить:
          Битва (класс Fight), например, может быть также отдельной сущностью — содержать свою информацию о событии. У битвы есть 2 стороны (это могут быть даже и коллекции) — интерфейсы, которые могут наносить урон, получать урон, использовать оружие или не использовать — это уже конкретная реализация интерфейса в виде классов Player или Monster. По SOLID ни один из этих классов не должен знать о существовании другого, поэтому кто кого и как пинает может решаться в классе битвы Fight. Но битва тоже напрямую ничего не знает о конкретных реализациях, она только знает, что интерфейс стороны А может нанести N урона, а интерфейс стороны B получить N урона (а может и не N, потому что вмешались какие-то третьи магические силы).
          А можно придумать еще кучу вариантов. Считать недостатком инструмента, что он предоставляет множество вариантов сделать кривую реализация, я думаю, неправильно. Также полагаю, что нельзя считать недостатком инструмента непонимание или нежелание его понимать. Как и на машине можно разогнаться и въехать в столб — машина ведь не виновата.


        1. arandomic
          17.05.2019 13:07

          На самом деле я бы смотрел здесь в сторону Entity-Component-System паттерна.
          Вот здесь было обсуждение:
          softwareengineering.stackexchange.com/questions/369066/oop-ecs-vs-pure-ecs


        1. hd_keeper
          17.05.2019 16:17

          Чтобы было правильно, надо использовать мультиметоды.: )


        1. RedCatX
          18.05.2019 00:02

          Я бы создал абстрактный базовый класс Character, имеющий поля attack, defense, hp, и метод hit c аргументом типа Character. От этого класса я бы унаследовал и Player, и Monster. Класс Weapon же просто присоединяется к Player, и уже Player изменяет свои показатели атаки, защиты, и т.д. в соответствии с выбранным Weapon. Таким образом, получается довольно простая архитектура, где игрок может атаковать монстра, монстр — игрока, или даже другого монстра. Где тут можно запутаться?


          1. Rukis
            18.05.2019 11:53

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


        1. farcaller
          18.05.2019 13:03

          Событийную шину с emit(hit) и onHit уже предлагали?


          1. avost
            19.05.2019 22:23

            Неа! Вы же видите, предлагают ровно то же самое, что описано в статье (осуждаем, а потом это же и предлагаем, да) — вариации с hit/isHitBy, либо использовать класс "менеджер битвы" (чорд! Это ещё бОльший трэш, но его всерьёз предлагают несколько человек, как более хорошее решение! Я был лучшего мнения о человечестве...)
            Ваш вариант, кажется мне куда более вменяемым. Но, вообще-то, это не ОО решение ;). Оно, безусловно, может быть реализовано в ООП стиле, но совершенно не обязательно :).


            1. TheShock
              19.05.2019 22:31

              Ваш вариант, кажется мне куда более вменяемым
              Увы, на практике ВСЁ делать на событиях приводит к тому, что оно довольно сложно поддерживается.


            1. alsii
              20.05.2019 19:28
              +1

              Шина событий — это просто очень-очень простой "менеджер битвы". "Менеджер битвы" — это такая навернутая шина событий с middleware и маршрутизацией...


    1. Arlekcangp
      17.05.2019 10:42

      Да уж… Кони ездят верхом на людях у автора статьи. Начать стоит с того, что реальные сущности практически никогда не мапятся на объекты и классы один к одному. Пример с игроком и монстром не корректен, т к по сути такая ситуация напрямую показывает, что нужен ещё класс и возможно не один. (Например Game или FightRule)
      Второе — он упомянул акторы… Так они по сути ещё ближе к ооп в том его виде, в каком задумывал Алан Кей. Объекты обмениваются сообщениями. И всегда поддерживают инварианты внутри себя. Инкапсуляция! Причем в акторах она прям тру — ничего нельзя сделать без сообщений. И состояния у них есть… Короче, что то тут не то...


      1. kalininmr
        17.05.2019 11:57

        конкретно в java в этом есть интересный недостаток.
        нет нормальных data object.
        во всяческих kotlin — стараются исправить.
        в идеале, возможно, часто нужен вобще аналог struct(яркий пример — Point, Color, Rect и т.д.).


        1. symbix
          17.05.2019 16:05
          +1

          Чем класс с конструктором и пачкой public final-ов не data object?


          1. kalininmr
            17.05.2019 20:16

            хочется доступ бещз гетеров/сетеров — нафиг они там.
            хочется serializable и прочее искаропки
            сравнение опять же.

            вот в котлине грамотно сделано.
            ну и аналог struct тоже хотелось бы


            1. symbix
              17.05.2019 21:56
              +1

              Я не предлагаю геттеры и сеттеры делать. Java как язык не виновата в том, что Javabeans стал де-факто стандартом.


              Serializable — это уже DTO. Для обычного data object достаточно внешней сериализации.
              Если нужен кастомный Comparable — это уже Entity. Для обычного data object равенство объектов — это равенство всех полей по определению.


              В общем, не вижу, чем не хватает обычного класса, в котором нет ничего, кроме конструктора и public final-ов, и парочки внешних утилит, работающих через рефлексию. Это в общем-то и есть immutable struct по сути своей. А mutable — не надо, спасибо.


              1. kalininmr
                18.05.2019 14:34

                struct — в плане передачи по значению.


                1. symbix
                  18.05.2019 21:27

                  Если immutable, то какая нафиг разница?


                  1. kalininmr
                    20.05.2019 10:05

                    скорость не кисло так.


      1. megasuperlexa
        17.05.2019 16:20
        +2

        как прям в коментариях к лекции одного известного апологета ООП. «Ушел переписывать сортировку пузырьком на ООП. Выделил объект пузырек. обернул в объект сортировка. Дальше запутался, помогите!?»


      1. khim
        18.05.2019 02:49
        +1

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


        1. Arlekcangp
          19.05.2019 07:47

          Все аргументы подобного рода игнорируют предысторию, почему вдруг стали появляться эти "кучи классов"?.. А предыстория такова что стало "модным" поддерживать и разширять ПО уже после того как первоначальная задача решена при создании. Тот же ООП никак вам не диктует сколько создавать классов. Можете создать один только "Game" если для вашей задачи этого достаточно. А игрок и чудовище будут в нем полями, структурами или массивами (привет Фортран-стайл). Автор статьи валит проблемы на инструмент, в то время как реальная беда — отсутствие людей способных прочитать и осмыслить инструкцию к инструменту


          1. khim
            19.05.2019 20:31

            С другой стороны вы же сами предложили «ещё один класс и, возможно, не один». Я же смотрю вот сюда и не понимаю что там этот «класс и, возможно, не один» будет делать.

            Вот сделать несколько классов для изображения обсуждаемой там логики — понятно. Но ещё один (или, тем более, много) классов для рассчётов? Зачем?


            1. TheShock
              19.05.2019 22:51

              Но ещё один (или, тем более, много) классов для рассчётов? Зачем?

              Какой-то AttackDamageCalculator, который сведет все возможные модификаторы урона и выдаст решение. Ну или функция calculateAttackDamage(), в которую зависимости пробрасываются каррированием.

              Ну или какая-то ParabolaTrajectory для того, чтобы узнать траекторию полета снаряда.

              У меня вот ещё в одном проекте были довольно сложные условия и, как результат — был билдер фильтров. Выглядело так:

              // Нанесите 2 повреждения выбранной технике или взводу
              // Восстановите 2 прочности вашему штабу.
              
              public class ParisGunAbility : SpecificAbility
              {
                protected override Command[] Exec(Card target)
                {
                  return new Command[] {
                    new DealDamage(2, Card, target),
                    new HealDamage(2, Card, Owner.Cards.GetHq())
                  };
                }
              
                protected override Search Targets(Search search)
                {
                  return search.Enemy().Vehicle().Platoon();
                }
              }


              Ещё нам необходим какой-то OptionsCollector, который сможет проанализировать модель и выдать для вьюшки в адекватном виде возможные действия, которые мы ждем от пользователя.


    1. symbix
      17.05.2019 16:02

      Да нет, в DDD перечисленных проблем бы не было.
      Скажем, вопроса про hits бы точно не возникло — поскольку был бы aggregate root.


    1. AN3333
      17.05.2019 16:48
      +1

      DDD это что?

      А то что автор путает ООП с чем-то несусветным это точно.
      Достаточно такого:

      ООП требует передавать повсюду длинные списки аргументов или непосредственно хранить ссылки на связанные объекты для быстрого доступа к ним.

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


      1. Ryppka
        17.05.2019 17:08

        Длинные списки аргументов прекрасно объединяются в структуры.


        1. evocatus
          19.05.2019 01:50
          -1

          У которых в названии встречается слово Context


  1. karl93rus
    17.05.2019 08:37
    +2

    Кто-нибудь тоже пропел слова с картинки? :)


    1. WinPooh73
      17.05.2019 09:44
      +4

      Разумеется. И даже продолжил: «We don't need no flow control...»


      1. didkovskyi
        18.05.2019 23:36

        No dark sarcasm in the Habr…


        1. Bronx
          19.05.2019 09:56
          +1

          Dijkstra leave them kids alone!


  1. mozgosteb
    17.05.2019 08:46

    Строгий порядок:

    архитектура данных -> код

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


    1. flancer
      17.05.2019 09:26

      Код оперирует данными. Все базовые конструкции кода (присвоение, ветвление, цикл) без данных не осуществимы. Можно ради хохмы создать пустую функцию и сказать, что это код без данных, но и она будет возвращать null по-умолчанию. Так что, даже не сомневайтесь ;)


      P.S.
      хотя… можно создать кучу классов, наследующих друг от друга и все без единого метода (если не брать во внимание наличие default-методов типа toString()) — это лучшее, что удалось придумать на тему "код без данных".


      1. mozgosteb
        17.05.2019 09:33

        Наверное, я чего-то недопонял, но где в присвоении, ветвлении, цикле что-то такое, что можно назвать громким словосочетанием «архитектура данных»? Я всего лишь имел ввиду, что писать код ТОЛЬКО после того как сформирована архитектура данных ммм… сомнительно. Более того, можно писать код, частично независимый от архитектуры данных. В этом же и сила ООП. Ну или я и правда ничего не понял.


        1. flancer
          17.05.2019 09:52

          А сколько, по-вашему, различных элементов данных нужно иметь, чтобы можно было говорить об "архитектуре данных"?


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


          1. ghrb
            17.05.2019 11:06

            Ну можно иметь объект «машиностроительный завод», который знает что ему как параметры передают станки и он умеет станки запускать, обеспечивать энергией, подавать станкам коробки с материалом и удалять коробки с отходами, что там в этих коробках он понятия не имеет и не должен.
            Станки бывают деревообрабатывающие, ЧПУ по металлу, ткацкие и они сами знают что будут работать в заводе который их будет обслуживать и они знают как им что обрабатывать.
            Это получается нам нельзя писать код завода пока мы не узнаем все тонкости металлобработки и пряжи шерсти?


            1. SadOcean
              17.05.2019 11:35

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

              В этом смысле Я пожалуй согласен со статьей — Data-First
              Но в остальном так себе — соломенное чучело ООП


              1. VolCh
                19.05.2019 15:05

                А если я пишу код с абстрактними данными, ну, например, у всех методов и конструкторов абстрактный param: Object единственный параметр, который уточняется, специфицируется по мере необходимости?


        1. khim
          18.05.2019 03:01

          Ну или я и правда ничего не понял.
          Вы правда ничего не поняли. Вся эта история восходит к мифическому человеко-месяцу и в оригинале звучит так: «Стратегические находки приходят в результате изменения способа представления данных или таблиц. Именно здесь лежит сердце программы. Покажите мне ваши блок-схемы, но спрячьте таблицы, и я останусь в неведении. Покажите мне ваши таблицы, и мне уже не надо смотреть блок-схемы, они и так очевидны.»

          Это, как бы, 1975й год, до увлечения ООП ещё далеко… речь идёт «всего лишь» о проекте, перевернувшим всю IT-индустрию…

          А с ООП, как вы правильно замечаете «можно писать код, частично независимый от архитектуры данных». Ну это всё равно как строить водопровод и канализацию «частично независимо от физических свойств воды». Можно разработать такую технологию? Не вопрос. Только тогда вам для того, чтобы налить чашку воды потребуется три энергоблока АЭС, а чтобы суп сварить — десять новых электростанций вы начнёте задумываться над тем, что что-то в этом подходе «не так». А в программировании — за счёт дикого запаса прочности — эти подходы прижились…


          1. VolCh
            19.05.2019 15:13

            ООП — это как раз результат изменения способа представления данных. :) Графы данных вместе с подпрограммами ихх обработки вместо таблиц и отдельных подпрограми


      1. SadOcean
        17.05.2019 11:31

        Проблема в том, что и это будет данными — они будут отражать какую-то структуру и связи объектов.


      1. VolCh
        19.05.2019 15:02

        Вот именно, что код оперирует данными. Но не для оперирования данными он обычно пишется, а для решения какой-то задачи. Решение задач — смысл кода, а оперирование данными — способ реализации этого смысла, свойство кода, он вынужден оперировать данными, чтобы решать задачу. Какие данные и в каком виде нам нужны для решения задачи часто нельзя определить, не начав писать код, блок-схемы или иные виды представления алгоритмов, пускай и в голове.


        1. flancer
          20.05.2019 06:42

          он вынужден оперировать данными, чтобы решать задачу.

          Ну так и что первично, в таком случае? Данные, например, не требуют наличия какого-либо кода для своего существования. Попробуйте придумать функцию с пустыми входными/выходными аргументами, решающую какую-либо задачу. Или объект с пустым конструктором, без set'еров и с аналогичным методом (без входных/выходных аргументов)?


          Я могу определить иерархию абстрактных классов не имеющих ни одного метода с входными-выходными аргументами или набор интерфейсов с такими же свойствами и сказать, что вот он — код без данных. Можно даже сделать обычный класс, наследующий от и имплементирующий. И потом чесать репу, как доставить данные вовнутрь, чтобы задача, для которой написан этот код, решалась. Так стоит ли ставить телегу впереди лошади и писать код, который вынужден оперировать данными, чтобы решать задачу, не определяясь с тем, какими же все-таки данными он вынужден оперировать?


          1. VolCh
            20.05.2019 13:41

            Очень часто задачи ставятся в виде подобном "при наступлении такого-то события (пользователь нажал клавишу в игре, потенциальный клиент захотел заключить сделку в B2* системах и т.п.) должна біть такая-то реакция (у монстра уменьшилась жизнь, система выдала договор на подпись) мы сначал как-то составляем алгоритм, а лишь потом определяем а какие входные и выходные данные нам нужны. Мы можем набросать код типа


            class ContractSigningProccess {
              private Contract contract
            
              static start(): self;
              signByCustomer(): void;
              signBySupplier(): void;
              getStatus(): string ;
              getContract(): Contract;
            }

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


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


            1. flancer
              21.05.2019 07:18

              private Contract contract

              Сначала описываем процесс обработки абстрактных данных

              А абстрактные данные уже не данные?


    1. heleo
      17.05.2019 15:34

      К примеру «когда есть код, но данных нет» можно взять шаблонные контейнеры и алгоритмы.


  1. Wilk
    17.05.2019 08:48
    +1

    Хммм… Т.е. злоупотреблять чем-либо и/или использовать это неправильно — плохо. «Ни разу этого не было, и вот опять» (с)


  1. Pydeg
    17.05.2019 08:48

    Есть ещё хорошее видео на эту тему, которое более детально раскрывает эти идеи


  1. yudinetz
    17.05.2019 08:55
    +1

    Я не умею в ООП, поэтому ООП плохой
    — это все, что нужно знать о данной статье


    1. mapron
      17.05.2019 10:13

      Меж тем у ООП действительно есть проблемы, и я готов признать что в списке
      wiki.c2.com/?ArgumentsAgainstOop
      часть вполне себе объективные.
      Просто на мой взгляд, критика автора сама по себе слабая.
      Меня больше возмутила даже не слабая аргументация сама по себе, а выводы которые автор пытается сделать:
      «ООП плохой в некоторых сценариях <слабые аргументы>, поэтому не нужно его использовать вообще».
      С таким подходом ВООБЩЕ ничего нельзя использовать, сидеть и ждать когда идеальная методология разработки всего упадёт аки манна небесная.


      1. Alexeyslav
        17.05.2019 10:49

        Не в ООП есть проблемы, а в конкретных реализациях и людях их использующих. Всё начинается с отсутствующего архитектора в проекте…


        1. mapron
          17.05.2019 10:53

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


        1. SadOcean
          17.05.2019 11:39

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


          1. VolCh
            19.05.2019 15:15

            Может это проблема процессоров и кэшей? ;)


    1. exception13x
      17.05.2019 10:16

      А кто-то вообще умеет?


    1. ddinochrome
      18.05.2019 00:11
      -1

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


      1. symbix
        18.05.2019 00:31
        +2

        ФП точно так же уводит в сторону. Кратчайший путь — это императивная лапша.


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


        1. ddinochrome
          18.05.2019 01:54
          -1

          Под функциональным кодом я понимаю код, который описывает функции и даёт прямой видимый эффект. Про концепцию ФП я вообще тут не говорю.

          Код как раз пишется для машин. А для человека пишется база знаний. «Любой дурак» — это тот, кто пытается подменить человекочитаемую базу знаний кодом с комментариями)

          Суровая реальность ИТ в том, что большинство бизнесов или долго не живут, или быстро не развиваются. Поэтому почти никогда нет нужды 10 лет что-то там активно поддерживать и развивать.

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

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

          императивная лапша

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


          1. khim
            18.05.2019 03:07
            +1

            Но в реальности код неотрывно связан со своим создателем, ибо основан на миллионах неявных логических моделей и связей, которые никак не отражены в коде, но присутствуют в голове автора.
            К сожалению или к счастью, но в суровом ынтырпрайзе — это не так. Там — совершенно нормально «для оптимизации затрат» перекинуть код из офиса в Бангалоре в офис в Токио или наоборот. И для того, чтобы это было возможно — и нужен весь этот ООП и Фабрика Фабрик Фабрик.

            Поэтому наиболее эффективно, когда новый программист пишет с нуля новый код под себя и в соответствии с зафиксированными в старом коде требованиями заказчика.
            Это — совсем другой подход и применяется в совсем других случаях.

            Выбросить код не так страшно как кажется.
            Только в тех случаях, когда информация о том, что код должен-таки делать содержится где-то ещё.


            1. ddinochrome
              18.05.2019 10:42
              +1

              Скажите пожалуйста, как мы внезапно оказались в контексте «сурового ынтерпрайза»? Я пишу про свой практический опыт — руководство командами от 3 до 30 разработчиков. Поэтому я пишу про свой подход, который реально работал. А то, о чём вы пишете, делали мои предшественники, которые доводили проект до предсмертного состояния и увольнялись. Потому что все эти фабрики фабрик хорошо только в книжках работают. Когда перед тобой есть цель, срок и бюджет, и ты сам лично за них отвечаешь перед владельцем бизнеса, то начинаешь мыслить прагматично. И с такой точки зрения ООП — это нож в спину. Это сложно объяснить словами в рамках комментарий, но легко ощутить на собственном опыте, — попробуйте ;-)

              Выбросить код не так страшно как кажется.

              Только в тех случаях, когда информация о том, что код должен-таки делать содержится где-то ещё.


              Ну тут я вижу, что Вы в принципе не владеете методами оценки состояния инфо-проекта на уровне бизнеса.
              Допустим, я пришёл в контору, где есть кодовая база. Все программисты разбежались. Что я буду делать? Полезу в код? Полезу в багтрекер? Полезу в их внутреннюю вики? Нет.
              Я посмотрю внедрён ли этот продукт в реальный бизнес-процесс. Если нет, то ценность в нём нулевая — выкидываю смело и начинаю разработку заново с нуля с владельцем бизнеса.
              Если проект внедрён, то я анализирую этот бизнес процесс и составляю его модель (база знаний по проекту). Эта модель как раз и будет содержать актуальные общие требования к системе. Далее я проанализирую структуры данных, с которыми работают реальные пользователи. Что они получают на входе, что должны получить на выходе, как это структурируют, всё ли в этом процессе оптимально устроено. В 100% случаев оказывается, что имеющийся код не соответствует реальному бизнес-процессу, потому что делался под другие требования, а потом жестоко хачился. Соответственно и заглядывать туда не стоит — я делаю за несколько недель (обычно этого срока достаточно) собственную систему для осуществления тех же бизнес-процессов и работы с теми же структурами данных, а потом незаметно подменяю одно другим. И вуаля — теперь у меня код мой, подконтрольный и прагматичный. А не какой-то легаси-монстр, с которым надо по неделе за каждую новую кнопочку сражаться.
              Что дальше? А дальше идёт оптимизация и модификация бизнес-процессов для повышения прибыли компании. Больше всего этот процесс затрагивает структуры данных, а код для алгоритмов тянется уже в хвосте.

              Теперь хотелось бы услышать про Ваш опыт организации миграции проектов из Бангалора (а точнее из Бенгалуру) в Токио. И как на практике ООП в этом помог.


              1. dakuan
                18.05.2019 13:29

                И вуаля — теперь у меня код мой, подконтрольный и прагматичный. А не какой-то легаси-монстр, с которым надо по неделе за каждую новую кнопочку сражаться.


                А через полгода вы увольняетесь, на ваше место приходит другой разработчик и все начинается по новой? :)


                1. ddinochrome
                  18.05.2019 14:17
                  -1

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


                  1. dakuan
                    18.05.2019 16:27

                    Т.е. у вас проект после релиза вообще не развивается?


                    1. ddinochrome
                      18.05.2019 16:37
                      -1

                      Я описал цикл продажи компании. А вы спрашиваете о цикле релизов. Вы знаете в чём разница?


                      1. dakuan
                        18.05.2019 17:03

                        Я не спрашивал о цикле релизов, я спрашивал о развитии проекта. Потому что из ваших постов картина вырисовывается довольно противоречивая.


                        1. ddinochrome
                          18.05.2019 17:18
                          -1

                          Проект развивается постоянно. До продажи компании это осуществляется нашими силами. После продажи — силами нового владельца.
                          Мы оформляем информационные системы так, чтобы заказчик мог без программистов менять свойства информационной системы для нужд своего бизнеса. Обычно для этого используется документация с методиками и кастомный domain specific language, который мы разрабатываем под каждый конкретный проект. Таким образом после продажи для развития проекта уже нет нужды лезть в кодовую базу или в структуры данных. Что тут противоречивого?)


                      1. VolCh
                        19.05.2019 15:30

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


                  1. khim
                    18.05.2019 16:58

                    Обычно я ухожу из компании, когда она успешно продана.
                    Ну в этом случае вас действительно должно мало волновать что с ней произойдёт после продажи… но ведь что-то же с ней происходит. Вы никогда не задумывались — что?


                    1. ddinochrome
                      18.05.2019 17:24
                      -1

                      А зачем задуматься? Я и так знаю что произошло со всеми моими проектами. Если проблемы и возникали, то не на уровне технологий, а на уровне развития бизнеса. Иногда из-за неверных управленческих решений новый владелец сам разоряет свой бизнес. Тут уж я ничем не могу помочь.
                      А иногда всё идёт нормально. Короче, кому как повезло)


                    1. Space__Elf
                      18.05.2019 18:08
                      +1

                      Вероятно, это один из способов успешного ведения бизнеса в России
                      — успеть продать бизнес раньше, чем его решат отжать.


                      1. ddinochrome
                        18.05.2019 18:35
                        -1

                        С проблемой отжатия бизнеса я не встречался. Просто тут кому что нравится. Кто-то любит создавать бизнесы, а кто-то — развивать их и жить в уютной операционке. Последние 4 года я работаю не с Россией, а с Ирландией и Великобританией. Там та же история.


                1. ApeCoder
                  18.05.2019 14:47

                  Если программист уходит в отпуск, и обнаруживают ошибку весь код переписывается. Когда он возвращается — он переписывается обратно.


                  1. ddinochrome
                    18.05.2019 16:03
                    -1

                    У нас весь отдел разработки уходит в отпуск одновременно. Предварительно мы приводим инфосистему к стабильному состоянию, в котором она работает без проблем и без постоянной поддержки со стороны разработчиков. В наше отсутствие код не меняется (ибо некому его менять), соответственно риск возникновения проблем минимален.
                    Будут ещё какие-то домыслы про возможные проблемы? Или смиритесь, что мой подход рабочий?


                    1. khim
                      18.05.2019 17:15

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

                      Если один человек в принципе способен разобраться в коде — то тут вообще не нужно обсуждать никакие принципы и подходы. Как ему захочется — так и сделает. Может даже блок-схему со спегетти кодом на доске нарисовать или, прости господи, ДРАКОН, привлечь.

                      Проблемы начинаются тогда, когда размеры кодовой базы и/или организации становятся слишком велики для того, чтобы уместиться в одну голову.

                      И вот тут-то ваш подход «рассыпается». Вы не сможете ничего узнать у «владельца бизнеса», потому что тот появляется в компании раз в квартал, на собрании акционеров и о «бизнес-процессе» знает чуть более, чем ничего. Вы не сможете понять как устроен бизнес-процесс, опрашивая рядовых сотрудников, так как новые структурные подразделения открываются и закрываются каждый день. Ну Ok, может быть через день. Даже если вам в попу вставить реактивный двигатель — вы всё равно всюду не поспеете за изменением «бизнес-процесса». Структуры данных? Ну вот буквально вчера обсуждали вопрос: как лучше разбить класс на два, если он используется в 283 тысячах файлов (не строк, файлов). Успехов вам в выяснении того, что эти 283 тысячи файлов делают.

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

                      Да, для проектов, которые могут «осилить» 5-10 человек — ваш подход годится. Уже 30 человек — сомнительно: в коде начинают пониматься места, которые вы не понимаете. 1000 человек? 10000 человек? Оно просто не работает!


                      1. ddinochrome
                        18.05.2019 18:21
                        -1

                        Очень благодарю Вас за труд по написанию моей биографии в стиле псевдонаучной фантастики. Вот Вам ещё несколько фактов для разжигания фантазии:

                        1. В последние годы я вообще не лезу в код проектов, которыми руковожу. Я не знаю сколько там файлов, строк кода, я даже не знаю есть ли там ООП. Но я досконально знаю требования заказчика, наш технологический стек и его свойства, каждого разработчика и его зону ответственности, и лично провожу ревью каждой фичи.
                        2. Спарта — здесь я был рядовым программистом, на проекте работало 50 человек. Код был фаршем на С++ ровно в том стиле, который пропагандирует @S_Revan в своих комментах.
                        3. Сочи — здесь я был тех лидом, на проекте работало 30 человек. Код — модульный C# под Unity.
                        4. С 2013 года я сознательно не занимался проектами с командой более 10 человек. При этом время, которое я провожу на работе, сократилось в 2 раза, мои доходы выросли в 1,5-2 раза, я получил возможность жить в любой точке мира (что в 2 раза дешевле Москвы и в 100 раз интереснее).

                        Зачем мне ваши 1000-100000 человек и должность «раб лампы»?)


                    1. ApeCoder
                      18.05.2019 21:08
                      +1

                      У нас весь отдел разработки уходит в отпуск одновременно.

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


                      Предварительно мы приводим инфосистему к стабильному состоянию, в котором она работает без проблем и без постоянной поддержки со стороны разработчиков.

                      А если все-таки случается инцидент требующий такой поддержки? Вы пишете вообще без ошибок? Срочная задача, например?


                      Или смиритесь, что мой подход рабочий?

                      Вы, наверное, живете в параллельном мире.


          1. symbix
            18.05.2019 21:33

            Под функциональным кодом я понимаю код, который описывает функции и даёт прямой видимый эффект. Про концепцию ФП я вообще тут не говорю.

            Это называется "процедурное программирование".


            Код как раз пишется для машин. А для человека пишется база знаний. [...] Проектирование решения — это работа и с бизнес-процессами, и со структурами данных, и с алгоритмами.

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


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


            1. PsyHaSTe
              18.05.2019 23:21

              Да нет, типичный ООП обычно обрастает всякими менеджерами, провайдерами и фабриками адаптеров стратегий, отражения которым в домене никаких нет, просто паттерны и подпорки для проектирования в терминах ооп. Вообще подход "все есть объект" обычно не более полезен, чем "все есть указатель" или "все есть файл". Иногда чтобы удовлетворить данному утверждению начинают совмещать ежа с ужом. Если это нужно делать часто, то это называют "паттерном". Пример с современными DI я уже приводил, по сути тот же сервис локатор (который все давно ругать научились), только в профиль.


              1. EvgeniiR
                19.05.2019 00:02

                Забавно выходит, причем не только в этой ветке, один комментатор ругает ООП за то что у людей пытающихся прикрутить ООП получается лапша, а другой, что у людей на ФП получается лапша, и один справедливо замечает что проблема в людях которые не задумываются о нужных вещах :)

                Я бы сказал, что всё будет плохо пока люди не будут задумываться о правильных вещах, или о том что для них вообще важно и нужно, какую бы парадигму они не пытались прикрутить, потому что в данном случае выбор идет лишь по принципу «ну я знаю ООП» или «ну я знаю ФП», или «Ну вокруг говорят что X это круто»


              1. Belikov
                19.05.2019 12:09

                А «все есть функция» из ФП — лучше?
                В ООП при правильном подходе (DDD например), хотя бы часть кода описывает модель в более-менее понятных терминах и ее можно даже бизнесу объяснить (в отличие от ФП). А паттернами все обрастает от неумения строить объектную модель, по-моему.


                1. akryukov
                  19.05.2019 12:17

                  В ФП никто не предлагает ADT считать функциями (js не считается). Поэтому логику все так же можно формулировать в понятных терминах.


              1. VolCh
                19.05.2019 15:39
                +1

                Менеджер — часто просто неудачное название, если сильно захотеть, то из бизнеса можно вытащить реальное.


                А провайдеры часто в домене есть, они появляются при ответе на вопрос: откуда эти данные берутся?


                1. symbix
                  19.05.2019 17:54

                  Не может быть провайдеров в домене. Может быть интерфейс, описывающий протокол получения сущностей (EntityRepositoryInterface, скажем), да и это нужно далеко не всегда.


                  Конкретная реализация уже вне домена, домену все равно, откуда данные берутся и как они мапятся на сущности.


                  1. VolCh
                    20.05.2019 13:48

                    Вы говорите языком программиста, пишущего модель домена. Я же говорю о домене, в котором есть, например, клиенты, поставщики, иные внешние контрагенты, сотрудники разных подразделений, топ-менеджмент, собственники, внутренние и внешние автоматические сервисы и т. п. Они все, например, могут поставлять для процесса начисления НДФЛ определённые данные, необходимые для него. Кто они как не провайдеры этих данных?


                    1. symbix
                      20.05.2019 16:23

                      А, в этом смысле.


                      Тут все зависит от того, в каком bounded context-е на них смотреть. :)


              1. symbix
                19.05.2019 16:19
                +2

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


                Про DI и сервис-локатор: любой DI это одновременно и сервис-локатор. DI отличается от SL не столько реализацией, сколько использованием: при нормальном использовании DI от него нет прямой зависимости (как от сервис-локатора), классы просто принимают свои зависимости в конструкторе, и всегда можно их повторно использовать безо всякого DI или там SL.


                1. VolCh
                  20.05.2019 13:50
                  -1

                  Я бы сформулировал разницу так: SL — библиотека, DI-контейнер — фреймворк :)


                  1. symbix
                    20.05.2019 16:24

                    Почему же? Да, в фреймворках часто есть завязанные на архитектуру фреймворка DIC из коробки, но полно standalone DI-контейнеров, которые библиотеки.


                    1. VolCh
                      20.05.2019 19:03

                      Они библиотеки в каких-то точках входа, но они сами вызывают наш код (конструкторы, фабрики и т. п.), чтобы инстанцировать зависимости.


                1. PsyHaSTe
                  20.05.2019 17:44

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


                  Заменять компайл-тайм проверки на динамику — зло. Никакие "ну тут зато мокнуть можно и сконфигурировать в одном месте" не помощники. Конфигурировать и так пришлось бы в одном месте, но явно в типах, а не в пространных флюент-ассертах.


                  1. symbix
                    21.05.2019 01:11

                    Если вспомнить старика Кея, то весь ООП про динамику и late binding.


                    В принципе, не вижу большой разницы, ну свалится не при компиляции, а в test suite, ну, на минуту позже обнаружили.


                    1. khim
                      21.05.2019 03:56

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


                    1. PsyHaSTe
                      21.05.2019 08:54

                      Я вам конкретный пример привел, как так получилось на практике. Советовать тесты вместо типов может только самый глубокий приверженец динамики.


                  1. Druu
                    21.05.2019 10:51
                    -1

                    Мне с DI очень не нравится словить ошибку из-за того, что тип не зарегистрирован.

                    Нормальные DI фреймворки должны давать ошибку о том, что тип не зарегистрирован :hz:


                    1. PsyHaSTe
                      21.05.2019 11:18
                      +1

                      Ну он и дает.
                      Где-то в регистрации написан код в стиле


                      di.Register(sc => confg.EnableFoo ? new Foo(sc.Resolve<FooDep>) : new Bar(sc.Resolve<BarDep>)

                      А EnableFoo в true тестировался только в 3 приложениях из 4.


              1. alsii
                21.05.2019 15:10

                del


          1. alsii
            21.05.2019 11:57

            Выбросить код не так страшно как кажется.

            Угу. Скажите это тем, кто сегодня тратит невообразиллиарды денег на поддержку COBOL-кода вместо того, чтобы выбросить его и переписать все с нуля.


            1. mayorovp
              21.05.2019 14:45

              Проблема COBOL-кода — в том, что


              1. к нему не известны все требования (каждое требование в отдельности кому-то известно, иначе это не было бы требованием — но вот вместо их так просто не собрать);
              2. его нельзя переписать на новый язык по частям;
              3. его просто много.

              Не не для любого старого кода все эти требования выполняются одновременно...


              1. alsii
                21.05.2019 15:20

                к нему не известны все требования

                Ага. А как понять, что к какому-то существующему коду извстны все требования? Вот прямо все-все? И никто ничего не забыл? Даже если предварительно какие-то тесты написать и убедиться что все тест кейсы одинаково работают для старого и нового кода, как понять, что покрытие 100%? 100% не по коду, а по поведению...


  1. Kroid
    17.05.2019 09:01
    +1

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


    1. sticks
      17.05.2019 09:20
      +1

      а в итоге имеем:

      То есть у меня будет некий объект


    1. exception13x
      17.05.2019 10:21
      -1

      1. Fenzales
        17.05.2019 10:44
        +2

        Нет там функционального подхода, только процедурный.


        1. exception13x
          17.05.2019 10:54

          Тогда объясните, что такое функциональный подход.


          1. Fenzales
            17.05.2019 11:00

            В котором нет состояния, например что-то типа этого.

            В данном примере функции могут иметь сайд-эффекты, которые будут влиять на работу всех остальных функций. При функциональном подходе такого быть не должно.


            1. Fenzales
              17.05.2019 14:10

              Как-то по-дурацки сформулировал. По ссылке пример наличия состояния, если что.


            1. 0xd34df00d
              17.05.2019 21:02

              Функциональное программирование (в хаскель-стиле) — это не про отсутствие эффектов, а про контроль эффектов.


              1. Fenzales
                20.05.2019 10:44

                Да, точно.


          1. mayorovp
            17.05.2019 11:02

            Это подход, при котором функция является объектом первого рода: функции можно передавать как параметры, а также создавать новые в процессе работы.


            В крайнем случае кроме объявления функции и её вызова вообще никаких конструкций не используется. Или даже объявление функции заменяется кучкой стандартных комбинаторов.


            Также в последнее время с ФП связывают идею "чистоты": структуры данных в ФП обязаны быть иммутабельными, а функции обязаны не иметь побочных эффектов и возвращать тот же самый результат при повторном вызове.


            1. Arqwer
              17.05.2019 12:14
              -1

              Пробовал я поверх таких функций построить мемоизацию. Вся чистота и иммутабельность пошла лесом.


              1. mayorovp
                17.05.2019 17:40

                Видимо, как-то не так пробовали...


              1. 0xd34df00d
                17.05.2019 21:03

                Мемоизация и, в частности, динпрог отлично на чистоту ложится.


        1. UnknownUser
          17.05.2019 15:42

          На самом деле (я прочитал только статью, так что не знаю как там автор пропагандирует писать), про ФП ничего написано не было. Так что код не обязан быть в ФП стиле ).

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


      1. oxidmod
        17.05.2019 10:53

        Ну и кодеки\архиваторы — это чисто алгоритмические задачи. Где здесь бизнесс логика какая?


        1. Alexeyslav
          17.05.2019 11:01

          Бизнес-логика там есть, но она тривиальная — 1) прочитать данные с источника, 2) обработать(сжать, переконвертировать, разделить на потоки) 3) сохранить в цель.


        1. exception13x
          17.05.2019 11:17

          Условия были:
          * 2 репозитория
          * используемых в реальной жизни
          * в коде нет ни одного класса
          * одна «функциональщина»
          Какие претензии?


          1. oxidmod
            17.05.2019 11:23
            +1

            Вы сами не понимаете, почему ваши репы не подходят под пример с Player/Monster?


            1. exception13x
              17.05.2019 11:34

              Тогда может быть, просветите меня чем алгоритмические задачи отличаются от бизнес-логики?


              1. oxidmod
                17.05.2019 11:41
                +1

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


                1. exception13x
                  17.05.2019 12:20

                  И которое «дело» у вас зовут бизнес логикой?
                  Мне кажется, Вы всерьез считаете, что термин бизнес логика произошел от слова «предпринимательство», а не от слова «занятие».
                  Это так?


              1. ghrb
                17.05.2019 12:26

                Сложностью? Алгоритмическая задача это один алгоритм, бизнес-логика это когда каждый день к проекту добавляли новый алгоритм на протяжении N лет. И это всё должно хорошо читаться, поддерживаться, не падать от первого прикосновения и не нужно трястись что последний старожил уволится/умрёт и тогда никто не сможет ничего в проекте доработать.


    1. PsyHaSTe
      17.05.2019 15:45

      Все кто критикует ооп функциональщики? Кстати в качестве 'решения' автор предлагает все то же ооп, только со слегка иным вкусом


    1. EvgeniiR
      19.05.2019 11:50

      А вот, собственно и гитхаб с интересующим нас кодом — github.com/dpc :)
      Это гитхаб автора оригинального поста, ссылка на него есть у него в блоге, на вкладке open source.

      Пример кода:

      php
      function generateCommonMenu($nas_name)
      {
      	global $tpl;
      	global $config;
      	$tpl->addBlockfile("common_menu", "common_menu", "common_menu.html");
      	$tpl->setVariable("nas", $nas_name);
      	$tpl->setVariable("edit_url",
      		generateUrl(
      			array("mg" => "nas", "mm" => "edit", "nas" => $nas_name)
      		)
      	);
      	$tpl->setVariable("info_url",
      		generateUrl(
      			array("mg" => "nas", "mm" => "info", "nas" => $nas_name)
      		)
      	);
      	$tpl->setVariable("acct_url",
      		generateUrl(
      			array("mg" => "acct", "mm" => "generic",
      			"value" => $nas_name, "search" => "nas-name")
      		)
      	);
      	$tpl->setVariable("online_url",
      			generateUrl(
      				array("mm" => "users-online", "mg" => "nas",
      				"nas" => $nas_name)
      			)
      		);
      	$nas_info = getNas($nas_name);
      	if ($nas_info) {
      		$tpl->setVariable("nas_common_nas_descr",
      			htmlentities(
      				$nas_info[SQL_NAS_COLUMN_DESCRIPTION],
      				ENT_QUOTES, "UTF-8"
      			)
      		);
      	}
      }
      ?>


      1. EvgeniiR
        19.05.2019 12:38

        upd: судя по копирайтам это бэкап написанного им очень давно кода. Можно взглянуть на код на rust`е в закрепленных репозиториях. Не буду судить о качестве кода на совершенно незнакомом для меня языке


        1. freecoder_xx
          19.05.2019 18:08

          Ну, через текст статьи уже проступает некоторая "ржавчина" ) В Rust нет ООП (в смысле наследования данных и образования подтипов с помощью наследования), нет культуры обязательного наличия геттеров/сеттеров, уровень доступа к полям можно ограничивать видимостью в модуле и т.п. Rust снимает большинство претензий автора.


          1. symbix
            19.05.2019 18:11

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


            Если придерживаться более разумных практик, то между Растом и Simula-подобными языками разница в определенном смысле будет чисто синтаксическая по большому счету.


            1. freecoder_xx
              20.05.2019 10:41

              Это вряд ли. Проблема ООП (в том виде как оно реализовано в мейнстримовых языках) как минимум в том, что в нем смешивается наследование с образованием подтипов.


              1. VolCh
                20.05.2019 13:56

                Можно ваши определения наследования и образования подтипов?


              1. symbix
                21.05.2019 01:15

                Надо просто мысленно отделять value objects от всего остального, и все будет хорошо.


      1. Druu
        19.05.2019 13:00

        А как бы вы записали этот код?


        1. ghrb
          19.05.2019 13:05

          Того что в коде есть глобальные переменные вам мало? Убрать бы их для начала.


          1. Druu
            19.05.2019 13:33

            Убрать бы их для начала.

            Ну допустим, две эти строки поменялись и вы не из глобала их достается, а из импортированного из некоторого модуля объекта. Что дальше?


            1. ghrb
              20.05.2019 10:09

              Убрать магические строки и хорошо бы вообще отказаться от сеттеров для шаблонов, просто передавать шаблонизатору переменные. Да и зачем вообще меню процедурно генерировать, намного нагляднее написать его в html и всё, подставлять значения вместо псевдопеременных.


              1. Druu
                20.05.2019 10:56

                Ну это все несущественные детали, я хотел узнать, как вы идейно этот код по-другому представить хотели.


                Да и зачем вообще меню процедурно генерировать, намного нагляднее написать его в html и всё, подставлять значения вместо псевдопеременных.

                Это уже совсем другой алгоритм бы был.


        1. VolCh
          20.05.2019 14:07

          Прежде всего переименовать :) — это не generateCommonMenu, а какой-то buildCommonMenuBlockByNasName.


          Потом разделить ответственности. Тут их навсидку 5.


  1. Ryppka
    17.05.2019 09:10
    +1

    При всем моем критическом отношении к ООП статья выглядит поверхностной и несколько бессвязной, все смешано в кучу.
    И, как обычно, мне непонятно, как концепция абстрактных типов данных (классы) оказалась столь тесно связана с ООП?!


  1. andy_p
    17.05.2019 09:11

    > цель -> архитектура данных -> код

    С такой последовательностью, конечно будет кривой код.
    Должно быть:

    цель -> алгоритм -> структуры данных -> код

    Архитектура данных выбирается в зависимости от алгоритмов,
    а не наоборот.


    1. flancer
      17.05.2019 09:40

      Тогда уж:


      цель -> структуры данных -> алгоритм -> структуры данных -> код -> структуры данных

      так как алгоритмы по своей сути — тот же код, только вид сбоку, описание последовательности действий над данными. Прежде, чем создавать последовательность действий, нужно определить, над чем можно производить эти действия. Т.е., определить предметную область. Структуры данных в конце цепочки я для красоты добавил, т.к. обычно код налагает свои ограничения на данные, которыми он может оперировать (MAX_INT, напирмер). Просто для усиления идеи, что в программировании данные фундаментально первичны.


      1. andy_p
        17.05.2019 17:31
        -2

        Когда не выбран алгоритм, это значит, что вообще не понятно, как решать задачу. О каких структурах данных можно говорить в этот момент ?


        1. ddinochrome
          18.05.2019 00:23

          Важно сначала сформулировать задачу, а потом искать способ её решения.
          Классическая последовательность: «Зачем? --> Что? --> Как?»
          У автора статьи идёт прямая проекция: Зачем? — это цель; Что? — это структура данных; Как? — это реализация, то есть алгоритм или код.
          Но некоторые люди могут действовать только на уровне абстракции «Как?», поэтому для них алгоритм первичен, а структуры данных (не говоря уже о целях) околачиваются где-то на задворках сознания. Может быть, в итоге и получается «некривой» код (по субъективному мнению авторов), но почти всегда он приводит к кривому решению бизнес-задачи. Если вообще что-то решает.


          1. S-e-n
            18.05.2019 01:03

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


            1. ddinochrome
              18.05.2019 01:33

              Зачем? — не сказано
              Что? — без понятия — какие-то данные, какие-то операции
              Как? — расположить данные в таблицу горизонтально или вертикально и проводить одни и другие операции

              Мне в принципе думать об этом сложно) Слишком мало информации о задаче. И здесь, как я полагаю, Вы под структурой данных подразумеваете сервисную структуру, которой оперирует алгоритм. Но люди из Data Driven мира под структурой данных всегда имеют ввиду входные данные, которые Вы всё равно не контролируете — они определяются легаси-кодом и устоявшимися бизнес-процессами. А как вы её там трансформируете для своих нужд — это Ваше внутреннее дело.


              1. andy_p
                18.05.2019 09:00

                Как можно выбирать организацию структур данных не имея алгоритма?
                Приведу пример: как вы будете хранить коллекцию структур в вашей программе — в векторе, листе или мапе? Это зависит от алгоритма, т.е. от того, как ваши структуры будут добавляться, удаляться и как будет происходить их поиск.


                1. ddinochrome
                  18.05.2019 10:47

                  Вы не поставили мне задачу, поэтому рассуждать о путях её реализации бессмысленно. Впрочем, 90% заказчиков так же поступают. Поэтому я должен спросить — Что конкретно вы хотите сделать? Зачем это нужно, как это будет использоваться в реальности?

                  как вы будете хранить коллекцию структур в вашей программе?

                  Прежде чем писать программу, я напишу документ с требованиями к ней. И пока что там зияющая пустота)


                  1. khim
                    18.05.2019 17:18

                    Де легко. Пара строк буквально: в сайтах из Top10000 должно быть не более 98% неработающих, все сайты из Top100 работают правильно.

                    Это примерно техзадание на Chrome (когда он ещё был секретным проектом). Просто, не так ли?


                    1. ddinochrome
                      18.05.2019 18:39

                      Простите великодушно, но я не понял вообще к чему это)
                      Мы тут с Энди уютно общались про векторы, листы и мапы. И вдруг на тебе — Top10000!


                      1. khim
                        18.05.2019 19:00

                        Вы, наверное, не поняли задания. Мы хотим делать браузер. Критрий приёмки — то, как в нём отображется десять тысяч самых популярных сайтов. 98%. А 100 самых популярных — должны вообще работать без нареканий.

                        Вроде не очень длинное задание…


                        1. ddinochrome
                          18.05.2019 19:04

                          Сколько платите? Какие сроки? Где критерий «нареканий»? Где ссылки на списки топов?


                          1. Cerberuser
                            18.05.2019 19:24

                            Первый вопрос не имеет отношения к прояснению деталей ТЗ, по идее. Я не спорю, что его задавать следует, но сейчас он, имхо, вырывает из контекста.


                            1. ddinochrome
                              18.05.2019 19:27

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


                              1. khim
                                18.05.2019 20:29

                                Это мне начинает напоминать ГСМ


                                Браузеры кто-то ведь когда-то делал. И операционки. Линус вообще сделал ядро «в одно рыло». И если вы считаете, что ваш подход для них не годится — то так и скажите. Мы поймём.

                                А вот это «я буду с вами разговаривать только если вы вначале заплатите миллион долларов наличными» — лучше кому-нибудь другому оставьте.


          1. Ryppka
            18.05.2019 22:32

            Важно сначала сформулировать задачу, а потом искать способ её решения.

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


  1. Beshere
    17.05.2019 09:22

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

    А через 30 лет появляется человек и вдруг замечает «а двухколесный велосипед не всегда самый удобный» и начинает делать нечто, что давно сгнило на перфокартах.


  1. neurocore
    17.05.2019 09:35
    +4

    Предложенная задачка про избиение монстра прекрасно решается паттерном команда. Когда A бьёт B пишем в очередь «hit A B by C»; когда команда обрабатывается, B получает урон. Если B погибает, то его объект инициирует команду «dead B by A» и экспа прилетает объекту A.


    1. PsyHaSTe
      17.05.2019 15:49

      Статья довольно слабая, но вот комментарии отлично иллюстрируют проблему. Все изобретают каких то менеджеров, акторы которые там урон разносят команды и стратегии… Тогда как нам просто нужна функция attack(player, monster) - > (newPlayerState, newMonsterState)


      1. kpakozz96pyc
        17.05.2019 16:11
        +2

        Если у вы пишете на ООП языке вам надо чтобы эта функция где-то лежала и кто-то её выполнял — это и есть менеджер.


        1. khim
          18.05.2019 03:13

          Это не в ООП. Это в Simula 67 и его потомках. И да — ровно эта болезнь обсуждается в статье.


        1. ApeCoder
          18.05.2019 14:49

          Почему менеджер а, не, например, Игра


        1. PsyHaSTe
          18.05.2019 19:51
          +1

          Мы сейчас говорим на уровне самой функции "нанеси урон". Кто её будет вызывать, вопрос отдельный.


          Ну сириузли, посмотрите FizzBuzz enterprise edition. Он неспроста такой. И реально многие люди делают софт именно так. Люди реально разучились думать в терминах функций. Такое ощущение, что люди думают в стиле "блин, если я просто напишу функцию, это будет нерасширяемо и не по гайдлайнам, пацаны засмеют. Запилю-ка я пару классов. Так, теперь надо DI настроить. И автомаппинг на DTO. И вот этот миддлвейр впилить. Во, теперь энтерпрайзненько".


          Вот буквально вчера я правил багу с тем, что при некотором стечении обстоятельств тип не регистрировался и мой DI фреймворк выдавал "не смог зарезолвить зависимость Y" с километровым стектрейсом. Почему я должен ловить это в рантайме? А потому что DI это серьезно, это показывает класс. Люди начинают говорить про SOLID, DDD и прочее, как надо инвертировать зависимости и все такое. Много умных слов, а на деле я пытаюсь понять, почему в рантайме вылез null, тогда как я мог все сконфигурировать в компайл тайм и компилятор бы меня проверил. Но нет, этот подход недостаточно серьезный, обязательно надо обмазаться рантайм рефлексией, а тип получать через сервис, даже если у всех интерфейсов всего 1 реализация. После такого смотришь на ФП с их фри монадами, и понимаешь, что жить-то оказывается можно сильно лучше, не теряя при этом инверсию зависимостей.


          image


          Но люди смотрят на ФПшников как на академиков, которые дальше своего носа не видят, и не умеют в "настоящую разработку". При этом лямбды и функции высших порядков уже давно нашли дорогу в мейнстрим языки, стримы/итераторы за ними, сейчас наступает черед тайпклассов, паттерн матчингов и отказа от эксепшнов в пользу адт. Причем именно в серьезной разработке обычно все это эмулируется какой-нибудь внутрикорпоративной либой. И неспроста там происходит, я вам скажу.


      1. Guitariz
        17.05.2019 16:18

        Player совсем не факт, что атакует одного монстра и не просто машет саблей.


        1. khim
          18.05.2019 03:14
          -1

          Тоже мне проблема. Цикл — такое слово слышали?


          1. TheShock
            18.05.2019 03:16
            +1

            А вы слышали слово "амбидекстрия"?


        1. PsyHaSTe
          18.05.2019 19:57

          Слушайте, это уже разговоры в стиле задачи теорвера про подбрасывание монетки "а что если на ребро? А что если в воздухе зависнет?". Это все за рамками обсуждения, мы обсуждаем конкретную модель. Можно подумать, как будет развиваться в этих рамках модель кода. Ну будет множество монстров, можно добавить какой-нибудь параметр типа сплеша и т.п. Общий смысл не меняется. Просто делаем чистую функцию, которая вычисляет результат. Например, мне сейчас пришло в голову, что атака многих монстров может работать через эту функцию. Просто делаем функцию типа


          attackMany(player, monsters) -> 
             let attackResults = monsters.fold((player, List()), (state, monster) -> 
                let attackResult = attack(state.player, monster);
                (attackResult.player, state.monstersState :: attackResult.monsterState)

          ну и все.


          1. neurocore
            20.05.2019 06:19

            Ну то есть, если избавиться от функционального стиля, то всё тоже можно описать как свободную функцию двух аргументов

            void attack(Unit & player, vector<Unit> & monsters);

            Я правильно понимаю?


            1. PsyHaSTe
              20.05.2019 07:30

              Не совсем. Без функционального стиля вы не сможете сделать композицию нормально, если она понадобится.


              Хороший пример из книжки:


              Suppose we’re implementing a program to handle purchases at a coffee shop. We’ll begin with a Scala program that uses side effects in its implementation (also called an impure program).
              def buyCoffee(cc: CreditCard): Coffee = {
              val cup = new Coffee()
              cc.charge(cup.price)
              cup
              }


              The line cc.charge(cup.price) is an example of a side effect. Charging a credit card involves some interaction with the outside world—suppose it requires contacting the credit card company via some web service, authorizing the transaction, charging the card, and (if successful) persisting some record of the transaction for later reference. But our function merely returns a Coffee and these other actions are happening on the side, hence the term “side effect.” (Again, we’ll define side effects more formally later in this chapter.) As a result of this side effect, the code is difficult to test. We don’t want our tests to actually contact the credit card company and charge the card! This lack of testability is suggesting a design change: arguably, CreditCard shouldn’t have any knowledge baked into it about how to contact the credit card company to actually execute a charge, nor should it have knowledge of how to persist a record of this charge in our internal systems. We can make the code more modular and testable by letting Credit- Card be ignorant of these concerns and passing a Payments object into buyCoffee.
              def buyCoffee(cc: CreditCard, p: Payments): Coffee = {
              val cup = new Coffee()
              p.charge(cc, cup.price)
              cup
              }


              Though side effects still occur when we call p.charge(cc, cup.price), we have at least regained some testability. Payments can be an interface, and we can write a mock implementation of this interface that is suitable for testing. But that isn’t ideal either. We’re forced to make Payments an interface, when a concrete class may have been fine otherwise, and any mock implementation will be awkward to use. For example, it might contain some internal state that we’ll have to inspect after the call to buy- Coffee, and our test will have to make sure this state has been appropriately modified (mutated) by the call to charge. We can use a mock framework or similar to handle this detail for us, but this all feels like overkill if we just want to test that buyCoffee creates a charge equal to the price of a cup of coffee. Separate from the concern of testing, there’s another problem: it’s difficult to reuse buyCoffee. Suppose a customer, Alice, would like to order 12 cups of coffee. Ideally we could just reuse buyCoffee for this, perhaps calling it 12 times in a loop. But as it is currently implemented, that will involve contacting the payment system 12 times, authorizing 12 separate charges to Alice’s credit card! That adds more processing fees and isn’t good for Alice or the coffee shop.

              What can we do about this? As the figure at the top of page 6 illustrates, we could write a whole new function, buyCoffees, with special logic for batching up the charges. Here, that might not be such a big deal, since the logic of buyCoffee is so simple, but in other cases the logic we need to duplicate may be nontrivial, and we should mourn the loss of code reuse and composition!

              Как это в фунцкиональном стиле решается там описано, но еще столько же текста мне показалось будет слишком много для комментария. Красная книга Scala, поищите, это первая глава :)


      1. alsii
        17.05.2019 16:40
        +1

        Нам просто нужно 100500 функций для всех возможный сочетаний классов типов player и monster и их количеств. И желательно все в глобальной области видимости. И еще желательно рекурсивных. И вызывающий друг-друга самым беспорядочным образом.


        1. 0xd34df00d
          17.05.2019 21:09
          -1

          Нам просто нужно 100500 функций для всех возможный сочетаний классов типов player и monster и их количеств.

          ООП как-то спасёт от 100500 возможных сочетаний?


          И разные player и monster не обязаны быть разными типами (а могут и быть, конечно, если очень нужно, но так мы к tagless final придём в итоге, а не хочется).


          И желательно все в глобальной области видимости.

          Ну не экспортируйте их из модуля, тоже мне проблема.


          1. mayorovp
            18.05.2019 01:25
            -2

            Не путайте свои пожелания как ФП-программиста с требованиями предметной области.


        1. mamitko
          17.05.2019 22:10

          Хоть один толковый коммент! :)
          Я тоже за систему правил (может просто плоский набор) из которых выбирается самое или просто подходящее. И уже правило модифицирует hp и xp.


          1. khim
            18.05.2019 03:16

            А теперь посмотрите на CLOS — и убедитесь, что это всё ещё ООП. Довольно старый притом, The Art of the Metaobject Protocol — это 1991й год.


      1. Bronx
        18.05.2019 12:36

        Или нам нужна функция attack(player, monster, battlefield, engagementRules, gameConfig) -> (newPlayer, newMonster, newBattlefield). И тут выясняется, что engagementRules, gameConfig и прочие параметры окружения приходится тянуть в добрый десяток функций, поэтому вначале напрашиватся решение "выделить это всё в объект context", а следом "а почему бы нам не передавать этот контекст неявно?" — и у нас появляется this и ООП.


        1. mayorovp
          18.05.2019 14:19

          Не всё так плохо в ФП. engagementRules и gameConfig выносятся первыми параметрами, после чего каррированная функция частично применяется и вот у нас уже есть нормальная функция (player, monster, battlefield) -> (newPlayer, newMonster, newBattlefield).


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


          1. Bronx
            19.05.2019 00:07

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


            new Combat(engagementRules, gameConfig).attack(player, monster);

            почти эквивалентно


            attack(engagementRules, gameConfig)(player, monster)

            только в ФП каррирование придётся сделать для каждой такой функции отдельно.


            1. 0xd34df00d
              19.05.2019 00:26

              только в ФП каррирование придётся сделать для каждой такой функции отдельно

              Что значит «придётся делать»? Оно там бесплатно.


              А в ООП — нет. Поэтому вам приходится думать, как прокидывать gameConfig аж до Combat, а мне — нет.


              1. Bronx
                19.05.2019 04:12
                +1

                Что значит «придётся делать»?

                Я имел в виду, что если у нас есть модуль с десятком близкородственных функций — protec(), attac(), trac(), barrac(), connec(),… — и у каждой есть зависимость от (gameConfig, player, ...), и мы хотим их все каррировать до action(player, ...) чтобы не таскать gameConfig по всему коду, то нужно проделать каррирование с каждой функцией в отдельности, и, чтобы не повторять это, сохранить их в какой-то структуре, что уже напоминает класс.


                Если же эти функции — члены класса, то мы проталкиваем зависимости один раз в конструкторе, после чего они доступны каждой функции через this.


                Оно там бесплатно. А в ООП — нет.

                Не вижу большой разницы. В любом случае всё сводится к некоторому "замыканию", только в ООП оно делается через конструктор и сразу для пачки функций, а в ФП — через лямбду и для каждой функции в отдельности. Ну разве что в ООП чуть больше церемоний с объявлением и с наследованием этих "замыканий", плюс приходится в коде различать this- и local-параметры, плюс часто не поддерживается повторное каррирование на методе (т.е. у метода нельзя связать player и оставить monster свободным) — теряется гибкость, да.


                Поэтому вам приходится думать, как прокидывать gameConfig аж до Combat, а мне — нет.

                В ФП точно так же придётся продумывать, как протащить gameConfig от корня программы до каждого вызова зависимой от неё функции (или делать gameConfig глобальной переменной). В ООП это нужно делать только до вызова конструктора.


                1. 0xd34df00d
                  19.05.2019 21:07

                  Если же эти функции — члены класса, то мы проталкиваем зависимости один раз в конструкторе, после чего они доступны каждой функции через this.

                  Поэтому я уже написал про ReaderT, который композится куда лучше, чем this.


                  Привязывать функции к классу только ради энвайронмента — очень сомнительная идея, как по мне. Вы теперь все функции, которым нужен один и тот же энвайронмент, будете засовывать в один класс? Или создавать по классу на функцию? А когда какой-то функции понадобится энвайронмент A и энвайронмент B одновременно? А когда ей потребуется вызвать функцию, которой нужен только энвайронмент A, и которая уже написана?


                  а в ФП — через лямбду и для каждой функции в отдельности

                  Даже лямбд не надо, если у меня есть функция foo :: a -> b -> c -> d, то foo a b уже частично применённая. Можно её такую частично применённую куда-то передать и так далее.


                  В ФП точно так же придётся продумывать, как протащить gameConfig от корня программы до каждого вызова зависимой от неё функции

                  Не придётся, см. выше.


                  1. Druu
                    20.05.2019 11:01

                    Привязывать функции к классу только ради энвайронмента — очень сомнительная идея, как по мне. Вы теперь все функции, которым нужен один и тот же энвайронмент, будете засовывать в один класс? Или создавать по классу на функцию? А когда какой-то функции понадобится энвайронмент A и энвайронмент B одновременно? А когда ей потребуется вызвать функцию, которой нужен только энвайронмент A, и которая уже написана?

                    Тут все очень просто — в 99% случаев, если нам нужна ф-я с энвайронметом Х, то она прекрасно логически ложится в соответствующий класс, предоставляющий этот энвайромент. Ну а для 1% оставшихся случаев есть паттерно-костыли.


                    Не придётся, см. выше.

                    Ну ридер для прокидывания энвайромента — это на самом деле закат солнца руками. Фактически, this делает в точности тоже, но автоматически. А когда компилятор делает что-то за меня автоматически — это всегда хорошо.


                    1. 0xd34df00d
                      20.05.2019 17:12
                      +1

                      Тут все очень просто — в 99% случаев, если нам нужна ф-я с энвайронметом Х, то она прекрасно логически ложится в соответствующий класс, предоставляющий этот энвайромент.

                      И получается god object, ок.


                      Ну ридер для прокидывания энвайромента — это на самом деле закат солнца руками. Фактически, this делает в точности тоже, но автоматически

                      Чем написание констрейнта на MonadReader или прямое заворачивание в ReaderT более ручное, чем создание класса и перенос функции в этот класс?


                      Компилятор куда скорее за меня выведет нужные инстансы и констрейнты, чем сделает класс.


                      1. Druu
                        21.05.2019 10:56

                        И получается god object, ок.

                        Это если вы весь энвайромент свалите в одну кучу.


                        Чем написание констрейнта на MonadReader или прямое заворачивание в ReaderT более ручное, чем создание класса и перенос функции в этот класс?

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


                        Компилятор куда скорее за меня выведет нужные инстансы и констрейнты, чем сделает класс.

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


        1. PsyHaSTe
          18.05.2019 19:59

          Глобально ООП ничего не упростит, а иногда даже наоборот, ибо закон Деметры беспощаден.


          Вот неплохой пример, который показывает, как жить с такими параметрами. Как видно, никакого ужаса-ужаса как вы описали не происходит. Частичное применение наше все.


        1. akryukov
          18.05.2019 20:24

          Любопытно, что в этом случае в scala полезно применить implicit аргументы.
          Примерно так:


          case class Player(power:Int, energy: Int)
          case class Monster(hp:Int)
          case class Environment(terrainType:Int)
          
          def attack(player: Player, monster: Monster)
                    (implicit env: Environment): (Player, Monster) = {
            val newPlayerState = player.copy(energy = player.energy - env.terrainType)
            val newMonsterState = monster.copy(hp = monster.hp-player.power*env.terrainType)
            (newPlayerState, newMonsterState)
          }
          
          val p = Player(1,3)
          val m = Monster(20)
          implicit val env = Environment(2)
          val (p2, m2) = attack(p, m)
          println(p2)
          println(m2)

          и собирать engagementRules, gameConfig и прочие параметры окружения в Context не нужно


        1. 0xd34df00d
          19.05.2019 00:35

          а следом "а почему бы нам не передавать этот контекст неявно?" — и у нас появляется this и ООП.

          Даже не обращая внимание на каррирование, про которое вам написали, вместо this и ООП у вас появляется ReaderT GameConfig, ReaderT EngagementRules и прочие, которые композятся куда лучше (ибо несколько this у вас быть не может, если не наследовать, поэтому возникает соблазн что-нибудь скостылять наследованием, и всё начинает плохо пахнуть).


      1. ApeCoder
        18.05.2019 14:50

        Которую мы положим в файл с каким именем?


  1. vav180480
    17.05.2019 09:54

    Работал я в одной компании (Delphi + Oracle) там было так, в приложении (на клиентском компьютере) вызывается процедура базы данных (у нас на сервере PL/SQL) типа сформировать платежки за такой то период, внутри процедуры собираются вагоны исходных данных, все пробулькивает, создаются записи в БД соответствующих платежек, база отвечает приложению «Я сделяль» пользователь в соответствующем окошке смотрит платежки, т.е. обмен по сети минимален
    Работал в другой компании (Scala + PostgreSQL) там было так, приложение (на клиентском компьютере) запрашивало из БД вагоны исходных данных, в клиентском приложении вызывалась процедура формирования платежек, все это пробулькивало на клиенте, формировались платажки и отправлялись обратно в БД. Странно:) Даже интересно серверу проще сложить (умножить поделить… в финансовых расчетах в общем то 4 действия арифметики) 1000 переменных и вернуть результат, или передать 1000 переменных и получить результат


    1. flancer
      17.05.2019 10:03

      Зависит от мощности сервера, пропускной способности сетки, мощности и кол-ва рабочих станций. Оба решения имеют свои области применимости. Оба решения плохие, т.к. имеют свои узкие места, которые обязательно вылезут в процессе эксплуатации. Что характерно, достоинства обоих решений, являющиеся продолжением их недостатков, будут восприниматься пользователями как само собой разумеющееся, отдельного упоминания недостойное.


      1. andewil
        17.05.2019 22:11
        +1

        В общем случае обрабатывать данные нужно там, где они хранятся.


        1. Bronx
          18.05.2019 12:42

          А если данные хранятся распределённо?


          1. vav180480
            18.05.2019 17:44

            Думаю в общем случае все что МОЖЕТ быть обработано в месте хранения, должно быть обработано в месте хранения, предварительные выборки, расчеты и ТП. Если вы храните «шапки» документов на одном сервере, а спецификации на другом, то это немножко странно:)


  1. vladkorotnev
    17.05.2019 09:59

    когда class Player ударяет при помощи метода hits() class Monster, где на самом деле нужно изменять данные?


    Как мне кажется, здесь должен быть введён ещё один объект а-ля GameCore, который будет обрабатывать все эти ивенты и прочее. Тогда и структура остаётся ближе к реальной — есть монстр, пушка и игрок, но они лишь объекты, а взаимоотношения между ними задаются правилами игры. И тогда Player.hit должен передаваться просто как ивент к GameCore.handleHit(sender, object), который проверит у sender'а weapon, раскидает ему экспы и object'у хп. Вообще по-хорошему нужно ввести ещё и Playfield, и работать с ивентами в абстрактных координатах, тогда будет проще добавлять мультиплеер например :-)


    1. Guitariz
      17.05.2019 10:10

      Никто вам не мешает добавть объект Hit в игровой Stage. У объекта Hit есть порождающий объект. Stage найдет все объекты, которые затронет Hit, и раздаст и целям, и порождающим объектам кому опыт, кому дамаг.


    1. EvgeniiR
      17.05.2019 10:24

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


    1. Ryppka
      17.05.2019 11:35
      -1

      А мне кажется, что должен быть введен объект-синглтон F*ingRealWorld, которому объект Player при помощи метода hit(...) направит сообщение ImTryingHitTheMonster.
      А F*ingRealWorld это сообщение обработает: направит объекту Monster, тот ответит сообщением ImSuccessfullyBlockedTheHitNoDamageCaused.
      И объект Player получит от F*ingRealWorld ответное сообщение: MonsterUntouchedOblomisLoh…
      ООП такое ООП…


      1. Cerberuser
        17.05.2019 14:42
        +1

        F*ingRealWorld

        Мне аж интересно, первое слово здесь — это прилагательное или (дее)причастие...


        1. 0xd34df00d
          17.05.2019 21:15

          Зависит от жанра игры.


      1. Flammar
        17.05.2019 14:45

        объект-синглтон F*ingRealWorld
        Объект-медиатор (есть такой «дизайн-паттерн»), он же объект-менеджер (реализация паттерна «стратегия»), он же потенциальный «God Object» (есть такой антипаттерн).


        1. Ryppka
          17.05.2019 15:51

          Паттерны-шматтерны-антипаттерны — а для каждого взаимодействующего F*ingRealWorld — один на всех и общий. И чихал он на инкапсуляцию, наследование и полиморфизм со всеми лучшими практиками, процессами, концепциями, парадигмами и догмами))))


          1. Guitariz
            17.05.2019 16:22

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


            1. Ryppka
              17.05.2019 16:41

              Вы не поняли, я имел в виду тот самый f*ing real world:))))
              Которому совершенно фиолетово на все концепции и парадигмы и пустопорожние препирательства о паттернах их чистоте кода))))


          1. alsii
            17.05.2019 16:49

            отчего же один? Может много быть, для различных условий боя, в разных зонах/режимах игры, например. Или даже одновременно для разных типов действий (нампример физический/магический урон можно обеспечить двумя менеджерами, которые работают "параллельно". а если еще магию сделать не одного вида, то можно вообще несколько "планов" сражения сделать).
            И даже если он один, зачем ему чихать на что-то? Он например может механизмы боя отдельно инкорпорировать выбирая из кучи реализаций.


            1. Ryppka
              17.05.2019 17:08

              Ну правда! Вы пытаетесь меня троллить или правда не понимаете, что это был просто стеб?!


              1. Guitariz
                17.05.2019 17:39

                Просто не смешно


                1. Ryppka
                  17.05.2019 21:50

                  На всех не угодишь...)))


  1. NitroJunkie
    17.05.2019 10:04

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

    К инкапсуляции действительно есть претензии (прежде всего в плане модульности). К наследованию и полиморфизму я так понимаю у автора вопросов нет. Поэтому заголовок конечно странный, без наследования и полиформизма построить сложную и гибкую систему очень тяжело (во всяком случае за адекватные деньги / время), а отказываться от обоих концепций сразу это с водой выплескивать ребенка.


    1. Arlekcangp
      17.05.2019 10:49

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


      1. NitroJunkie
        17.05.2019 11:00

        Момент, что автор похоже больше связан с прикладным программированием, я действительно упустил. А в прикладном программировании объектно-реляционный семантический разрыв это на самом деле та еще боль, и тогда понятно откуда все эти страдания (и кстати да там скорее всего у акторов там полный доступ к бд, хотя как по мне эти акторы та еще дырявая абстракция)


    1. EvgeniiR
      17.05.2019 11:20

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

      Инкапсуляция это объединение данных и логики работающей с этими данными, что такое в вашем понятии «инкапсуляция методов»?

      (строго говоря наследование и полиморфизм тоже можно использовать по отдельности, но смысла в этом очень мало)

      Полиморфизм прекрасно обходится без наследования. Более того, реализовывать полиморфизм без наследования считается предпочтительнее чем с ним.

      ообще говоря, ООП состоит из двух концепций, в общем случае не связанных друг с другом:

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


      1. NitroJunkie
        17.05.2019 11:47

        Инкапсуляция это объединение данных и логики работающей с этими данными, что такое в вашем понятии «инкапсуляция методов»?


        В моем понимании инкапсуляция более общее понятие — это вообще объединение функционала имеющего отношение к классу и размещение его внутри класса. Поля это или методы уже вторично. Собственно в определении инкапсуляции и кроется основная претензия к нему, потому как за объединение функционала вроде модули отвечают. А основная функция классов это все же наследование+полиморфизм.

        Полиморфизм прекрасно обходится без наследования. Более того, реализовывать полиморфизм без наследования считается предпочтительнее чем с ним.

        А по какому критерию (кроме как принадлежности классу) реализуется этот полиморфизм? Пример можно языка какого нибудь, где есть полиморфизм не по принадлежности классу? То есть понятно что полиморфизм можно и явными выражениями задавать, но я на практике такого не припомню.

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


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


        1. EvgeniiR
          17.05.2019 11:53

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

          А ещё в куче книжек, и в википедии, кстати, тоже, по другому.
          Только вот инкапсуляция наследование и полиморфизм были и есть и в процедурных языках, например в том же Си.

          А по какому критерию (кроме как принадлежности классу) реализуется этот полиморфизм?

          1. Через интерфейсы
          2. Все варианты реализации параметрического и ad-hoc полиморфизма


          1. NitroJunkie
            17.05.2019 12:08

            А ещё в куче книжек, и в википедии, кстати, тоже, по другому.


            В википедии вроде так:
            Основные принципы структурирования в случае ООП связаны с различными аспектами базового понимания предметной задачи, которое требуется для оптимального управления соответствующей моделью:

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


            Абстрагирование это что-то супер абстрактное, оно во всех парадигмах есть. Так что остается только три, которые я и перечислил.

            Через интерфейсы


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

            Все варианты реализации параметрического и ad-hoc полиморфизма


            Вы сейчас теплое с мягким мешаете. Понятно что в ООП и выше речь шла про subtype полиморфизм. Ad-hoc и параметрический полиморфизм — это вообще особенности resolve'инга, то есть синтаксический сахар (про них к примеру большинство языков в run-time забывают, если не считать reflection'а), для удобства именования функций и более правильного вывода типа (иначе скажем функции пришлось называть бы sumInteger, sumString и т.п.)


            1. JSmitty
              17.05.2019 13:45

              Интерфейс — это тип. Класс — это код + инкапсулированные данные. То, что мейнстрим языки не делают разница между типами и кодом, с ним связанным — это просто исторически сложившееся упрощение.


              1. Ryppka
                17.05.2019 15:52

                И кто это Вам сказал такое? Класс, структура и интерфейс (подчеркните то, что есть в рассматриваемом типизированном языке) суть типы.


            1. Flammar
              17.05.2019 14:50

              Скоро field'ы еще туда добавят, будет еще смешнее, как они будут разницу между интерфейсами и классами объяснять
              Смысл как раз в том, чтобы данные не добавлять. Только стандартное поведение по умолчанию, включающее в себя другое поведение, которое требуется воплотить в конкретных реализациях («паттерн Template Method»).


            1. 0xd34df00d
              17.05.2019 21:20

              Ad-hoc и параметрический полиморфизм — это вообще особенности resolve'инга, то есть синтаксический сахар (про них к примеру большинство языков в run-time забывают, если не считать reflection'а)

              Параметрический? Резолвинга? Резолвинга чего, в таком случае?


            1. EvgeniiR
              19.05.2019 12:00

              В википедии вроде так:

              А ещё в википедии так:
              ООП имеет уже более чем сорокалетнюю историю, но, несмотря на это, до сих пор не существует чёткого общепринятого определения данной технологии[14]. Основные принципы, заложенные в первые объектные языки и системы, подверглись существенному изменению (или искажению) и дополнению при многочисленных реализациях последующего времени. Кроме того, примерно с середины 1980-х годов термин «объектно-ориентированный» стал модным, в результате с ним произошло то же самое, что несколько раньше с термином «структурный» (ставшим модным после распространения технологии структурного программирования) — его стали искусственно «прикреплять» к любым новым разработкам, чтобы обеспечить им привлекательность. Бьёрн Страуструп в 1988 году писал, что обоснование «объектной ориентированности» чего-либо, в большинстве случаев, сводится к некорректному силлогизму: «X — это хорошо. Объектная ориентированность — это хорошо. Следовательно, X является объектно-ориентированным».


    1. 0xd34df00d
      17.05.2019 21:18

      Полиморфизм бывает разный. Есть subtyping polymorphism, как в ООП, а есть parametric polymorphism, как в ФП. Второе ИМХО приятнее и как-то, ну, не знаю, поддерживаемее.


  1. Fenzales
    17.05.2019 10:33

    Пример: когда class Player ударяет при помощи метода hits() class Monster, где на самом деле нужно изменять данные? Величина hp объекта Monster должна уменьшиться на attackPower объекта Player; величина xp объекта Player должна увеличиться на уровень Monster в случае убийства Monster. Должно ли это происходить в Player.hits(Monster m) или в Monster.isHitBy(Player p)? Что если здесь нужно учитывать и class Weapon? Мы передаём аргумент в isHitBy или у Player есть геттер currentWeapon()?
    В таких случаях пишется ECS. Автор статьи точно имеет достаточные компетенции, чтобы его переводить и читать?


    1. SadOcean
      17.05.2019 11:46

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


  1. Pafnutyi
    17.05.2019 10:57

    Общая идея придумок в программировании это управление сложностью. Конечно ООП с этим справляется лучше процедурщины, но ООП ниразу нелогична для программиста. Что и вызывает постоянные споры об устройстве объектной модели «кони ездят на людях или наоборот» ) Функциональщина вроде вот неплохой способ упростить код программы, но пока не устареют туториалы по ООП, не «помрут» все адепты ООП которые от лени не хотят учить новое ничего не изменится. После функциональщины придёт ещё чтото новое, но кому это нужно пока «получку платят» за «и так сойдёт» ))) Вопрос не в ООПе, а в том почему существенного развития в создании программ нет! Даже древний Делфи инновационнее был чем все современные котлины-мотлины/скалы-шмалы вместе взятые — его делали для людей )))


    1. NitroJunkie
      17.05.2019 11:04

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


      1. mayorovp
        17.05.2019 11:11

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


        1. NitroJunkie
          17.05.2019 11:54

          Ну я еще использую чистые функции (immutability это вообще одна из самых важных концепций при построении сложных систем), map reduce'ы. И это все никак не связано с наследованием / полиморфизмом.

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


          1. 0xd34df00d
            17.05.2019 21:23

            И это все никак не связано с наследованием / полиморфизмом.

            Зато тайпклассы, экзистенциальные типы, rank-N polymorphism и всё такое связаны.


      1. defaultvoice
        17.05.2019 11:43

        Мне тоже всегда интересно, вон в OCaml с JS они прекрасно живут вместе и никому не мешают. А если брать изначальную концепцию Кея с сообщениями и этим вот всем, то Erlang вполне Ъ ООП язык


      1. Flammar
        17.05.2019 15:02

        Есть всеобщая тенденция реализовывать функциональщину через ООП. Т.е.чтоб передать блок кода в функцию, мы вставляем этот блок кода в заранее определённый метод класса-потомка, и потом передаём экземпляр (фактически синглетон, ибо данных там нет, один код) этого класса в функцию, которая требует экземпляр класса-предка.


    1. Flammar
      17.05.2019 15:09

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

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


      1. megahertz
        18.05.2019 02:51

        Почти так-же как и в других языках. Разве что есть свои особенности, поскольку основная причина введения интерфейсов была работа с COM.


    1. mayorovp
      17.05.2019 15:17

      Даже древний Делфи инновационнее был чем все современные котлины-мотлины/скалы-шмалы вместе взятые — его делали для людей

      Простите, но что в древнем Делфи было такого инновационного, если сравнивать с современными языками?


    1. kpakozz96pyc
      17.05.2019 16:01

      Чет жирно. Самое забавное, что на одной из самых первых лекций по программированию от Мейлру расписывают плюсы и минусы и того и другого. И у ООП там один из минусов — сложность в освоении по сравнению с ФП. Так при чем здесь тогда лень?
      Не понимаю почему нельзя использовать и то и другое — что мешает?


  1. denisshabr
    17.05.2019 11:13
    +2

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

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


    1. arturpanteleev
      17.05.2019 12:21
      +1

      А где в пхп «забили» на обратную совместимость?
      Там наоборот много костылей до сих пор тянется, как раз из за этой самой совместимости, отсутствие которой сделало бы язык гораздо логичнее и удобнее.


      1. vlreshet
        17.05.2019 13:56

        Запустите код написанный под 5.6 на 7.2 без изменений. Костылей в PHP, конечно, дофига тянется, но обратная совместимость частенько ломается. Вот буквально через неделю собираемся у себя на проекте мигрировать на php 7.3 вместо 7.0 — а несколько страниц не взлетают, переписывать надо.


        1. Sartor
          17.05.2019 20:41

          Если вы так редко будете это делать — вам вообще тяжко будет. Код надо заранее готовить к новой версии и не пропускать ни в коем случае, тогда миграция будет занимать время одного прогона тестов.


          1. khim
            18.05.2019 03:32
            +2

            Так редко? Ды вы тут долбанулись все. Я переживаю, мне в программе на C++ из SPEC 2000 нужно пару строк изменить, чтобы оно на C++17 заработало, а тут мне говорят «код надо заранее готовить к новой версии и не пропускать ни в коем случае»… это же пипец какой-то.


            1. Sartor
              18.05.2019 18:39

              5.6 релизнулась в середине 2014 года. Да это реально редко. Если вы подумали, что нужно много править и кучу всего рефакторить — то это не так. Мой проект, которому 6 лет на каждую новую версию мигрировал в течении месяца после выхода новой мажорной версии. Иногда не приходилось править вообще ни одной строки. А если вы реально «переживаете править две строки» — то что-то явно не так в вашем проекте


              1. khim
                18.05.2019 19:04

                А если вы реально «переживаете править две строки» — то что-то явно не так в вашем проекте
                В нашем проекте всё так. Но SPEC 2000 — это тест скорости. Эталон. Он не должен менять вообще, никак.

                И те две строки, что пришлось менять — это то место, где разработчики нарушили спеки, описанные в документации. Но всё равно — изменение этих мест значит, что мы уже не можем на 100% доверять тем числам, что получали ранее. Так как теперь мы имеем уже другую программу.

                А вы хотите заставить нас что-то менять при выходе каждой минорной версии. Качмар.


    1. KvanTTT
      17.05.2019 13:56

      Отсюда забивание на обратную совместимость (python, php), ради эфимерной «красоты языка» (психам же насрать на совместимость, главное внутренняя красота и современные технологии).

      Довольно спорное утверждение. C++ хорош с его обратной совместимостью?


      1. khim
        18.05.2019 03:33

        C++ хорош с его обратной совместимостью?
        Для своей ниши? Да, несомненно. Ничего лучшего до сих пор нет. Rust пытается изобразить замену — но пока с очень-очень переменным успехом.


  1. evgenyk
    17.05.2019 11:18

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


  1. psFitz
    17.05.2019 11:24
    +2

    Статья называется "не осилил ООП — поэтому ООП говно"


    1. Bedal
      17.05.2019 17:09

      "… или как правильно писать фортрановские программы на любом языке"


  1. Ryppka
    17.05.2019 11:38

    И почему никто в спорах не упомянул multiple dispatch?


    1. khim
      18.05.2019 03:38
      -1

      Потому что при всей «модности стильности молодежности» в большинстве языков, которые знают тут обсуждающие, его нет?


  1. sys_int64
    17.05.2019 11:42

    ООП ни плохо ни хорошо, надо уметь его правильно готовить, практика показывает, что у многих с этим очень плохо и ООП в данном случае может только навредить. Лично я для себя делаю так — для низкоуровнего АПИ очень хорошо ложится Data Oriented Design (тут как бонус еще и скорость), для высокоуровнего АПИ очень хорошо ложится ООП.
    А вот еще и не плохая статья как готовить ООП: OOP is dead, long live OOP


  1. smart_alex
    17.05.2019 11:42

    Вообще, ООП, как концепция не просто хорош, а великолепен и во многих случаях прекрасно подходит для решения задач. Другое дело, что во всём нужно знать меру и не «тупо» везде использовать ООП, а подходить творчески к решению задач и использовать те подходы к построению программы, которые наилучшим образом подходят в каждом конкретном случае. Благо сама природа программирования оставляет широкие возможности для творческого подхода.


  1. Tzimie
    17.05.2019 11:42
    -2

    Когда объекты «большие» это помогает. Но когда адепты ООП настругают сотни коротких искусственно придуманых классов, не имеющих соответствия в реальном мире, это ужасно.

    Да, еще эти фабрики фабрик фабрик фабрик.


    1. VIkrom
      17.05.2019 12:03
      +2

      Когда объекты «большие» это помогает. Но когда адепты ООП настругают сотни коротких искусственно придуманых классов, не имеющих соответствия в реальном мире, это ужасно.

      Все как раз наоборот. Если слышите, что ООП нужно для моделирования реального мира, значит человек плохо понимает ООП. Можно посмотреть Роберта Мартина на эту тему, например.


      1. Belikov
        18.05.2019 13:49
        -2

        Изначально же ООП так и задумывался, разве нет? Потом оказалось что мало кто умеет нормально моделировать реальный мир и все ищут готовые паттерны, чтобы под копирку кучу фабрик фабрик наделать где надо и где не надо. Тогда стали популярны паттерны. А еще немного позже появилось DDD которое пытается вернуть все к истокам и использовать ООП так как оно задумывалось — для описания модели предметной области.


        1. Arlekcangp
          19.05.2019 08:06

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


          1. EvgeniiR
            19.05.2019 12:02

            Изначально же ООП так и задумывался, разве нет? Потом оказалось что мало кто умеет нормально моделировать реальный мир и все ищут готовые паттерны, чтобы под копирку кучу фабрик фабрик наделать где надо и где не надо. Тогда стали популярны паттерны.

            Нет, не задумывался он так.
            Алан Кей про историю создания ООП


  1. panvartan
    17.05.2019 12:08

    Зачем искать код в данных, когда можно придумать свои данные-объекты и код начнет производить правильные, с точки зрения кода, данные. Большинство разработчиков, со временем, начинают использовать похожую систему объектов, и их системы начинают производить данные, которыми уже можно обмениваться. ООП не читатель, ООП — писатель. И большинству это нравится. А если ваши данные не подходят — ООП идет к вам.


    1. sys_int64
      17.05.2019 12:15

      В общем-то никто не мешает тебе также делать и в Data Oriented Design, только вместо классов тут структуры, чаще всего иммутабельные.


  1. oracle_and_delphi
    17.05.2019 12:57
    +1

    image
    Шизофреническая инкапсуляция объектов

    Кто мне пояснит, чем функция, которая вызывает функцию, которая вызывает функцию, которая вызывает функцию, которая вызывает функцию… принципиально отличается от глубокой инкапсуляции?
    image
    Не троллю, серьёзно спрашиваю!


    1. Ztare
      17.05.2019 17:32
      -2

      По сути ООП популярных языков чуть ровнее ложится на железо и проще в осознании человеком. А так тоже самое.


      1. f0rk
        17.05.2019 18:23
        +4

        В каком месте ООП ложится ровно на железо? Это же граф из каких-то хреновин выделенных на хипе, все кэш-лайны идут мимо.


        1. alsii
          17.05.2019 18:53
          +1

          Ну положим ФП c хипом еще жестче обращается. Насколько я понимаю, там самые частые операции: выделение/освобождение памяти для иммутабельных данных. Тут про кэш вообще можно забыть, как мне кажется… Или я не прав?


          1. Cerberuser
            17.05.2019 19:31

            Операции над иммутабельными данными, по идее, можно очень сильно оптимизировать, фактически исключая большую часть промежуточных шагов (и, соответственно, большую часть выделений/освобождений). В конце концов, иммутабельны они по семантике языка, а не по семантике итогового машинного кода. Хотя я не специалист и утверждать однозначно не могу, но попытки разобраться, к примеру, в Rust-овых итераторах (которые, насколько я понимаю, на ФПшную концепцию иммутабельности ложатся отлично за счёт смены владельца данных) подсказывают именно что-то в таком духе.


          1. 0xd34df00d
            17.05.2019 21:28

            Насколько я понимаю, там самые частые операции: выделение/освобождение памяти для иммутабельных данных. Тут про кэш вообще можно забыть, как мне кажется… Или я не прав?


            Во-первых, не зря gen0 в GC делают размером с L1 или L2 cache, оно почти как стек по производительности становится, если у вас много короткоживущего мусора.

            Во-вторых, действительно, компилятору легче доказывать, что старая версия объекта не нужна, и проводить все изменения без всяких копирований. Особенно с линейными типами. Я не так давно уже где-то цитировал, процитирую ещё раз мануал:
            umap : (a -> b) -> UList a -> UList b
            umap f [] = []
            umap f (x :: xs) = f x :: umap f xs
            

            In the second clause, xs is a value of a unique type, and only appears once on the right hand side, so this clause is valid. Not only that, since we know there can be no other reference to the UList a argument, we can reuse its space for building the result! The compiler is aware of this, and compiles this definition to an in-place update of the list.


    1. S-e-n
      17.05.2019 17:55

      Не уверен, что понял вопрос, но попробую ответить. В примере, который вы привели, границы (разбиение кода на части) проводятся по логике, а не по данным.

      Инкапсуляция требует связи данных с логикой, поэтому в случаях, когда взаимодействуют сразу несколько объектов логика может быть ГОРАЗДО более раздробленной, а структура программы гораздо более сложной.


  1. alexwillrock
    17.05.2019 13:04
    +2

    ну во-первых, С++ это не ООП, и сам Страуструп об этом писал в своей книжке. Что классы в плюсах были созданы для более удобного структурирования кода. А в статье снова говорится не о минусах самого ООП, а о «как же плохо писать на ООП языке в процедурном стиле», и вместо решения проблемы или даже зачатков поиска ответа на поставленные вопросы, автор говорит «но я все равно буду писать на еще более примитивном процедурном уровне и создавать структуры данных».
    Структура данных != Класс. Работа через структуры != моделирование объектного мира. Использование сеттеров, геттеров, опционалов замыканий генериков — еще больше отодвигает от настоящего ООП, который задумывался в бородатые годы, усугубляет текущие проблемы, делает код сложнее и запутаннее. Достаточно посмотреть на метрики и быстродействие софта.


    1. Ryppka
      17.05.2019 13:37

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


      1. alexwillrock
        17.05.2019 14:20
        -1

        Например, используя некое замыкание completion() === (void -> ()), реализуешь по сути переход GO TO к метке его создания, при этом практически анонимно, что полностью процедурно и вообще плохой тон.


    1. megasuperlexa
      17.05.2019 16:31

      почему вы противопоставляете ООП и процедурный стиль? в ООП принято пользоваться goto?


      1. Ryppka
        17.05.2019 16:43

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


        1. megasuperlexa
          17.05.2019 16:52

          да, и при этом в каждой книжке по этим языкам пишется «не используйте его». Интересовались, почему?


          1. xotey83
            17.05.2019 17:05

            Простите, но в какой именно книжке пишут «не используйте его»?

            Боюсь, что практика «категорически не использовать goto» есть только в структурном программировании, основным идеологом коей является Вирт и его последователи.
            Остальные люди высказываются более осторожно: говорят, что без понимания «для чего» этот оператор использовать не стоит.

            А в остальном, это вполне нормальный оператор, который иногда помогает упростить код или добавить микрооптимизаций.

            З.Ы. за всю мою карьеру, мне этот оператор понадобился лишь 3 раза.


          1. Ryppka
            17.05.2019 17:10

            ИМХО, в тех книжках, которые стоят прочтения, не пишется «не используйте goto». В них пишется когда его использовать нужно, когда можно и когда не нужно.
            Кроме того, Вы и исключения/перехватываемые паники осуждаете?


            1. megasuperlexa
              17.05.2019 17:33
              -2

              да, конечно. Исключения это очень плохо, и мало чем от goto отличается. По сути это вариант результата выполнения функции, который вам неизвестен без того, чтобы заглянуть внутрь имплементации (причём по цепочке во все вызываемые методы). Либо нужно запускать код и узнавать об этом (возможном) результате в рантайме.
              Есть конечно checked эксепшены в Java, но кажется народ уже понял насколько это ужасно с ними работать ) но попытка хорошая


              1. Cerberuser
                17.05.2019 17:43

                А типы-суммы типа Result в Rust или Maybe/Option в функциональных языках?


                1. 0xd34df00d
                  17.05.2019 21:30

                  Прекрасны.


                  Особенно если абстрагироваться поверх конкретной стратегии обработки ошибок и работать в произвольной монаде (возможно, с констрейнтом MonadError).


              1. Ryppka
                17.05.2019 22:00

                Тем не менее, без исключений или паник не обойтись, особенно в языках с конструкторами и перегрузкой операторов. Да и просто нелокальная обработка ошибок имеет право на существование, хотя и необязательно реализуется через исключения.
                Даже перехват паник (те хе исключения, вид сбоку) необходим в определенных ситуациях и присутствует и в Go, и в Rust.
                А вот неразумное, повсеместное, ничем не ограниченное и синтаксически «удобное» злоупотребление исключениями — беда, хот checked, хоть unchecked.


  1. SergeyUstinov
    17.05.2019 13:26

    Если рассматривать те программы, которые уже существуют — минимум 70-80% программ, относящихся к «учетным» (активно использующим РСУБД), написаны отвратно. Хотя, подозреваю, проблема не только в ООП.
    То есть данные все равно хранятся в РСУБД, так как ничего лучше для таких задач на текущий момент не придумано, но работают с базой крайне извращенным способом. В результате дикие тормоза и проблемы.


    1. v2kxyz
      17.05.2019 22:11

      Мне кажется 70%-80% учетных программ написано вообще не в ООП стиле, либо в том, что Вы или я или кто-либо еще подразумеваем под ООП.
      Например SAP ERP — ну нет там ООП, хотя сейчас появляется нечто использующее классы.


      1. SergeyUstinov
        19.05.2019 08:51

        Это и наводит на мысль, что источник проблем — не ООП.
        Вернее не только ООП.


    1. symbix
      18.05.2019 00:34
      +1

      Если в программах используется ключевое слово class — это не значит, что там ООП.


  1. picul
    17.05.2019 13:39
    -1

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


    1. tuxi
      17.05.2019 15:20

      Да вся суть этого хайпа сводится к «нафига нам эти абстракции, у нас есть стримы-потоки данных, так давайте просто их обрабатывать!», при этом совсем забывая, как и кем эти потоки формализуются и создаются.


    1. PsyHaSTe
      17.05.2019 16:09

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


      1. mayorovp
        17.05.2019 17:43

        А при чём тут тайпклассы?


        1. 0xd34df00d
          17.05.2019 21:33

          Полиморфненько становится.


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


        1. PsyHaSTe
          18.05.2019 20:05

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


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


          1. ddinochrome
            18.05.2019 20:18

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


            1. PsyHaSTe
              18.05.2019 20:49

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


              1. Druu
                18.05.2019 22:01

                Ну вот, собственно тайпклассы это и возможность создать несколько иерархий.

                Только у тайпклассов нет иерархии.


                1. ApeCoder
                  18.05.2019 22:33
                  +4

                  Это нельзя назвать иерархией?
                  image


                  1. Druu
                    19.05.2019 10:40
                    -1

                    Это можно назвать иерархией. Но только в таком случае у слова "иерархия" будет не то значение, как у слова "иерархия" применительно к ООП.
                    В случае ООП, если что-то MonadPlus — оно автоматически становится Monad. В случае тайпклассов — если что-то не Monad, то оно не может стать MonadPlus.


                    1. mayorovp
                      19.05.2019 10:59

                      Но это одно же и то же.


                      1. Druu
                        19.05.2019 12:34

                        В чем одно и то же? Это принципиально разные вещи, которые совершенно по-разному работают. В хаскеле что-то, являющееся MonadPlus, не станет Monad, вы просто не сможете MonadPlus написать. То есть "иерархия" тайпклакссов обращена в противоположную сторону.


                        1. mayorovp
                          19.05.2019 12:53

                          А ООП вы не сможете объявить свой класс как реализующий MonadPlus пока не напишете все методы требуемые интерфейсом Monad.


                          Итоговая разница — в одно объявление.


                          1. Druu
                            19.05.2019 13:08
                            -1

                            требуемые интерфейсом Monad

                            При чем тут интерфейс, мы же про иерархии? Интерфейсы не наследуются, с-но и в иерархию не входят по определению, это контракты, как и тайпклассы. Мы же про множества значений — то есть про классы (реализации интерфейсов) и инстансы (реализации тайпклассов). Чтобы сделать Foo < Bar вам достаточно сказать: class Foo extends Bar {};
                            В случае хаскеля вам надо сперва сделать инстанс Bar для типа и только потом можно будет для него объявить инстанс Foo. Более того, вы можете сделать инстанс Foo для любого Bar и тогда с точки зрения ООП Foo < Bar, а с точки зрения хаскеля Bar < Foo.


                            1. mayorovp
                              19.05.2019 13:43

                              Тайпкласс в ФП соответствует интерфейсу в ООП.


                              1. Druu
                                19.05.2019 13:54

                                Тайпкласс в ФП соответствует интерфейсу в ООП.

                                Я же это и сказал: "Интерфейсы не наследуются, с-но и в иерархию не входят по определению, это контракты, как и тайпклассы".


                                Мы говорим не об интерфейсах и тайпклассах (они не входят ни в какие иерархии), а о классах и инстансах тайпклассов (реализациях интерфейсов). Они как раз иерархию и образуют.


                                1. mayorovp
                                  19.05.2019 14:08

                                  Интерфейсы, вообще-то, могут наследовать друг друга, образуя свою иерархию.


                                  1. Druu
                                    19.05.2019 15:15
                                    -1

                                    Интерфейсы, вообще-то, могут наследовать друг друга, образуя свою иерархию.

                                    Это вы к чему? Как-то раскрывайте мысль, а то непонятно из полунамеков.


                              1. 0xd34df00d
                                19.05.2019 21:28

                                Только там не изоморфизм, а сюръекция (тайпклассы мощнее, если смотреть на multiparam type classes, fundeps и вот это всё).


                                1. mayorovp
                                  19.05.2019 21:38

                                  Ну да, тайпклассы мощнее. Тут умных слов не надо, достаточно на ICloneable посмотреть :-)


                                  Но они с интерфейсами решают одну и ту же задачу.


                              1. VolCh
                                20.05.2019 14:15

                                А давно в ООП интерфейсы завезли? :)


                                1. PsyHaSTe
                                  20.05.2019 15:04

                                  Давно


                                  1. VolCh
                                    20.05.2019 15:07

                                    Я вот как-то не встречал в описаниях ООП интерфейсов. Самый близкий к ним термин "абстракция".


                                    1. PsyHaSTe
                                      20.05.2019 15:21

                                      Чисто виртуальный класс, если вам ближе С++ терминология. А на рынке ООП интерфейс как термин уже лет 20 сидит.


                                      1. VolCh
                                        20.05.2019 15:26

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


                        1. 0xd34df00d
                          19.05.2019 21:29
                          +1

                          Оно в другую сторону работает — если у меня есть констрейнт MonadPlus m =>, то я могу использовать все методы Monad.


          1. EvgeniiR
            18.05.2019 20:40
            +1

            > Одна из очень плохих проблем ООП — иерархии.

            А в каком месте ООП диктует какие должны быть иерархии объектов, и кто мешает используя ООП не реализовать в интерфейсе кучу «мусорных», как вы говорите, методов?


            1. PsyHaSTe
              18.05.2019 20:48

              Ну вот у вас есть B который наследуется от A. А через год вы поняли, что плохо, что он так наследуется, но сделать ничего нельзя, потому что у вас миллионы клиентов, и обратную совместимость надо оставлять. Что делать в такой ситуации? Вариант "надо было думать раньше" только не надо предлагать, это не решение.


              1. Druu
                18.05.2019 22:10

                Ну вот у вас есть B который наследуется от A. А через год вы поняли, что плохо, что он так наследуется, но сделать ничего нельзя

                А с тайпклассами-то в чем разница? Типа была у нас реализация тайпкласса для типа Х, а потом поняли, что это плохо, не должно быть ее. И что, как удалить-то, не нарушая обратную совместимость?


                1. PsyHaSTe
                  18.05.2019 23:24

                  С тайплкассами делаем тайпкласс АА и тайпкласс ВВ. Реализуем их как надо. На А и В вешаем "деприкейтед", и говорим использовать только АА и ВВ. Профит.


                  1. Druu
                    19.05.2019 10:49
                    +2

                    Не понял. У нас была проблема — есть реализация тайпкласса, которую надо удалить. Как ее удалить, не нарушая обратную совместимость?


                    А описанное выше можно и с классами сделать — делаем класс АА и класс BB, A и B депрекейтим, и говорим использовать только AA и BB. С-но, на практике так и делают.


                    1. PsyHaSTe
                      19.05.2019 11:33
                      -1

                      Удалять не надо, надо сделать параллельную иерархию. Ну вот например, я знаю, как надо спроектировать правильно эти коллекции. С тайпклассами я сделаю своих тайпклассов и реализую для стандартной библиотеки, и все будет работать. В случае ООП мне нужно влезть в исходники этих самых List/Array/..., чего я сделать конечно же не могу.


                      1. ApeCoder
                        19.05.2019 11:44
                        +1

                        По вашему, это свойство ООП как такового, или скорее популярных языков? В каком-нибудь self или vba нет вообще никаких классов. Может быть можно придумать (или уже придумали) язык, который


                        1. Является OO
                        2. Поддерживает параллельную независимую классификацию тех же объектов.


                      1. Druu
                        19.05.2019 12:40

                        В случае ООП мне нужно влезть в исходники этих самых List/Array/...

                        Зачем? Просто делаешь параллельную иерархию и ее используешь, как в хаскеле. Проблема-то в какой момент возникает?


                        1. 0xd34df00d
                          19.05.2019 21:31

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


                          1. Druu
                            20.05.2019 11:14
                            +1

                            Вы про то что нельзя добавить к типу реализацию дополнительного интерфейса? Ну это не свойство ООП, это свойство конкретных реализаций.


                            1. PsyHaSTe
                              20.05.2019 12:08

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


                              1. Druu
                                20.05.2019 13:22

                                Ну в тс можно через prototype-костыль. В шарпе "почти" можно (если бы можно было группировать extension методы в интерфейсы)


                                1. mayorovp
                                  20.05.2019 13:26

                                  Уточнение: с версии 1.8
                                  И основное тут не prototype-костыль (это как раз фича javascript, которая там была изначально), а declare module


                                  1. Druu
                                    20.05.2019 14:10

                                    И основное тут не prototype-костыль (это как раз фича javascript, которая там была изначально)

                                    prototype-костыль ЕМНИП до каких-то пор не типизировался нормально, хотя может и путаю что-то


                                    1. mayorovp
                                      20.05.2019 15:48

                                      Так я про него и говорю же, он до 1.8 не типизировался, пока declare module в языке не появился.


                                1. PsyHaSTe
                                  20.05.2019 13:42

                                  Энтерпрайз на ТС не пишут. На ТС пишут обычно представление, это вообще не в ту степь.


                                  Extension методы это просто немного сахара, толку с них никакого, ибо на них нельзя обобщаться.


                                  Что-то похожее предлагают в пропозале, и само его существование намекает на то, что я прав.


                                  1. Druu
                                    20.05.2019 14:05
                                    +1

                                    Энтерпрайз на ТС не пишут. На ТС пишут обычно представление, это вообще не в ту степь.

                                    Какая разница, кто что на чем пишет или нет? Вот язык с ООП, где можно. И не какая-то эзотерика, а вполне себе мейнстрим.


                                    Что-то похожее предлагают в пропозале, и само его существование намекает на то, что я прав.

                                    Как раз наоборот, пропозал намекает на то, что невозможность описанного расширения существующего класса ортогональна ООП.
                                    Так что надо говорить не о том что "в ооп так нельзя", а о том что "в джаве так нельзя" или "в шарпе так пока нельзя" и т.д..


                                    1. PsyHaSTe
                                      20.05.2019 14:08
                                      -1

                                      Какая разница, кто что на чем пишет или нет? Вот язык с ООП, где можно. И не какая-то эзотерика, а вполне себе мейнстрим.

                                      Потому что это не сложная бизнес-логика, а просто валидация ввода в большинстве случаев. Когда я писал десктопное приложение на шарпах, там точно так же на 10к строк логик было 30к строк валидации.


                                      Для валидации и бизнес-правил несколько различаются требования к расширяемости и пр.


                                      Как раз наоборот, пропозал намекает на то, что невозможность описанного расширения существующего класса ортогональна ООП.

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


                                      1. Druu
                                        20.05.2019 14:16
                                        +2

                                        Потому что это не сложная бизнес-логика, а просто валидация ввода в большинстве случаев.

                                        Не понял при чем тут это. Хочешь — пиши бизнес-логику на тс, тебе не мешает никто.


                                        ООП == как в мейнстрим языках.

                                        тс в 2019 — вполне мейнстрим язык.


                                        Вот когда в джаве появится, тогда и значит что можно. Если в джаве нет, значит нельзя.

                                        Ну так и говори, что "в джаве нельзя", при чем тут все ООП.


                                        1. PsyHaSTe
                                          20.05.2019 15:04
                                          -3

                                          ООП это джава.


          1. mayorovp
            18.05.2019 21:02

            С появлением default methods не будет никаких проблем поправить иерархию и в .net.


            1. PsyHaSTe
              18.05.2019 23:25

              Как вы поправите? Вот нужно из ICollection выпилить IsResizable или перестать наследовать массив от IList. Как это сделать?


              1. mayorovp
                19.05.2019 10:22

                Точно так же как и с тайпклассами:


                для всех стандартных классов сделать параллельную адекватную иерархию, а эту задеприкейтить


                1. PsyHaSTe
                  19.05.2019 10:33
                  -1

                  Вы не можете так в ООП, потому что вы буквально пиште в ООП class B: IFoo, IBar, A.


                  1. mayorovp
                    19.05.2019 11:01
                    -1

                    И что? Это как-то мешает добавить в этот список ещё один интерфейс? Или задеприкейтить класс B и написать новый?


                    1. PsyHaSTe
                      19.05.2019 11:34

                      Потому что исходник класса стандартной (или любой другой) библиотеки вы менять не можете.


                      1. mayorovp
                        19.05.2019 11:40

                        Простите, но как вы собрались в таком случае деприкейтить стандартный класс?


                        1. PsyHaSTe
                          19.05.2019 12:11

                          В некоторых ЯП атрибуты можно вешать снаружи, хотя бы для IDE.


                          1. mayorovp
                            19.05.2019 21:39

                            От того, что вы повесили на класс атрибут, его не перестали использовать сторонние библиотеки.


                            1. PsyHaSTe
                              19.05.2019 22:29

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


                              1. mayorovp
                                20.05.2019 00:18

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


                        1. 0xd34df00d
                          19.05.2019 21:30

                          Писать новые инстансы в точке объявления нового тайпкласса.


                          1. PsyHaSTe
                            19.05.2019 22:29

                            Заворачивать все в ньютайпы замучаетесь.


                            1. 0xd34df00d
                              19.05.2019 22:48

                              Не замучаюсь. Если я пишу объявление инстанса в том же модуле, что и объявление тайпкласса, то вообще ничего писать не надо, никаких ньютайпов.


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


          1. 0xd34df00d
            19.05.2019 00:41

            Э, ну не совсем. Applicative/monad proposal, например, поломал немного кода, равно как и чуть более недавнее добавление Semigroup как суперкласс Monoid.


            Другое дело, что это всё ловится тайпчекером, а исправление практически механическое (я даже расстроился, что никто тулзу соответствующую не сделал).


    1. EvgeniiR
      17.05.2019 16:31

      люди прочитают определение термина «ООП» и поймут, что это не про понты с абстракциями, а про объединение данных и методов их обработки

      Алан Кей такого не говорил. Если вы считаете что есть какое-то однозначно верное определение, так поведайте его уже миру, наконец, и распишите конкретно в чем был не прав Алан.

      p.s.
      Но что такое ОО?
      Один из возможных ответов на этот вопрос: «комбинация данных и функций». Однако, несмотря на частое цитирование, этот ответ нельзя признать
      точным, потому что он предполагает, что o.f() — это нечто отличное от f(o).
      Это абсурд. Программисты передавали структуры в функции задолго до
      1966 года, когда Даль и Нюгор перенесли кадр стека функции в динамическую память и изобрели ОО.
      © Чистая Архитектура


  1. KvanTTT
    17.05.2019 13:58

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


  1. sbnur
    17.05.2019 14:49
    +1

    Вопрос — автор знаком со SmallTalk?


    1. BD9
      18.05.2019 00:44

      Автору это незачем.


      1. EvgeniiR
        18.05.2019 10:40

        Пригодилось бы, чтобы хоть немного разобраться в теме о которой вещает..


  1. tuxi
    17.05.2019 15:16

    Есть очень простой пример
    «Давайте вернемся в конец 90-х и попробуем без ООП написать все те программы и системы, которые существуют и развиваются до сих пор»
    А так то да, очень удобно, с позиции уже существующих экосистем и решений, начать призывать отказаться от ООП, как от какой-то ереси.


    1. zorn_v
      17.05.2019 16:25

      > А так то да, очень удобно, с позиции уже существующих экосистем и решений

      Осталось только переучить ту гору разработчиков на «правильный стиль» )
      Вобщем кликбейт и пук в лужу.


    1. EvgeniiR
      17.05.2019 16:34

      В 90-х парадигма ООП уже существовала, причем долгое время.

      А так то да, очень удобно, с позиции уже существующих экосистем и решений, начать призывать отказаться от ООП, как от какой-то ереси.

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


      1. tuxi
        17.05.2019 16:45

        Существовала, но «цифровизация» бизнеса массово началась все же в 90-х годах. Я про формализацию бизнес-задач и процессов. И все это отлично реализовывалось на принципах ООП и как раз и дало толчек к обработке и управлению данными на ЭВМ. Причем не только в «очень большом бизнесе»/обороне/науке (там и так это уже было), но и в большом и малом бизнесе. Без ООП, рынок разработки весьма чахло бы развивался.


      1. zorn_v
        17.05.2019 16:52

        Чем же ООП так устарело? Не подходит для фронта и дата майнинга?

        Когда в руках молоток, все вокруг кажется гвоздями, да?


        1. EvgeniiR
          17.05.2019 17:49

          Я ведь написал, «Если оторваться от споров про ООП».


  1. zorn_v
    17.05.2019 16:20
    +1

    > Первым делом я изучаю данные. Анализирую, что поступает на вход и на выходы, формат данных, их объём. Разбираюсь, как данные должны храниться во время выполнения и как они сохраняются: какие операции должны поддерживаться и с какой скоростью (скорость обработки, задержки) и т.д.

    Вобщем, мне не подошел ООП, поэтому БЫСТРА переставайте его использовать )


  1. saboteur_kiev
    17.05.2019 16:37
    +2

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


    То есть мотивирует игнорировать архитектуру данных?
    Наоборот — объект это и есть архитектура данных. Просто в тот же самый объект помещаются и методы работы с данными.

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

    То же слепое следование паттернам приводит к той же ошибке.


  1. zorn_v
    17.05.2019 17:04

    Пацану не подошел ООП в ЕГО задаче. ООП плохой. Весь смысл статьи


  1. rfq
    17.05.2019 17:18

    «Сами данные будут содержаться в виде структур ADT/PoD, а любые ссылки между записями данных будут представлены в виде ID»
    Ну вот зачем мне ссылка в виде ID, если можно в виде типизированной переменной? Только чтобы отложить обнаружение ошибки со времени компиляции на время исполнения. Это же так круто, слышать вопли пользователя — ааа, у меня ClassCastException!


  1. cross_join
    17.05.2019 17:31

    Автор работает в data driven подходе, характерном для транзакционных бизнес-приложений, тогда как ООП выросло из двух других: имитационное моделирование и графические интерфейсы, где оно и показывает наибольшие преимущества.


  1. evocatus
    17.05.2019 19:00

    в комментариях ад — многоветочные дискуссии о том сколько ангелов можно инкапсулировать, куда и как

    посмотрите это видео:
    youtu.be/Tb823aqgX_0


  1. vt4a2h
    17.05.2019 20:45

    ООП — не серебряная пуля разумеется, но и не абсолютное зло. Если применять этот подход по месту, то польза очевидна. Аргументов против этого очевидного утверждения, я в статья не увидел.

    Однако же, вот этот пример интересен:

    Пример: когда class Player ударяет при помощи метода hits() class Monster, где на самом деле нужно изменять данные? Величина hp объекта Monster должна уменьшиться на attackPower объекта Player; величина xp объекта Player должна увеличиться на уровень Monster в случае убийства Monster. Должно ли это происходить в Player.hits(Monster m) или в Monster.isHitBy(Player p)? Что если здесь нужно учитывать и class Weapon? Мы передаём аргумент в isHitBy или у Player есть геттер currentWeapon()?

    Тут явно надо больше классов :) Как минимум сама операция атаки должна описываться отдельным классом. А ведь есть ещё и экипировка, и различные стратегии взаимодействия, в зависимости от экипировки.
    Я к тому, что если всё более-менее адекватно спроектировать, применяя, допустим, DDD, то это не так уж и страшно. Хотел бы я посмотреть на подобную модель, которая будет реализована с другими подходами. В частности на её поддержку и расширение.


  1. BreathDeeper
    17.05.2019 22:11

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

    P.S. Я знаю что это перевод, просто надеюсь что комент оттолкнет кого нибудь от написания hate-driven статей


  1. VS_Revan
    17.05.2019 22:11

    Monster&Player — элементарно!
    Опуская менее важное:

    -Player : Actor -
    Player::Fire() {
        _weapon->Fire(this);
    }
    
    Weapon::Fire(Actor aOwner) {
        HitInfo* hit;
        if(Physics::Raycast(aOwner.transfrom.position, aOwner.transfrom.forward, _fireDist, Globals::c_affectableLayer, hit)) {
            IAffectable* affectable = hit->hitCollider->GetComponent(IAffectable);
            if(affectable) {
                affectable->takeEffect(_damageEffect, aOwner);
            }
        }
    }
    
    - Monster : Actor -
    Monster::takeEffect(IEffect* aEffect, Actor aEffectOwner) {
        ActorValue targetValue = _values[aEffect->targetValue];
        aEffect->OnApply(aEffectOwner, this, targetValue);
        targetValue ->Apply(aEffect);
    }
    
    ActorValue::Apply(IEffect* aEffect) {
        // и тут уже пошла логика применения изменения используя дескриптор эффекта.
        //  в простейшем случае:
        _value += aEffect->getDelta();
    }
    


    1. ddinochrome
      18.05.2019 01:15
      +1

      А чем такой код хуже?)

      let player = {
        attackPower: 5,
        xp: 0
      };
      
      let monster = {
        hp: 50, 
        level: 10  
      };
      
      function isMonsterAlive(monster)
      {
        return monster.hp > 0;
      }
      
      function playerHitsMonster(player, monster)
      {
        monster.hp -= player.attackPower;
        if (!isMonsterAlive(monster))
          player.xp += monster.level; 
      }
      


      Минимально необходимое количество структурированных данных и функций для решения поставленной задачи. Минимальная стоимость реализации и поддержки ведёт к максимальной прибыли от внедрения решения в бизнес-процесс (в случае, если гипотеза геймдизайнера оказалась верна и этот функционал стал приносить прибыль). А что дадут в денежном эквиваленте все эти Actor, ActorValue, HitInfo, Globals, Physics, IAffectable, IEffect? Лишнее время на проектирование, написание и отладку, лишние сущности и связи между ними, больше рисков словить баги.


      1. VS_Revan
        18.05.2019 01:32

        При реальной бизнес-задаче нужно будет предоставить демку такой игры. Всего по минимуму, но оно должно быть рабочим. Я просто предположил, что речь идет о 3х-мерном шутере — в этом суть этих всех HitInfo, Globals, Physics: показать на основании чего будет происходить взаимодействие между сущностями (на основании физики в данном случае). И этот код будет работать правильно практически без изменений для многих расширений: стреляем из дробовика или огнемета, стреляем по банкам/стенам/окнам/врагам/союзникам или вообще хиляем их или бафаем.

        Прелести ООП в примитивных задачах понять нельзя.


        1. ddinochrome
          18.05.2019 02:08
          +1

          Да тут нет никакого ООП. Почему физика — это namespace, а не объект? Что это за такой Globals? Где идёт разделение игровой логики, физической модели, геометрической модели и рендеринга? Коллайдер — это геометрия, а не физика. Физика опять же бывает разная: кинематика, динамика, инверсная кинематика.
          Вобщем, Ваш код больше похож на хаки джуна, это своеобразная мимикрия под продакшн-код в геймдеве. Я такого добра много видел на проектах второкурсников — они его по выходным по ночам до 4 утра отлаживали перед выставками, а потом оно всё равно тормозило, глючило и падало.
          Нагородить абы каких абстракций — это не помогает. А с возрастом и опытом понимаешь — даже если нагородить «правильных» абстракций — это тоже не помогает. В итоге Time To Market рулит процессом и определяет стиль программирования.
          Мой код не имеет внешних зависимостей. Его можно уже брать и использовать. А Ваш код имеет кучу внешних зависимостей, которые нельзя описать без дополнительной информации от заказчика. Мой Time To Market — 10 минут. Ваш — если всё-таки возьмётесь довести свой код до работоспособного состояния — пара дней. Эффект для пользователя один и тот же.
          Теперь считаем: 1 час разработчика стоит $100. За готовую демку платят $1000.
          Я заработал $980.
          Вы в убытке на $600.
          Вот Вам и примитивные задачи)


          1. VS_Revan
            18.05.2019 03:03

            Почему физика — это namespace, а не объект
            Это класс со статическими методами.

            Что это за такой Globals
            Глобальные константы. в данном случае хранит битовую маску слоев с IAffectable объектами.

            Где идёт разделение игровой логики, физической модели, геометрической модели и рендеринга?
            Это игровая логика, использующая физику. За рендеринг обычно отвечает конвейер визуализации — это другая степь.

            Коллайдер — это геометрия, а не физика
            В гейм-деве часто коллайдеры используются как часть физической модели (примитивы — для упрощения расчетов).

            Физика опять же бывает разная: кинематика, динамика, инверсная кинематика.
            Физика использовалась только для определения столкновений динамических объектов — Актора и пули(которая для упрощения примера двигалась моментально, да и ее класс мы опустили).
            Инверсная кинематика (IK) обычно используется в гейм-деве для анимаций конечностей (ходьба по ступенькам, толкание дверей и т.п.), а не для расчетов физики.

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

            Мой Time To Market — 10 минут. Ваш — если всё-таки возьмётесь довести свой код до работоспособного состояния — пара дней. Эффект для пользователя один и тот же.
            Теперь считаем: 1 час разработчика стоит $100. За готовую демку платят $1000.
            Подскажите плиз где за такие демки как ваша платят по 1к? Она не рабочая. Она решает одну тривиальную задачу в вакууме. Как, впрочем, и мой набросок (хотя в нем хотя бы подразумевается какой-то намек на способ интересующего взаимодействия).


            1. ddinochrome
              18.05.2019 11:15
              +2

              суть ООП — правильные абстракции уменьшают количество ...


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

              Она не рабочая. Она решает одну тривиальную задачу в вакууме.


              Определитесь, пожалуйста, моя демка нерабочая или всё-таки решает задачу?)
              Мой код запускается и работает прямо в консоли Вашего браузера. Да, у него нет 3Д и партиклов. Но в постановке задачи этого и не было. Соответственно я пошёл по кратчайшему пути и дошёл быстро. Я могу показывать свою демку заказчику описанной логики взаимодействия Игрока и Монстра уже сейчас.
              image
              А Вы когда сможете?


              1. VS_Revan
                18.05.2019 13:14

                Ваш код больше похож на хаки джуна, это своеобразная мимикрия под продакшн-код в геймдеве. Я такого добра много видел на проектах второкурсников.
                А с возрастом и опытом понимаешь
                Ну так у Вас и не пахнет правильными абстракциями
                Но у Вас всё хуже — у Вас дикая смесь
                То, что принято использовать в низкобюджетном геймдеве
                нелогичное нагромождение случайных шаблонов
                Это не конструктивно.

                не должно быть статических методов и глобальных констант. Всё должно быть реализовано на объектах.
                Мои Globals да, попахивают, лучше эту константу хранить в спец. классе (да хоть в том же Физикс), но по сути это останется глобальной константой.
                Без статических методов можно обойтись, но зачем? Это будет не оптимально — нужно выделять и освобождать память там, где это не нужно. Это просто методы, которые можно использовать без создания экземпляра класса. Почему статические методы противоречат концепции ООП?

                Определитесь, пожалуйста, моя демка нерабочая или всё-таки решает задачу?)
                Она не рабочая как демка. С таким же успехом можно было написать просто log(50-5) — т.е. это хардкод для одного случая. Мы по-разному смотрим на задачу: я вижу проблему взаимодействия Monster-Player, Вы, на сколько я понял, проблему отнимания хп. Первая решается при помощи ООП (особенно если подразумевается такое взаимодействие между различными объектами, а не только уловными Monster и Player), для второй, безусловно, ООП оверкил.

                А Вы когда сможете?
                Используя какой-то Unreal или Unity — к вечеру, но моя демка будет являться продуктом в который можно поиграть и, т.к. я потратил лишние 20 мин на проектирование структуры и еще 40 на имплементацию доп. слоев абстракций, можно будет в дальнейшем расширять, не переписывая все с 0.


                1. ddinochrome
                  18.05.2019 15:45
                  -1

                  Это не конструктивно.

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

                  Без статических методов можно обойтись, но зачем?

                  Затем, чтобы показать пример ООП в обсуждении про ООП. Иначе у читателей возникнут сомнения в Вашей адекватности.

                  Почему статические методы противоречат концепции ООП?

                  Потому что это по сути те же глобальные переменные. Они нарушают принцип инкапсуляции. Глобальные константы также нарушают инкапсуляцию. Согласно ООП ничто извне не должно менять поведение объекта. А что Вы делаете?
                  const int Global::magicOOPNumber = 5;
                  ...
                  function MyLovelyGamedevClass::Fire(IMonster *victim)
                  {
                    victim->takeItAsADamage(this->power * Global::magicOOPNumber);
                  }
                  

                  Я могу только предположить, что на основании того, что вы используете C++, вы утверждаете, что ваш код — это ООП. Но, к сожалению, C++ — это не объектно-ориентированный язык. Это мешанина из всех известных человечеству концепций. Чисто теоретически на нём можно написать ООП код, но у вас, увы, это пока что не получается. Как и у всех, кого я знаю)

                  С таким же успехом можно было написать просто log(50-5)

                  Позвольте не согласиться. Успеха тут не получится, потому что это — не валидный JS-код.
                  console.log(50-5);

                  Вот так будет работать, но в чём смысл? Это просто однократная арифметическая операция с магическими числами.
                  Важно, чтобы было понятно какова семантика входных и выходных данных. И потом уже эту семантику можно подключать к системам ввода/вывода. Также важно оформить типичные операции над данными в функции, которые можно вызывать повторно и использовать для разных сущностей в игре.
                  В моём коде тоже описывается взаимодействие между игровыми сущностями. Тут есть и hp, и xp, и сила удара, и уровень Монстра. Просто я оформил игровую сущность как структуру данных и не стал накручивать несуществующие в тексте статьи дополнительные требования (типа 3д, физики, affectableLayer'ов и прочего оверинжиниринга).
                  А Вы пытаетесь реализовать то же самое взаимодействие тех же самых сущностей через объекты. Но это не ООП, это не компилится, и у этого до сих пор нет скринов — а вечер уже близко.


                  1. VS_Revan
                    18.05.2019 18:01
                    +1

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

                    Согласно ООП ничто извне не должно менять поведение объекта. А что Вы делаете?
                    На самом деле там нет изменения объекта через константу, там оптимизация рейкаста при помощи константы. Из этого я делаю 2 вывода:
                    1) да, для текущей задачи это и правда было излишеством — сори, писал с оптимизацией по привычке.
                    2) хороший код должен быть очевиден даже для джуна и т.к. Вы его не поняли, то код и правда не так хорош.

                    Но это не ООП, это не компилится, и у этого до сих пор нет скринов — а вечер уже близко
                    Хм, а ведь и правда сказал. Ок, вот демка (как оказалось заняло это на Юнити около 40 мин):


                    1. ddinochrome
                      18.05.2019 18:41

                      HP и Level у монстров не видно, XP игрока не видно. Переделайте, пожалуйста.


                      1. VS_Revan
                        18.05.2019 19:20

                        Экспа игрока слева внизу, остальное согласен добавить при условии что вы предоставите аналогичный проект (3-д, играбельный) с «не ООП» подходом


                        1. ddinochrome
                          18.05.2019 19:32
                          -1

                          Да у вас у самого проект не 3Д. Все события происходят в плоскости. Если игрок и монстры хотя бы прыгать начнут… тогда на 3Д будет похоже)
                          Я лично не буду писать никакой графики и интерактива — ничего этого в описании задачи в оригинальной статье не было. Я выбрал наиболее лаконичный способ реализовать описанное в статье взаимодействие сущностей и вполне себе доволен этим. Мой билд занял 15 минут жизни (вместе с презентацией), весит 301 байт и я могу его демонстрировать.
                          Ваш билд весит 36 863 414 байт, до сих пор не готов к демонстрации и уже занял как минимум 2 часа на изготовление и презентацию.


                          1. VS_Revan
                            18.05.2019 19:47

                            Это не билд демо, а лог какого-то алгоритма. Если Вы предоставите это заказчику, он просто не поймет что это.

                            Да у вас у самого проект не 3Д
                            Это 3д-графика и 3-д мир, но да все события происходят в плоскости. Но я согласен и на 2-д игру взглянуть.

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


                            1. ddinochrome
                              18.05.2019 19:58

                              Мой заказчик — автор статьи. Он программист и всё прекрасно поймёт. Скажем так, у меня — текстовый квест)
                              Вот игра в сторе, созданием которой я руководил: crystal slots. Серверная часть написана без ООП, на ней вся игровая и бизнес- логика крутится. DSL для настройки правил игры присутствует. Исходники, конечно, не покажу. Но можете их купить за N x $100k, если есть большое желание.


                              1. VS_Revan
                                18.05.2019 20:06
                                +2

                                Ясно, спасибо. Вопросов больше нет. Хорошего дня!


                    1. ddinochrome
                      18.05.2019 18:53

                      Предоставленный код снова не компилится — говорит нет сцены и файла проекта.


                      1. VS_Revan
                        18.05.2019 19:16

                        https ://drive.google.com/open?id=1Yvxf_YjrD7FuHJnNJGn7oe1_sx8eowCG
                        Погуглите как запускать сцену если что. Версия Юнити 2018.1.8.1f1+


  1. IvanTamerlan
    17.05.2019 22:12

    Задумался про взаимодействие. Например, класс/запись Unit {name, hp, items, param}, от него наследуются Monster и Player (или новые записи). Это ради того, чтобы получили метод или функцию:
    Attack(a,b:Unit);
    И тогда нам неважно, кто кому наносит урон: игрок монстру, монстр игроку, питомцы из нового обновлния, PVP или же у монстроботов появилось несколько враждующих фракций (пока боты дерутся игрок может постоять в сторонке и потом добить выживших и собрать лут). Также упрощает создание монстров с инвентарем, дабы поднимали вещи и при смерти выкидывали и/или тушка была временным сундуком-инвентарем. Инвентарь дает еще возможность собирать броню (бронированный зомби). Ладно, инвентарь можно вынести в отдельную запись.
    Мало того, можно все данные заключить в Record (записи). А далее использовать классы-хелперы (если выбранный ЯП позволяет), которые дают возможность вызова методов у обычных переменных (вызываются методы класса-хелпера).

    Проблема ООП — неограниченный полет фантазии. И потом попробуй разбери, как оно работает и работает ли вообще.


    1. S-e-n
      18.05.2019 02:16
      +1

      Мало того, можно все данные заключить в Record (записи). А далее использовать классы-хелперы (если выбранный ЯП позволяет), которые дают возможность вызова методов у обычных переменных (вызываются методы класса-хелпера).


      Мне кажется, это называется процедурным программированием.


      1. IvanTamerlan
        18.05.2019 19:27
        -1

        Я не замечал в процедурном программировании классов-хелперов. Т.е. я не могу в обычно процедурном программировании использовать методы, например, у целого числа, даже у константы, например
        a = (0xFF55.toStr).addBold; // преобразование в строку константы, добавление html-тега;
        На втором месте по удобству — функции, типа
        a = HtmlBold(IntToStr(0xFF55)); // выполнение функций идет от вложенных справа до внешних слева, т.е. обратный от порядка чтения слева направо;
        И самое неудобное — чисто процедурами, иногда могут потребоваться доп.переменные:
        IntToStr(0xFF55, a); // аргументы inVar, outVar;
        HtmlBold(a,a); // иногда вход и выход совмещаются;

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


        1. S-e-n
          18.05.2019 21:24

          Я про смысл скорее, не про способ записи.


  1. platycosm
    17.05.2019 22:44

    Чем-то мне это напоминает «каждая мать хочет, чтобы ее сын женился, но ни на одной из существующих женщин». В каждой концепции есть изъяны, но если вам и для ваших задач подходит, это не повод от неё отказываться :)


  1. maxxannik
    17.05.2019 23:20
    +1

    Очередной холивар на тему ООП. При этом люди не удосужились понять что это такое. Как автор статьи, так и те кто в комментах пишет.

    Надо почитать мысли Алана Кея, чтобы понять что ООП это не про классы.
    Еще есть книга МЧМ Фредерика Брукса написанная аж в 80е годы. Там тоже расскрывается тема разных ООП подходов. Классы — это лишь один из подходов.

    Как только разобраться в том что есть ООП — все эти холивары начинают походить на цирк и спор слепых людей о цвете помидора.


  1. muhaa
    18.05.2019 00:42
    -2

    Смотрите:

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

    2. Допустим, у нас есть список сходных сущностей и нам нужно выполнить для них всех некую операцию, детали которой зависят от конкретного типа сущности. Тогда, мы используем ООП и полиморфизм. И опять, все красиво, полиморфная часть кода записана внутри класса. Ничего лучше и не придумаешь.

    3. Допустим, у нас есть некий простой объект, который изначально вводится ради его полей, в которых он хранит данные (например, доменный объект, юнит в игре, видимый элемент в графическом редакторе). Допустим, мы запрограммировали в этом объекте некое «поведение» — функции, работающие с его данными более сложные, чем обычные setter-ы. Это ООП, который я никогда не мог понять. Для меня это ужасное и вредное применение ООП. Сценарии поведения чуть посложнее сразу становятся страшно запутанными. Где заканчивается поведение одного класса и начинается поведение другого? Почему так много вариантов как это можно реализовать и чего ради все эти мучения? Что будет если поведение придется дорабатывать? Что если поведение объектов зависит от того какая задача сейчас решается (случай имитационного моделирования)? Почему я должен ломать голову об этом уже сейчас и чего ради вообще все эти сложности?
    Во много раз проще описать поведение в отдельных функциях (статичных или в виде отдельных классов-обработчиков). Возможно есть некие особые виды ПО, с которыми я не сталкивался и в которых доменно-подобные объекты с поведением что-то дают. Для меня это загадка.


    1. muhaa
      18.05.2019 13:52

      Минусы это понятно. Я написал это в надежде что кто-то пояснит что именно я упускаю.


      1. michael_vostrikov
        18.05.2019 23:01
        +1

        Вы написали что-то вроде "Допустим, у нас код плохой, и сценарии запутаны. Зачем нужны эти сложности". Код плохой это наверно не очень хорошо, только причем тут ООП. И без ООП было бы так же запутано. Если б вы пример какой-то привели, можно было бы проверить, связано это с ООП или нет.


        1. muhaa
          19.05.2019 11:52

          Допустим, это ПО для моделирования электрической сети. Доменные объекты — выключатели, трансформаторы, узлы и ветви графа схемы.
          Могут решаться задачи нахождения каких-то общих показателей для схемы, поиска требуемых изменений схемы или переключений в схеме. Хорошая ли идея, пытаться представить требуемые алгоритмы как поведение доменных объектов? Это очень сложно. Допустим, это удалось. В чем преимущества такого решения? Какого-то скрытия сложности не получается, потому что алгоритмы моделирования и оптимизации сами по себе проще чем тем те же алгоритмы представленные в виде поведения доменных объектов.
          Другой пример — пишем графический редактор, позволяющий редактировать табло с бегущими строками, расписаниями и др. Объекты здесь — это видимые на табло редактируемые элементы. Понятно, что в эти объекты удобно спрятать сложный и полиформыный код, типа функций рисования, удаления, перемещения при редактировании. Но стоит ли пытаться поместить саму логику редактирования в методы этих объектов? Эта логика работает сразу с несколькими объектами разных классов и это эту логику не сложно понять. Зачем делить эти алгоритмы на части по методам отдельных классов?


          1. akryukov
            19.05.2019 11:58

            Покажете пример кода без ООП, где эти задачи решены?


            1. muhaa
              19.05.2019 13:48

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


          1. michael_vostrikov
            19.05.2019 14:46

            Доменные объекты — выключатели, трансформаторы, узлы и ветви графа схемы.

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

            А где у вас доменный объект "Схема"? Он у вас в 3 задачах появляется. Вот в этом объекте и будет эта логика.


            Но стоит ли пытаться поместить саму логику редактирования в методы этих объектов? Эта логика работает сразу с несколькими объектами разных классов

            Не стоит. Значит у вас в предметной области есть объект, который вы не учли, он и содержит логику взаимодействия разных классов.


            1. muhaa
              19.05.2019 16:42

              Да, конечно в ПО есть крупные объекты, которые отвечают за решение отдельных задач. Это все в рамках пункта 1 в первом моем сообщении выше. Это очень простое и очевидное применение ООП, не вызывающее никаких трудностей. По сути это просто модульность. Вопрос о применении ООП в случаях пункта 3.


              1. michael_vostrikov
                19.05.2019 17:07

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


                1. muhaa
                  20.05.2019 00:26

                  Все, теперь сориентировался. Прочитал выше ваш спор michael_vostrikov vs Belikov. Как понял, мой вопрос и вопрос который вы обсуждали был о допустимости использования анемичных моделей. Ваш оппонент предлагает вводить неимоверно сложные иерархии перекрестного наследования, которые весьма вероятно придется еще и рефакторить по ходу развития проекта. И утверждает, что это будет проще понять и развивать, чем отделенные от объектов игры алгоритмы-менеджеры на пару десятков строк, каждый из которых получает на входе ситуацию и выполняет последовательность довольно очевидных действий с объектами. Во втором случае вместо ада наследований поведения мы имеем несколько логический условий, которые всегда легче реализовать и поправить чем иерархию наследования.
                  Мне понятны ваши доводы, но непонятна точка зрения Belikov, ратующего за связь кода с данными любой ценой.
                  Допустим, в модели с combat-менеджерами мы не можем взять отдельный объект и понять из его кода как он себя ведет в битве. Нужно изучать систему combat-менеджеров. Это видимо и есть главный недостаток. Но ведь в чистой ООП-модели по одному объекту тоже не будет ничего понятно. Будет только понятно, что он ждет чего-то абстрактного и реагирует. Хорошо, если этому абстрактному удалось дать вменяемое название, не требующее пару страниц разъяснений всей концепции. И так далее.
                  В общем, у меня ощущение, что свободное от анемичных элементов ООП — это технология для очень простых задач или для некого узкого круга задач, с которыми я не сталкивался. В большинстве случаев чистое ООП требует введения очень сложных и довольно неустойчивых абстракций. Точно так же как в случае ФП если задаться целью работать исключительно с чистыми функциями, то все становится очень не просто.


                  1. michael_vostrikov
                    20.05.2019 01:24

                    Да нет, поведение должно быть связано с объектом. Только в том примере с игрой в предметной области оно находится не в том объекте, в который его пытаются поместить в коде.


                    1. muhaa
                      20.05.2019 10:14

                      Попробую понять. Значит, если у объекта есть какое-то понятное характерное поведение, то оно реализуется в объекте. А если у нас есть общее поведение группы объектов, которое теоретически можно порезать по объектам, то тогда стоит подумать: возможно это разрезание будет искусственным, слишком сложным и бесполезным. Тогда не стоит этого делать и нужно реализовать это поведение в виде методов этой группы. При этом, формально данные объектов находятся внутри объекта-группы и он имеет право ими оперировать. (в действительности это немного самообман, т.к. от такого подхода остается один шаг до менеджера со статическими методами).
                      Т.е. если у нас несть некий алгоритм поведения объектов, который легко режется на части по объектам, и эти части хорошо дорабатываются в дальнейшем а мы его не порезали и реализовали как единое целое в неком менеджере — тогда это антипатерн, анемичная модель. Если же этот алгоритм не получается порезать однозначным и естественным образом, не сочиняя дикие неустойчивые абстракции, то тогда и не нужно это делать. Он просто не относится к доменным объектам на самом деле, он относится к объектам уровнем выше.
                      Если алгоритм можно было разделить по классам, но программист этого не сделал, возможно он плохо владеет ООП. Если алгоритм был неделим по сути, но его ухитрились раскидать по методам классов, так, что теперь концепция системы стала не для средних умов, тогда этот программист применяет концепции ООП не по назначению.


                      1. michael_vostrikov
                        20.05.2019 13:11

                        А если у нас есть общее поведение группы объектов, которое теоретически можно порезать по объектам

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


                        Главное делать наиболее точную модель предметной области, а если где-то упрощаем, то надо определять, корректно ли такое упрощение, и что именно нам это дает. Например, про ту же гранату. Необязательно моделировать 1000 осколков при взрыве, можно получить объекты в радиусе поражения, допустим их 2, и смоделировать всего 2 осколка. Это корректное упрощение, потому что моделирование остальных осколков ни на что не влияет.


                        При этом, формально данные объектов находятся внутри объекта-группы и он имеет право ими оперировать.

                        Оперирование данными конкретного объекта можно и скорее всего нужно поместить в его класс. В класс группы надо помещать только то, что связано с несколькими объектами.


                      1. VolCh
                        20.05.2019 14:30

                        Алгоритмы поведения доменных объектов — часть модели домена. В зависимости от платформы и соглашений они могут представлены методами объектов-сущностей, другими объектами (сервисами, стратегиями и т. п.), функциями/процедурами, методами базовых классов. При моделировании домена у нас как бы нет уровня выше, "выше" — инфраструктура, UI, API и прочие *I


  1. semenyakinVS
    18.05.2019 01:01
    -1

    Многие проблемы ООП решаются использованием элементов компонентного подхода. Часто поведение, включаемое в объекты с помощью интерфейсов, реализуемо компонентами. Всякие интерфейсы IDamagable и IMovable заменяются на компоненты DamagableComponent, MovableComponent, которые проще и прозрачнее интерфейсов и, как бонус, делают возможным включение аспектов поведения объектов динамически.


  1. third112
    18.05.2019 02:14
    +1

    ИМХО прежде всего забыть ООП, согласно заголовку статьи, не реально, т.к. очень многие IDE имеют графические инструменты для создания GUI, и эти инструменты генерируют ОО код. Такие инструменты не интегрированные в IDE, не генерирующие кода, а только ресурсы, появились до широкого распространения ООП. В частности, ResEdit для классических Macintosh 68K. ООП окзалось очень удобным для генерации кода GUI. Про GUI у автора статьи нет ни слова.

    Но ИМХО основная сомнительная посылка статьи:

    структура данных определяет необходимый код


    Сошлюсь на известную классическую книгу Вирта, не содержащую ООП:

    Алгоритмы + структуры данных = программы

    Об алгоритмах автор забыл? Умолчал?

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


    Недавно в другом обсуждении писал про виртовкий (не ОО) Паскаль:
    Простейшая инкапсуляция уже есть в записях (record). Далее понятие о наследовании приходит в таких простых примерах:
    type
    TCoord = record // координаты точки
                        x, y : integer
                      end;
    TRect = record // прямоугольник
                         leftTop, RBot : TCoord;
                   end;

    Но автор борется с ветряными мельницами — многие ЯП не настаивают на использовании ООП, как не настаивают на использовании записей (record) и IDE. Не хотите использовать визуальное средство разработки — чертите на бумажке окошки вашей программы, меряйте линейкой координаты и записывайте в ресурс в виде текста. Бывают случаи ограниченного использования ООП, из собственного опыта: для игрового бота взял интерпретатор Паскаля (Вирт и др.), написанный не в ООП, и приделал IDE, сделанное в ООП.

    Если данные программы, например, хранятся в табличном, ориентированном на обработку данных виде, то можно создать два или более модулей, каждый из которых работает с той же структурой данных, но различным образом. Если данные разбиты на объекты с методами, то это больше невозможно.
    Снова сошлюсь на свой опыт: ООП не мешает (а помогает) использовать столь нелюбимые автором графы в различных представлениях.

    огромные спагетти-графы объектов, указывающих друг на друга, и методы, получающие огромные списки аргументов.

    Давным давно для одного коммерческого проекта делал GUI на упомянутом Macintosh 68K под упомянутым ResEdit. Списки аргументов были гораздо больше, чем м.б. с применением ООП. Чтобы убедится достаточно посмотреть многотомниик Inside Macintosh тех лет — там много примеров.

    Сочетание разброса данных по множеству мелких объектов, активное использование косвенности и указателей, отсутствие правильной архитектуры данных приводят к низкой скорости выполнения. Такого обоснования более чем достаточно.
    Это не обоснование, а голословное утверждение. Необходимы примеры со сравнением времени выполнения.

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


  1. kuftachev
    18.05.2019 02:30
    -1

    Если человек хочет писать бизнес логику на SQL, то кто ему доктор? Уже некоторые базы данных дают REST API, которое дёргает функции внутри самой БД.


    Но по сути статья бредовая.


  1. Groramar
    18.05.2019 07:38
    +1

    Как это часто и бывает, вначале автор набрасывает кучу крайне спорных тезисов (которые к реальной жизни/практике имеют мало отношения), а потом с ними борется.
    Пишу на Delphi, достаточно толстые проекты, миллион строк+. Без ООП была бы каша из типов, хранилищ данных и функций, их обрабатывающих. С ним всё проще: понятно, где данные хранить и как их обрабатывать — всем занимается один класс.


  1. Exilibris
    18.05.2019 07:54
    -1

    цель -> архитектура данных -> код


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

    Но для начала, давайте немного поговорим о терминологии, чтобы мы правильно друг друга понимали, общались едиными терминами:



    Цель — субъективно я понимаю это как конечное желаемое состояние системы. Цель сама по себе абстрактна, она не предполагает ни средства, ни пути достижения, ни является автоматически доказуемой (то есть мы не можем обладая только целью утверждать о ее достижимости в принципе, так как это требует доказательной базы).

    Например: цель — сделать двигатель, который будет перемещать транспортное средство быстрее скорости света; или, скажем, реализовать проект, который поглотит 120% емкости рынка (при очевидном максимуме в 100%).

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

    Я считаю это важным при построении дальнейших рассуждений.



    Архитектура данных — субъективно я понимаю это как конкретную конфигурацию представления данных (из бесконечного множества конфигураций), понятную инженеру и выбранную им с точки зрения оптимизации по целевым критериям.

    Архитектура данных не определяет логику, этот слой системы абстрагирован от цели также как и абстрагирован от функционала. Конфигурация представления данных является отдельной сущностью, производной действий инженера. Чуть ниже я раскрою смысл акцента внимания на этом.



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



    Цель, архитектура данных, код (логика).

    Цель не определяет архитектуру данных.
    Архитектура данных не определяет цель и не определяет логику.
    Логика не определяет цель и не определяет исходную структуру данных, а лишь предполагает исходную структуру данных.

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

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



    Причем тут ООП или «что ты несешь?!»

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

    Никто не мешает писать логику с точки зрения манипуляции атомарными единицами информации, без каких-либо ограничений, представляя часть атомарных сущностей данными, а часть инструкциями… Но это невероятно сложная задача для нашего сознания на современных масштабах систем. Люди вынуждены выдумывать способы уменьшения роста уровня сложности создаваемых систем. Именно для этого создаются концепции и парадигмы. Упрощение — это всегда ограничение.

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

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

    Вероятно «неудобство» и ограничения связанные с использованием ООП (с учетом конкретных примеров) вполне могут быть метафорически представлены ситуацией, когда ключом на 13 может быть неудобно откручивать гайку на 11, а возможно даже и ситуацией, когда ключом на 13 неудобно или невозможно закручивать саморез…

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

    Это не плохо само по себе.

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

    Это не плохо само по себе.

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

    Это не плохо само по себе.

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

    Нет плохих систем или хороших, есть те, которые удовлетворяют или не удовлетворяют целевым критериям.

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

    Хорошо спроектированные системы легко адаптировать, так как они воспринимаются простыми и при минимальном вложении труда удовлетворяют целевым критериям.



    Устали? Осталось потерпеть одну минутку!

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

    Сами по себе эти принципы применяются при формировании сущностей (абстракций), которыми мы непосредственно оперируем при построении систем: классы и абстрактные классы, интерфейсы, трейты, примеси и т.д.

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

    Каждый масштабный уровень системы обладает своим набором возможных абстракций.

    А потому каждый масштабный уровень системы обладает своими методами оптимизации композиций абстракций.

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

    В этот момент мы открываем для себя принципы проектирования ООП, такие как SOLID:

    S (The Single Responsibility Principle) — принцип единой ответственности (SRP).
    O (The Open Closed Principle) — обозначает принцип открытости/закрытости (OCP).
    L (The Liskov Substitution Principle) – принцип подстановки Лисков, описывающий возможности заменяемости экземпляров объектов (LSP).
    I (The Interface Segregation Principle) — принцип разделения интерйесов (ISP).
    D (The Dependency Inversion Principle) — принцип инверсии зависимостей (DIP).

    Эти принципы основаны на ООП, основаны в своей сути на инкапсуляции, наследовании и полиморфизме, но находятся на один уровень выше в системе.

    К чему это я? Да к тому, что говорить «хорошо» или «плохо» как минимум слишком ограниченно, причем ограниченно контекстом восприятия. При действительно масштабном представлении системы происходит своего рода «мульти-интерпретация» в рамках которой парадигмы, методы проектирования или методы разработки в целом (Waterfall, SCRUM, Kanban) являются лишь инструментами в ваших руках, и целесообразность их применения зависит от степени осознания их изначального предназначения.



    P.S.
    Мартышка к старости слаба глазами стала;
    А у людей она слыхала,
    Что это зло еще не так большой руки:
    Лишь стоит завести Очки.
    Очков с полдюжины себе она достала;
    Вертит Очками так и сяк:
    То к темю их прижмет, то их на хвост нанижет,
    То их понюхает, то их полижет;
    Очки не действуют никак.
    «Тьфу пропасть! — говорит она, — и тот дурак,
    Кто слушает людских всех врак:
    Всё про Очки лишь мне налгали;
    А проку на-волос нет в них».
    Мартышка тут с досады и с печали
    О камень так хватила их,
    Что только брызги засверкали.


  1. Druu
    18.05.2019 08:28

    Автор, кажется, из прошлого века заехал. В 2019 ООП — это про поведение, то есть про интерфейсы к данным. С-но, данные как раз можно представлять как угодно, в отрыве от ООП-структуры приложения.


  1. KvanTTT
    18.05.2019 12:37

    Объектно-ориентированное программирование — чрезвычайно плохая идея, которая могла возникнуть только в Калифорнии.

    Что за бредовая цитата? Почему Калифорния обязательно означает плохую идею? Там родилось и множество хороших.


  1. zaq1xsw2cde3vfr4
    19.05.2019 01:42

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


  1. YuriPanchul
    19.05.2019 06:02

    Объясняю на пальцах, почему во многих приложениях ООП увеличивает сложность, а не уменьшает. Допустим у вас есть компилятор. В нем есть объекты типа таблица символов, символ, узел дерева. Узлы бывают типа константа, ссылка на переменную, бинарная операция +, тернарная операция ?, присваивание, вызов функции итд.

    Существенная часть кода — это рекурсивный обход леса деревьев с преобразованиями поддеревьев. Например для вычисления выражений с константой, разворачивания циклов и рекурсии итд.

    Допустим у вас есть N=200 алгоритмов преобразования дерева и M=100 типов узлов. Эти N и M независимы. Каждый из N должен делать специфические действия для каждого из N.

    Если вы структурируете код без OOP, у вас универсальная функция обхода дерева + один тип узла Node + N=200 функций для локального преобразования дерева типа:

    transformation_1 (Node * & rp)
    {
    switch (rp -> op)
    {
    case OP_CONSTANT: // Делай что-то для константы
    case OP_PLUS: // Делай что-то для PLUS

    default: // Ничего или по умолчанию
    }
    }

    transformation_2 (Node * & rp)


    Если же вы решили сделать иерархию типов узлов, например:

    class NodePlus: Node {… }
    class NodeConstant: Node {… }

    — и при этом сделать все 200 преобразований деревьев виртуальными функциями, то у вас будет N * M = 20000 маленьких виртуальных функций типа:

    NodePlus::transormation_1 ()
    NodePlus::transormation_2 ()

    NodeConstant::transormation_1 ()
    NodeConstant::transormation_2 ()


    Как вы будете группировать эти функции по файлам? Группировать по алгоритмам или по классам? Если по алгоритмам, то вы раскидаете один класс по 200 файлам, что просто увеличивает количество набитого вами кода и никак не помогает уменьшить сложность. Если по классам, то оно резко увеличивает сложность, так как теперь, чтобы понять каждый алгоритм, программисту нужно будет просматривать не один, а 100 файлов.

    OOP годится для определенных программ, например для GUI. В других программах он приводит к тому, что программисты занимаются не полезной работой, а борьбой за/против OOP, из-за чего получаются непонятные, большие, медленные программы.

    Disclaimer: я использую C++ и OOP с 1993 года, и пришел к выводу о неоходимости использовать OOP сдержанно после года через три после начала его использования. Желание наплодить классов и иерархий — это желание либо новичка, либо человека, которые пишет GUI или подобные легко укладывающиеся в классы приложения. Весь софтвер, которые делает сложную работу с графами — just say NO to OOP.


    1. khim
      19.05.2019 06:35

      — и при этом сделать все 200 преобразований деревьев виртуальными функциями, то у вас будет N * M = 20000 маленьких виртуальных функций типа:
      Не путайте, пожалуйста, ООП и «виртуальные функции» — это не одно и то же.

      Весь софтвер, которые делает сложную работу с графами — just say NO to OOP.
      Серьёзно? А нельзя взглянуть на ваш компилятор? Ну чтобы сравнить его с LLVM'ом? Который использует ООП — и очень серьёзно (но не так наивно, как вы описали, с комбинаторным взрывом, конечно).

      Код LLVM мне, лично, кажется куда более понятным чем год GCC, к примеру. Хотя, вроде как эти десятиэтажные макросы в GCC и должны упрощать работу с кодом…


      1. YuriPanchul
        19.05.2019 06:49

        Код LLVM посмотрю. Мой компилятор показать не могу, так как он сейчас собственность Synopsys, но вот про него статья — en.wikipedia.org/wiki/C_Level_Design

        Если не трудно, ткните меня в код LLVM с классами для узлов деревьев выражений или чего-нибудь подобного.


    1. michael_vostrikov
      19.05.2019 10:22

      то у вас будет N * M = 20000 маленьких виртуальных функций типа:
      NodePlus::transformation_1 ()
      NodeConstant::transformation_1 ()

      Зачем? Почему нельзя сделать отдельный класс, который будет заниматься трансформациями дерева? Single responsibility и все такое.


    1. tmteam
      20.05.2019 01:04

      Я не очень опытный разрабочик компиляторов, но, кажется для конкретно вашей задачи отлично подходит «Визитор». Безусловно нужно смотреть контекст задачи.

      С проблемой каши из кода, при подходе DFS по синтаксическим нодам со свитчами и процедурами я столкнулся уже при N = 7, и M = 30.

      Также, у каждого типа синтаксической ноды есть множество принципиально различных атрибутов, нужных для анализа и принятия решений, что есть жирный, статически типизированный плюс (привет Roslyn).

      Один такой визитор будет инкапсулировать один из N алгоритмов для всех M нод. В случае дубляжа кода можно воспользоваться как полиморфизмом так и наследованием.


      1. PsyHaSTe
        20.05.2019 07:35

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


        1. mayorovp
          20.05.2019 08:38

          Ну так оператор switch тоже только с фиксированным набором констант работает.


          1. PsyHaSTe
            20.05.2019 11:23

            Матч это и есть крутой свитч, который работает с АДТ. В том же сишарпе он сейчас так и выглядит


            switch foo {
               case MyTypeA typeA: ...
               case MyTypeB typeB: ...
            }

            только обычно он еще позволяет интроиспекцию


            match foo {
               Ok(Some(Foo {x = 5})) => ...
            }

            Проверит, что объект является Result со значением Ok (выражение посчиталось эксепшна), в котором лежит Option со значением Some (то есть результат выражения не null), в котором лежит структура Foo у которой поле x имеет значение 5.


            1. mayorovp
              20.05.2019 11:25

              … И что из этого следует?


              1. PsyHaSTe
                20.05.2019 11:50

                У вас аргумент в стиле "а на поверхности солнца температура 6000 градусов". Верно, но отношения никакого не имеет.


                Визитор это классическое решение expression problem для случая фиксированной иерархии. То, что матч решает нативно, собственно. В случае динамической иерархии он перестает работать, очевидно: нужно дописать метод VisitNewType и обновить все визиторы.


                1. mayorovp
                  20.05.2019 13:03
                  +1

                  В данном случае аргумент "а на поверхности солнца температура 6000 градусов" я вижу у вас.


                  Тут выше комментатор показывал код:


                  switch (rp -> op)
                  {
                    case OP_CONSTANT: // Делай что-то для константы
                    case OP_PLUS: // Делай что-то для PLUS
                    …
                    default: // Ничего или по умолчанию
                  }

                  и утверждал, что аналог на ООП будет ужасен. Ему ответили про паттерн visitor. Да, разумеется, оба этих решения подходят только для фиксированных иерархий. Да, разумеется, в нормальные языки давно уже завезли match, который тоже подходит только для фиксированной иерархии.


                  Не понятно только к чему вы это всё пишите.


                  1. PsyHaSTe
                    20.05.2019 13:19
                    +2

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


                    1. 0xd34df00d
                      20.05.2019 17:40

                      Интересно, как на ООП будет выглядеть trees that grow, которые в компиляторах ну очень нужны и полезны.


                      1. PsyHaSTe
                        20.05.2019 17:45

                        Ну как-то попытку завести tagless final на джаве я видел тут. Можете глянуть, если интересно.


                        1. 0xd34df00d
                          20.05.2019 17:49

                          Tagless final таки требует чуть менее мощной системы типов, ЕМНИП. А тут и GADTs, и DataKinds, и TypeFamilies, и ещё всякое.


                          1. PsyHaSTe
                            20.05.2019 18:04

                            Ну я же линканул. Вся разница в том, что приходится чутка больше руками писать:


                            The difference between Haskell and Java is that in Haskell for every pair of a typeclass and a type there can be at most one instance declaration, and once this instance declaration is defined, it remains implicitly in the scope and is passed around automatically by the compiler. In Java, there can be many implementations of a given generic interface instantiated with a given type, and these implementation are entities in the program that have names and have to be passed around explicitly.


                            1. 0xd34df00d
                              20.05.2019 18:06

                              Так это tagless final. Я же говорю об этом.


        1. tmteam
          20.05.2019 14:37

          В синтаксическом дереве — набор типов фиксирован стандартом языка.

          С другой стороны Visitor это не только паттерн матчинг но и SRP точка. Как раз та самая логическая группировка процедур обработки узлов.

          И в довесок к ништякам — Visitor есть очень удобный интерфейс для использования в DFS. Тоесть отлично разделяются алгоритм обхода дерева и алгоритм преобразования.

          Также есть вопрос к процедурному обходу — по читабельности:
          В «безвизиторном» варианте обхода дерева с огромной вариативностью алгоритмов, вопрос группировки всех этих процедур встает очень остро, и, по сути, решается внимательностью и совестью разработчика. Это ок — если разраб один. Это очень не ок когда разрабов несколько и у каждого немного свое мнение, а среди них еще и затаился человек-снежинка.

          Итого. В данном случае, визитор это:
          — SRP
          — Читабельность
          — Строгая типизация

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


  1. mynameco
    19.05.2019 12:31
    +1

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


    1. maxxannik
      19.05.2019 14:44

      Все просто. Бывает много типов ООП. Классы, компоненты, прототипы… Но 99% программистов осилили только классы. А далее включается психическая болячка под названием «комплекс утенка». Первое выученное становится единственно верным и родным как мама. Все что не похоже на классы то не ООП :)

      Об этом говорит Ален Кей — автор идеи ООП. Об этом писал в 80е годы Фредерик Брукс в своей книге МЧМ. Про это написано в Википедии. Есть куча примеров в которых ООП не на классах (а на прототипах и/или компонентах) побеждает на рынке. Но ребята с комплексом утенка упорно не хотят это замечать )


      1. symbix
        19.05.2019 18:07

        С ООП на классах тоже все нормально можно делать, только многословнее.
        Проблема не в классах, а в том, как их проектируют. При этом львиная доля проблем с проектированием — злоупотребление наследованием.


        1. VolCh
          20.05.2019 14:42

          Отказ от него там, где нет "противопоказаний" тоже ни к чему хорошему не приводит.


          1. symbix
            20.05.2019 16:28
            +2

            Тут в целом хорошо работает широко известное в узких кругах правило "Abstract or Final": каждый класс должен быть либо абстрактным, либо финальным. Чтобы его нарушить, нужны очень веские причины.


            Когда я об этом правиле узнал, я проанализировал два довольно крупных проекта, над которыми я работал. Везде, где я нашел наследование не от абстрактного класса (такого было очень мало, кстати), это был по сути хак для срезания углов.


            Разумеется, это правило не отменяет принципы SOLID, это просто хороший маркер: если возникает необходимость наследоваться не от абстрактного класса, наверняка где-то тут нарушается SRP, LSP или ISP (а обычно все сразу).


  1. dolovar
    19.05.2019 12:52
    +1

    ООП требует передавать повсюду длинные списки аргументов или непосредственно хранить ссылки на связанные объекты для быстрого доступа к ним.
    В каком месте ООП требует этого? ООП? Требует? Этого?

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

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


  1. Ryppka
    19.05.2019 19:03
    -1

    Кажется, сам размер этого обсуждения показывает, что объектная ориентация хуже интегрирования по частям. В последней просто нужна изобретательность в каждом, сука, конкретном случае. А объектно-ориентированная парадигма еще и порождает пустопорождние споры ни о чем в каждом, сука, конкретном случае. Спасибо Тебе, Господи, что хоть в конце жизненного пути избавил от необходимости выслушивать едедневные бессмысленные препирательства апологетов паттернов проектирования! На все, впрочем, воля Твоя но не моя…


    1. TheShock
      19.05.2019 19:07

      порождает пустопорождние споры ни о чем в каждом
      Которые затевают адепты религии ФП


  1. skch
    20.05.2019 03:33

    Автор оригинальной статьи ничего не понимает в объектно-ориентированном программировании. Зачем тогда обсуждать?


    1. tmteam
      20.05.2019 14:46

      Нагорело у всех.


  1. Whisky667
    20.05.2019 12:47
    +1

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

    Код пишется под задачу. ООП позволяет быстро и прозрачно прикручивать фичи. Это важно на реальных проектах, поэтому ООП популярно. Вне контекста задачи споры о том, хорошо ООП или плохо — как абстрактное сравнение эффективности молотка, и, например, зубила.


    1. PsyHaSTe
      20.05.2019 13:20
      +1

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