Comments 145
Давайте теперь каждый комментарий на 20 строк оформлять отдельной статьей!
Больше статей богу твиттерастатей.
В вашей статье лишь завязка, гипотеза. Не хватает разбора ошибок автора той самом статьи с точки зрения борьбы со сложностью, чтобы подкрепить гипотезу.
1. Он не дает четкого определения ООП.
2. Приводит примеры плохого ООП.
3. Не приводит ничего взамен, чтобы подход был равносилен ООП. То, что приведено в конце — это слишком специфично, чтобы можно было использовать во всех местах, где используется ООП.
Можно делать разбор каждого утверждения. Но автор неправ фундаментальным образом: он боролся не с ООП, а с неправильным использованием ООП. На основе этого сделал вывод про ООП.
И это не замечание вам, это проблема многих — у меня один коллега частенько использует словосочетание «управление сложностью» но на прямой вопрос «как измерить сложность, что сложнее А или Б» он либо затрудняет ответить, либо дает противоречивые ответы.
но на прямой вопрос «как измерить сложность, что сложнее А или Б» он либо затрудняет ответить, либо дает противоречивые ответы.
Внезапно, не существует точных инструментов и определения всех аспектов влияющих на сложность ПО, так же как и не существует точных едениц измерения сложности.
А вы можете, например, ответить на прямой вопрос — «Как измерить качество картины художника, что лучше, „Чёрный квадрат“ или „Терзания святого Антония“»?
Одним из важных моментов определяющих сложность программного кода — как сильно связан каждый отдельный модуль с другими модулями в системе. Чем меньше используемых зависимостей, поведение которых нужно учитывать при разборе поведения модуля, тем легче понять его, собственно, поведение.
Если вы определите какой результат вы хотите получить от действий с кодом/любой другой системой, введете «элементарные» по какому либо критерию операции над ней, и скажете что разницей в их сложности вы пренебрегаете — у вас появится инструмент для оценки сложности в контексте какой либо задачи — это просто количество элементарных операций для получения результата.
К сожалению с человеческими мозгами сложно определить множество элементарных операций и саму задачу. Все зависит от информации, которая содержится в этих мозгах.
Например, если вы занимаетесь дебаггингом, то в случае если вы знакомы с этим участком кода и знаете как оно должно работать, то ваша задача выглядит как: «Понять, что работает неправильно.» А еще вам будет легко «навигироваться» в этом участке.
Если вы не знаете как он работает, то к этому добавлятся: понять как работает код на этом участке и каковы ожидаемые результаты его работы. И навигация по коду, пока вы это делаете, уже не будет такой простой, вам придется тратить дополнительные силы на то, чтобы изучать новые для вас объекты и зависимости.
Уменьшение количества зависимостей тоже не всегда будет работать. Простой пример — если вы соберете все классы в один god object — вы уменьшите количесво зависимостей, так как сможете напрямую работать со всеми сущностями. Уменьшит ли это сложность?)
В целом, это даже возможно проиллюстрировать в тексте:
168150320941
168-150-320-941
Сообщение минимальной длины и колмогоровская сложость тоже работают не для всех типов задач, примерно по той же причине.
Я как-то пытался все это математически описать.
В общем, сложность в контексте взаимодействия человека и чего-либо — это очень сложно)
upd:
Хотя, у меня есть одна мысль как можно очень грубо описать нашу проблему. Представьте, что у вас очень маленькая оперативка, которая умеет хранить только примерно 4-8 указателей на области памяти, а все операции производятся на харде и только для двух областей памяти в один момент времени.
А, и да, результат операций, через некоторое время откатывается, поэтому для его сохранения надо переодически его повторять. Но есть вероятность, что он все же в какой-то момент сохранится.
Если интересно, можете почитать подробнее:
en.wikipedia.org/wiki/Long-term_depression
en.wikipedia.org/wiki/Long-term_potentiation
Вот примерно с такой «вычислительной машинкой» в виде мозга нам и приходится жить. Еще раз повторяю, это очень грубая и неточная аналогия, но помоему она дает некоторое представление.
У Вас же даже на этапе формулирования мыслей случаются, кхм… exception'ы.
Что касается управления сложностью, то она производится на этапе формулирования требований и дальнейшего проектирования решения. Никакой код не породит столько оверинжиниринга, как изначально неоптимальные требования, которые «не ложатся» ни на потребности бизнеса, ни на технические возможности целевой платформы.
Поэтому в хороших компаниях до программиста доходит оптимальным образом сформулированная задача, которую можно решать как ему больше нравится, пусть даже и на ООП его мечты.
А в плохих компаниях всё превращается в свалку — и документация, и код, и бизнес-процессы в итоге тоже. Кстати, требование в стиле «должно быть готово вчера» — это как раз маркер плохой компании с некомпетентным менеджментом. Программист, идущий на поводу у таких требований, развращает менеджмент и бизнес, демпингует и в итоге сам себя оставляет без штанов и без выходных.
Сейчас пойдет немного о содержании:
Главный аргумент всех сторонников ооп — просто у вас понимание ооп не то, ооп — это вот это и мне это нравится. Любые попытки переубедить ооп-чников кончаются тем, что тебе говорят, что ты просто не так понимаешь ооп. Причем все без исключений приводят разные понятия ооп. (Ну да, многие, конечно сходятся общих на аспектах: полиморфизм, инкапсуляция и тд). Также говорится о том, что в ооп записывают то, что не есть ооп — то, что с понятием объектно-ориентированное программирование связь имеет лишь в том, что может с ним использоваться (по-моему это как раз про контракты).
Я считаю, что даже если мы не имеем альтернатив, это вовсе не значит, что наше дальнейшее развитие должно быть прибито к ооп или что лучше уже не придумать. Много пустых споров рождаются всего на всего потому, что каждый под словом «ооп» видит свою боль. Просто иногда происходит диссонанс между дифирамбами о ооп, которые ты прочел в начале пути, и твоей постоянной болью… Просто нужно попробовать что-то новое (я не про существующие парадигмы как есть). Я не думаю, что после того, как мы забудем ооп наши проблемы решаться, нет. Я думаю это посыл к тем, кто потенциально может создать новую парадигму и язык для нее.
Это чем-то похоже на великих литературных классиков. Они поднимают важные проблемы, но навязывать свое мнение (как ты видишь решение, костыли также считаются) — моветон. Классиков в их время обычно критиковали за их взгляд на проблему, на которую у общества есть устойчивые убеждения. А потом мы говорим о том, что они были правы. Это, конечно, замечательно, но, ГДЕ МОЯ СЕРЕБРЯНАЯ ПУЛЯ? Ее не будет? Так что, оставить попытки ее найти?
Вот вам и ответ.
Скорее всего вы неправы — главное в ООП — это полиморфизм
Точно так же при разработке GUI очень удобно все компоненты объединять в хорошо продуманную иерархию.
Полиморфизм — это про систему типов. А в ООПроектировании и следующем за ним ООПрограммировании — главное грамотно структурировать объекты, чтобы не было лапши, на которую жаловались в предыдущей статье. И если посмотреть на C++, то там эта задача начала решаться далеко не сразу — без именных пространств все эти классы это действительно просто лапша. А вот при разработке Java эту проблему учли и использовали ООП подход — пакеты. И частично подумали про следующий уровень (jar-архивы и возможность подключения классов/jar-архивов с помощью настраиваемого classpath)… но это было не очень удобно… И при разработке системы сборки maven следующие разработчики учли следующий уровень проблемы — инкапсуляция этих пакетов в крупные пакеты (библиотеки), чтобы можно было удобно рулить зависимостями и сборкой.
А поверх этого теперь ещё микросервисы/докеры/контейнеры/прочая инфраструктурная сложность добавилась, чтобы разделить сложность на очередные планы.
Это называется не "без ООП", а "эмулируя ООП".
А чем принципиально абстрактный класс и 10 наследников отличаются от объявления типа и 10ти функций?
Но зачем при замене объекта удалять его из ArrayList с последующей вставкой нового? Почему нельзя просто заменить объект по известному индексу?
Если мне нужно скажем PowerBonus отрисовать, не нужно открывать и править все 10 классов, если они в отдельных файлах, как в джаве
А может, достаточно не складывать их все в отдельные файлы?
О боже… и такие граждане спорят об ООП… Выучите пожалуйста сперва матчасть!
ArrayList хорош при добавлении в конец, чтении и произвольному доступу по индексу, как только возникает необходимость динамически удалять/добавлять объект — выкидываем нафиг ArrayList и заводим LinkedList. Да у нас будут большие проблемы при работе с индексами… но как часто вы их используете-то? В 5% случаев в лучшем случае они нужны, в остальных хватает простого итератора по списку.
Далее "уничтожаем, сдвигается" — а нафига ж удалять-то вообще из списка, если мы тут же вставляем другой объект? Взяли да вставили новый на место старого, а старый GC и без вас прекрасно подберёт. И тогда и от ArrayList-а отказываться становится без надобности.
По удобству — писанины меньше и всё на виду.
По удобству — писанины-то может и меньше, а вот контроль типов утерян напроч… и зачем тогда морока с типизированым языком? JS — ваше всё. А нам дайте работу с типизированными языками, которые путём добавление небольшого количества бойлерплейта защищает от кучи глупых ошибок.
Если мне нужно скажем PowerBonus отрисовать, не нужно открывать и править все 10 классов, если они в отдельных файлах, как в джаве.
Ну так вынесите свой PowerBonus в родительский-абстрактный для этих 10 классов класс… а ещё можно вынести его в утилитарный и ещё целая куча альтернатив. И да Java требует писать бойлерплейта в среднем больше других языков, но меня вот это мало смущает — взамен достаточно многое даётся.
В вот в ООП ИМХО главное — это идея инкапсуляции.
Если чуть подробнее, то разделение на данные — т.е. память без поведения (числа, строки, даты и т.д.) и на объекты — память, которая имеет поведение, т.е. у него есть набор функций, которыми с ним можно взаимодействовать — интерфейс, но при этом к внутренностям которого никто не должен иметь доступа (кроме самого объекта, естессно).
Да, иногда явного принуждения в языке нет и можно сделать объект без поведения (ака структуры), к данным которого можно получить доступ.
Но это не умаляет мощь концепции — если объекты А и Б удовлетворяют одному и тому же интерфейсу В, то их не просто можно обрабатывать единообразным образом как В, а это будет даже явно задано (потому что вы не имеете доступа к внутренностям реализации).
можно делать кучей способов, и без ООПМожно пример? Сразу говорю, если там есть указатели на функции в составе структуры — то это просто велосипедный способ использовать ООП.
Что касается инкапсуляции, то она есть в чистом C, и там она сильнее чем в C++.
Например, через протоколы вы можете достигать полиморфизма даже от типов, к реализации которых не имеете доступа.
Пример: https://medium.com/everydayhero-engineering/extensibility-in-elixir-using-protocols-2e8fb0a35c48
Ещё один вариант: Type Classes в Haskell.
Одной инкапсуляции мало — вы правы.
ООП = обмен сообщениями + инкапсуляция + максимально позднее связывание.
А вот полиморфизм вообще к ООП никакого отношения не имеет.
PS: Я не могу говорить за А.Кея, но ИМХО это был способ «помирить»
подход императивщиков, когда "все — данные" (с одной стороны это так и есть, ведь даже без «гарвардской архитектуры», на уровне железа все равно будет что число — это число, указатель — это число, строка — это несколько чисел, дерево — это несколько чисел, и в итоге функция — это тоже числа),
и функциональщиков, у которых "все — функции" (и даже числа они строят из лямбда функций).
И, хотя на практике различают объекты от например модулей (например для удобства хранения\обновления\загрузки и т.д.), но теоретические построения у них одинаковые: реализация скрыта, состояние напрямую недоступно, но оно хранится в памяти и мутабельно — общайтесь через интерфейс.
Более прокачанный и в процедурном стиле прекрасно напишет код без глобальных переменных, с идемпотентными процедурами и внятным интерфейсом.
Прокачка важнее идеологии.
З.Ы. про процедурный код с идемпотентными процедурами и без глобальных переменных: «Гладко было на бумаге, да забыли про овраги...»(с). Хороша теория, прекрасны подходы, да вот не всякая задача ложится в чистую и незамутнённую теорию и подходы, а вовремя остановиться и сказать, что вот тут нужно применить другой подход и иные инструменты, нужна не только большая голова но и серьёзный дан в разработке. А написать дрянь можно на всех языках, и процедурщину рефакторить тоже удовольствие спорное. Запутанную процедурщину рефакторить вообще талант Шерлока Холмса нужен.
Слышал мнение (от препода по ООП), что полиморфизм вообще не является столпом ООП. А главные принципы: инкапсуляция, модульность, иерархичность, и что-то ещё (то ли повторное использование, то ли посылка сообщений), но полиморфизм не обязателен.
Прочитал комментарии и решил дать ответ на все сразу с упором на ваш.
Я тоже преподаватель — поэтому получается мнение одного преподавателя на мнение другого преподавателя, хотя думаю между мной и неизвестным мне преподавателем есть разница в том, что я стал заниматься профессиональным программированием с 1975 года, и использовал почти все более-менее значимые языки от алгола до, скажем, котлина.
А теперь по существу.
- Любое программирование предполагает представление данных в структуры. Для обработки структур используются методы. Удобно структуры и методы для их обработки располагать в одном модуле, другими словами — это организация текста.
- Иерархичное расположение структур позволяет сократить описание как структур, так и методов.
Таким образом, инкапсуляция и наследование не несет особых объектных принципов — можно сложить все в один монолитный модуль, а можно разложить по разным мелким модулям. - Полиморфизм предполагает подняться над объектами, создавая полиморфные методы
Это и есть программирование, как описание поведения объектов в ходе выполнения
Удобно структуры и методы для их обработки располагать в одном модуле
Лишь до тех пор, пока вы не начали писать хоть что-то нетривиальное. Когда одна и та же структура в разных контекстах обладает совершенно разным поведением, пихать все методы в один файл становится преступлением. В лучшем случае вы такие структуры продублируете аля Bounded Context, в худшем — получите классический God Object аля Active Record.
Если бы вы изначально проектировали от поведения, а не от структур, таких проблем бы даже не возникло. Но в этом случае вам бы даже в страшном сне не приснилось структуры и методы для их обработки надо располагать в одном модуле.
Иерархичное расположение структур позволяет сократить описание как структур, так и методов.
Сокращение описания структур — так себе цель. Во-первых, если у вас в структуре 30+ полей, а вы ещё от неё и наследуете, и полей добавляете — это уже плохо пахнет. Лучше подумать над доменной моделью, чем экономить десяток строк.
А полиморфизм для чего? Для упрощения же. Нет?
Нет — полиморфизм для реализации общих методов для разных объектов (разных по структуре).
Согласно исходной идее объекты существуют в среде сообщений — каждый объект обрабатывает одно и тоже сообщение (скажем, вызов метода) по своему
То есть суть не в простоте, а выразительности кода — одним полиморфным методом охватывается управление различными объектами
каждый объект обрабатывает одно и тоже сообщение (скажем, вызов метода) по своему
То есть суть не в простоте, а выразительности кода — одним полиморфным методом охватывается управление различными объектами
И? А для чего всё это? Это и есть упрощение.
Но это не точно, было бы интересно почитать на эту тему.
я слышал легенду (действительно не уверен, что это правда), что ООП был создан, чтобы можно было понятно интерпретировать бизнес-процессы в код. Ну, чтобы объектам реального мира соответствовали объекты кода с должным уровнем асбтракции. И взаимодействовали они между собой, как объекты реального мира с должным уровнем абстракции.
Не было такого )
Но это не точно, было бы интересно почитать на эту тему.
Доклад Алана Кея, очень хороший — www.youtube.com/watch?v=fhOHn9TClXY&list=LLd6OFj5xQf9ZhwBb4EVbdSw&index=33
Пара его писем userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en
lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html
Алан Кей, если что и ввел в своё время термин ООП
Вообще-то этим занимается UML
Процедуры и библиотеки с повторно используемым кодом были ещё в ассемблере в 60е если не раньше. А о том, что удобно делать изолированные «кубики» и из них собирать механизмы люди додумались ещё в Древнем Египте, за 5000 лет до ООП.
ООП это про посылку сообщений, то есть о позднем связывании, о полиморфизме. Ещё о состоянии, то есть приёмник сообщения на один и тот же запрос может по разному отвечать.
Далее утверждается, что это удобно. Правда не уточняется кому и для чего это удобно. Вроде как все должно быть очевидно. Новация очевидна, простота на лицо, всем идеи нравятся. В итоге имеем то, что имеем.
И тут умные люди придумали: а давайте изолируем части так, чтобы была важна внешняя часть, а не то, как это внутри устроено.
Это же называется абстракция. Строго говоря, это не уникальная фича ООП (как и вообще программирования), этот принцип успешно применялся и раньше.
DI, иммутабельность, монады
ООП ничего не противоречит, включая ФП и процедурное программирование. Речь о "классическом" ооп — объекты с мутабельным состоянием, инкапсулирующие в себе поведение.
Мне больше интересно, а какие реальные альтернативы ООП для энтерпрайза предлагаются?
Прекратить искать подход который разом решит все проблемы энтерпрайза.
Дело в том, что класс/объект никогда не должен строиться вокруг данных, он должен строиться вокруг поведения — одной определённой ответственности. Простейшее утверждение! При этом одно оно позволит избежать практически всех антипаттернов ООП. А большинство паттернов — просто следствия из него.
Проблема только в том, что большинство ЯП, считающихся объектно-ориентированными, совершают фундаментальную ошибку, создавая классы под обычные типы данных прямо в стандартной библиотеке. Из-за этого у людей путаница, они не понимают границ между слоем архитектуры и слоем реализации. Подробнее можно почитать тут и в комментариях.
Да и для «поведения» классическая модель вызова методов не идеальна. Например (по мотивам первой статьи) — если у вас есть целая битва, в которой герои колотят друг друга, вам придется применять мультипоточность и бороться с ее побочными эффектами. В модели «очереди сообщений», например — такого не происходит.
Так в этом и дело, что когда объекты общаются посредством отсылки сообщений (как это в определении ООП было заложено), то у каждого объекта есть своя «очередь сообщений».
Проблема что по факту это реализовано только в модели акторов. А ООЯП на классах и вызовах методов изначально свернули не туда. В результате знаменитые холивары на темы типа "принтер печатает строку" vs "строка печатается на принтере".
Это не так просто, а часто и невозможно.
Я бы сказал, что это просто непривычно.
очень часто требуется анализ данных, полученных не в текущей сессии и накопленных в каком-то хранилище.
Это да. Но вам никто не мешает реализовать объект, получающий нужные данные из внешнего хранилища. Главное — не забыть, что никакой другой ответственности у него уже не должно быть, а преобразованием, обработкой, сохранением этих данных должны другие объекты заниматься.
В результате знаменитые холивары на темы типа «принтер печатает строку» vs «строка печатается на принтере».
Значит ли это, что обьекты с методами никогда не содержат состояние? Те же акторы держат в себе данные.
Нет, не значит. Иметь состояние — это нормально и для актора и для объекта. А вот что именно будет его состоянием зависит исключительно от роли, которую он выполняет.
Другими словами, не "данные и методы работы с ними", а "методы, реализующие определенную ответственность, и данные, необходимые для выполнения этой ответственности".
Звучит как будто разница не велика, но по факту разница огромна.
Первый подход: у нас есть деревянный брусок, давайте опишем всё (методы), что можно с ним сделать.
Второй подход: нам надо сделать скалку, давайте подумаем что (какие данные) нам для этого нужно.
никто не мешает реализовать объект, получающий нужные данные из внешнего хранилища
Это одна из проблем ООП — что «настоящим» обьектам, представляющим данные, постоянно нужны какие-то «вспомогательные». И если в постановке задачи про строку и принтер — и строк и принтеров будет несколько, у изрядной части программистов получится «менеджер заставляет принтер печатать строку».
"Настоящим" объектам как раз обычно "вспомогательные" не нужны. Они нужны людям, которые хотят, например, видеть определённый срез состояния "настоящих", хотят чтобы между выключением и включением компьютера "настоящие" сохраняли своё состояние, что они могли синхронизироваться по сети и прочие странные хотелки :)
И будет "пользователь хочет распечатать какую-то сроку на каком-то принтере, для чего менеджер определяет какую и на каком и сообщает о желании пользователя объекту, представляющему принтер"
«настоящим» обьектам, представляющим данные
Почитайте ветку чуть выше. Я начал как раз с того, что настоящие объекты не должны представлять данные. И им не нужны вспомогательные объекты, им нужен интерфейс для получения нужных данных. А кто там выступит в роли провайдера этих данных, будет ли он один или несколько, не так уж важно.
По сути любая программа — это тупо набор конвейерных линий по обработке данных. Вы получаете пользовательский ввод, как-то его обрабатываете, в результате этой обработки что-то идёт на склад (в БД), а что-то отдаётся обратно пользователю.
А объекты — это рабочие на этих конвейерах, к ним приходят одни данные — уходят другие (обработанные) и отправляются к следующим объектам.
А какое из определений ООП вы считаете классическим?
P.S. Мне кажется, с ответа на этот вопрос должна любая статья про ООП начинаться :-)
Про такое я что-то не слышал… можно целиком его привести? А то непонятно, что такое "сущности из области определения".
Да и метод, вроде как, не может представлять взаимодействие между объектами, т.к. он принадлежит одному из объектов. Другими словами, метод максимум может быть частью интерфейса возможного воздействия на объект (однако, это однонаправленное понятие, в отличии от взаимодействия). Но и то публичные методы — необязательная часть, т.к. объект может и активную "жизненную позицию" занимать и сам откуда-нибудь брать себе работу (из очереди например), без внешнего обращения к нему.
Класс описывает свойства и методы, которые будут доступны у объекта, построенного по описанию, заложенному в классе. Экземпляры используются для представления (моделирования) конкретных сущностей реального мира
метод состоит из некоторого количества операторов для выполнения какого-то действия
Опять же — очереди, диспетчеры, процессоры и т.п. — это «вспомогательные» обьекты. ООП говорит о моделировании сущностей и взаимодействий между ними — и ничего не говорит об инфраструктуре, требуемой для реализации этих взаимодействий. С точки зрения ООП тот же Телеграм — это пользователи, каналы, сообщения. А то, что там есть еще серверы, файрволлы, CDN, базы данных и т.п. — для ООП это некое абстрактное «облако».
Погодите, какая разница, что там описывает класс, если классы — это необязательная часть ООП. Это просто один из вариантов реализации.
Отсюда и все недопонимания. Если хотите руководствоваться википедией в этих вопросах, то советую читать английскую. В русской слишком много некорректных формулировок.
А классическое определение ООП, если что, выглядит так:
- Everything is an object.
- Objects communicate by sending and receiving messages (in terms of objects).
- Objects have their own memory (in terms of objects).
Про другие определения можно применять эпитеты "популярное", "распространённое" и т.д. Но классическое есть только одно.
Чем больше читаю про ООП, тем больше возникает ощущение, что ООП понимают не только лишь все.
Более того, это может являться критерием правильности выбора парадигмы: насколько просто и понятным оказывается итоговый код.
Вы хотели сказать, что т.к. ООП понятно и просто «не только лишь» всем, то парадигма ООП не очень то правильная?
Существенная часть кода — это рекурсивный обход леса деревьев с преобразованиями поддеревьев. Например для вычисления выражений с константой, разворачивания циклов и рекурсии итд.
Допустим у вас есть 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.
Если у вас есть топор, то у вас есть просто одно движение, типа:
«хрясь»
Если же вы решили воспользоваться ножом, то вы можете, например:
«пилить»
И при этом будете делать все 200 перепиливаний ножом.
Как вы будете пилить 200 бревен? Группировать кучками? Или раскидаете?
Нож годится для определенных действий, например, резка картофеля. В других сценариях он приводит к тому, что люди занимаются не полезной работой, а перепиливанием бревен, из-за чего получаются медленные результаты.
Disclaimer: я использую нож и топор с 1993 года, и пришел к выводу о необходимости использовать нож сдержанно после года через три после начала его использования. Желание наплодить картофелин — это желание либо новичка, либо человека, который любит жареную картошку или то, что похоже на жареную картошку. Все, кто имеет дело с бревнами — скажи нет ножу, даже если это ювелирная резьба по дереву.
Ну и в моём представлении ООП — это надстройка над процедурным подходом, разрешающая дополнительные действия, но не запрещающая процедурные.
Каждый алгоритм делает операцию не над одним узлом, а над поддеревом дерева. Т.е. общая функция visitAll обходит дерево и потом делает операцию не над текущим узлом, а над узлом и его соседями. Например один алгоритм при обходе заменяет все *p = *p + 1 на (*p)++ и *p = *p — 1 на (*p)--, а другой f (*a = b) на *a = b; f (b).
Т.е. ему нужно смотреть не только на node, но и на node.left, node.left.left, node.right, node.right.left и node.right.right. А потом переходить к другому узлу и тоже смотреть на соседей.
А теперь перефразирую свой коммент:
Так как алгоритмов 200, а типов узлов 100, и каждый алгоритм должен поддерживать все 100 типов узлов, то перегруженных функций «do» будет очень много, вплоть до 20000, так как в каждом из 100 классов узлов будет до 200 функций do_XXX (хотя большинство таких функций будут пустыми (т.е. можно использовать функцию base класса), но и непустых будет много.
Как расбросать эти много функций по файлам, чтобы сделать код читабельным? Группировать по алгоритмам или по классам? Если по алгоритмам, то вы раскидаете один класс по 200 файлам, что просто увеличивает количество набитого вами кода и никак не помогает уменьшить сложность. Если по классам, то оно резко увеличивает сложность, так как теперь, чтобы понять каждый алгоритм, программисту нужно будет просматривать не один, а 100 файлов.
Каждый алгоритм делает операцию не над одним узлом, а над поддеревом дерева. Т.е. общая функция visitAll обходит дерево и потом делает операцию не над текущим узлом, а над узлом и его соседями.…
Т.е. ему нужно смотреть не только на node, но и на node.left, node.left.left, node.right, node.right.left и node.right.right. А потом переходить к другому узлу и тоже смотреть на соседей.
Ну, если эту функцию вы добавите в узел, скорее всего это будет неправильное ООП. Она, скорее всего, не является методом узла, т.к. просматривает преимущественно другие узлы (другие объекты).
Более правильно, имхо, выделить её в какой-то объект SpecificVisitor, если нужно сохранение состояния между вызовами (и тогда вы останетесь в парадигме ООП), или оставить функцией, но не методом объекта (в этом случае ООП не нужно).
Как расбросать эти много функций по файлам, чтобы сделать код читабельным?
YuriPanchul, у вас ошибка на уровне проектирования.
А если бы было не 200 * 100, а 200 * 300 * 400?
Такие вещи не пишутся и не поддерживаются «вручную».
Поэтому вопрос о том, как и что тут группировать в какие файлы излишен.
хотя большинство таких функций будут пустыми
Явный указатель на то, что «что-то идёт не так и пытаться описать и поддерживать это вручную на базе ООП-модели, да еще и зачем-то отдельными файлами ошибочно.
Кроме того, судя по написанному выше, у вас давняя и глобальная ошибка понимания
ООП, извините.
ООП это не:
ClassForGraphNodeOne
ClassForGraphNodeTwo
ClassForGraphNodeThree
ClassForGraphNodeFour
…
ClassForGraphNodeThousand // Тысячный класс для тысячного типа ноды.
Я встречал такое применение ООП и это ошибка.
Не надо лепить статическую (под)типизацию классами языка.
Классы/объекты предназначены не для этого.
ООП это:
NodeClass {
field_for_node_type // here one, two, three, etc…
}
то есть один класс для ноды (она же point, она же vertex)
EdgeClass {
field_list_for_nodes
}
второй класс для рёбер (они же линки, стрелки или дуги)
Весь софтвер, которые делает сложную работу с графами — just say NO to OOP.
В случае графов, нужно около двух фундаментальных классов, а не десятки, не сотни и не тысячи одних и тех же типов, как вы это зачем-то делаете.
Вашу исходную задачу из первого поста выше нужно пытаться решать или тектовой кодогенерацией, или же выстраивать механизм обработки динамически, в памяти.
Но плодить пересечения из сотен классов и сотен методов и писать это руками — не надо так программировать.
И не только в парсерах и компиляторах. Я видел трех студентов с горящими от ООП глазами, которые написали объектно-ориентированный cycle-accurate симулятор процессора, в котором было на порядок больше кода, чем надо, а при изменении конвейера массажировать всю иерархию классов было невозможно. Студенты из Беркли пришли кстати.
Просто книжки по ООП и C++ имхо естественно толкают программиста пройти через полгода избыточного дробления на ненужные классы, с цепочками указателей друг на другу. С обычным структурным программированием и модульностью это не так.
И вот если эти полгода приходятся на начало коммерческого проекта, а не пробно-студенческого, то проекту швах.
В целом же да, вы совершенно правы. ООП и базовая литература о нем толкает начинающих делать бессмысленные иерархии классов и много других бессмысленных вещей (например класс — структуру данных и десятки getter-setter методов вместо того чтобы безбоязненно создать struct).
К этому добавляется юношеский запал, энергию девать некуда. Плюс, для многих это составляет своего рода занятную задачу, описать всё классами, а потому их надо наплодить как можно больше и некоторым явно доставляет удовольствие заниматься «классотворчеством».
Только полгода слишком оптимистично. Обычно больше, года 3, а то и 5-7 лет прежде чем получается нормальный разраб с умеренными представляниями проектирования. А всё потому что изучить механизмы ООП — этого мало. Надо научиться их грамотно применять, структурировать проект, а это уже уровень выше — уровень проектирования, а до него надо еще суметь добраться. Молодым на нём обычно скучно, «зачем читать эту скукоту, я и сам смогу!» :)
И вот если эти полгода приходятся на начало коммерческого проекта, а не пробно-студенческого, то проекту швах.Совершенно верно. Надо все-таки чтобы хотя бы «на бумаге» структуру накидал дядька постарше, а студни-аспиранты пусть заполнят предолженный каркас. Иначе действительно, добра не жди, в этом я с вами совершенно согласен.
Вот и получается что и тут всё как везде, без изменений. Хоть это будет software development «фабрика», или это будет завод по выпуску токарных изделий или автомастерская. Внезапно, еще одно знание, уже уровня людской организации. Как на старом добром призводстве электроники, нужен «мастер» и вокруг него подмастерья. Нужен «начальник цеха» и «старший инженер». И это только производственная часть.
Нужен генеральный конструктор и к нему- инженеры-разработчики. Нужны наладчики. Да и отдел контроля (сиречь тестирования) внезапно не помешают. Вот тогда, считаю, будет толк. А набить десятки комнатух одними только подмастерьями, как сейчас делают… Результат будет ожидаемым, спасает только то что продукция годами допиливается до новых версий.
Если ООП у вас увеличивает сложность — вы неправильно его используете (конкретнее: используете там, где не надо).
OOP годится для определенных программ, например для GUI. В других программах он приводит к тому, что программисты занимаются не полезной работой, а борьбой за/против OOP, из-за чего получаются непонятные, большие, медленные программы.
Вы использовали какие-нибудь сторонние библиотеки на C, кроме GUI? Даже мало-мальски сложные из них (zlib, libcrypto, lua) имеют ООП-подобные API, где создаются какие-то объекты, и для них вызываются методы. Даже в библиотеке apache commons math ООП там, где оно используется (а это не везде, у них довольно разумный подход), выглядит уместно.
Если по классам, то оно резко увеличивает сложность, так как теперь, чтобы понять каждый алгоритм, программисту нужно будет просматривать не один, а 100 файлов.
Не лучше ли просмотреть документацию, чем реализацию алгоритма? Если вы каждый раз смотрите реализацию алгоритма и у вас всё нормально — у вас не слишком сложная система по сравнению с другими, где без ООП действительно не обойтись.
Весь софтвер, которые делает сложную работу с графами — just say NO to OOP.
Можно ещё примеры, кроме компилятора? Я где-то слышал, что на некоторых проектах работа с БД может быть слишком многообразной, чтобы представлять сущности в виде объектов.
Следующее
То что вы описали это модульность, не OOP.
OOP это один специфический тип модульности.
Сдается мне, автор все перевернул с ног на голову, фактически придумав конспиративную торию зарождения ООП. В общем, вот моя версия:
Вы обвиняете автора в выдумывании понятия а далее сами выдумаваете ещё одно, вместо того чтобы обратиться к первоисточникам.
Алан Кей происторию создания ООП, и пара его писем, если интересно:
userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en
lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html
Печально то, что это известно лишь по письмам. И по «Ранней истории смолтолк».
Если бы автор удосужился вовремя это объяснить, возможно все пошло бы другим лучшим путем.
Печально то, что это известно лишь по письмам.
Это да, согласен что одна из причин такого искажения понятия — отсутствие четкого определения и примеров даже от его автора, ну и ещё он писал об этом:
If you focus on just messaging — and realize that a good metasystem can
late bind the various 2nd level architectures used in objects — then much
of the language-, UI-, and OS based discussions on this thread are really
quite moot. This was why I complained at the last OOPSLA that — whereas at
PARC we changed Smalltalk constantly, treating it always as a work in
progress — when ST hit the larger world, it was pretty much taken as
«something just to be learned», as though it were Pascal or Algol.
Smalltalk-80 never really was mutated into the next better versions of OOP.
Given the current low state of programming in general, I think this is a
real mistake.
И
Smalltalk is not only NOT its syntax or the class
library, it is not even about classes. I'm sorry that I long ago coined the
term «objects» for this topic because it gets many people to focus on the
lesser idea.
The big idea is «messaging»
Вообще, на эту тему есть информация в интернете, и есть доклады Кея на ютубе. При желании, не составляет труда найти что называет этим термином Кей.
Думаю основная проблема по прежнему в людях, которые стали использовать «модное» понятие чтобы продвинуть себя/свой продукт, а после пытаться найти в нем серебрянную пулю, решение от всех проблем, пихать его везде, куда можно, при этом даже не удосуживаясь разобраться что это такое и зачем нужно. И проблема это существует с любыми популярными понятиями, люди пытаются запихнуть в свой код паттерны, пытаться сделать «всё по SOLID», «по MVC», даже не задумываясь о конкретном влиянии их действий на качество, в данном случае, кода, и реальный смысл и идеи популярных терминов.
Как пример явного искажения термина, имеющего достаточно четкое и понятное определение — REST, было описано что это архитектурный стиль, набор ограничений при разработке клиент-серверных приложений, решающий определенные проблемы.
Что мы видим сейчас — люди кличут REST любую http апишку, которая, чаще всего, просто принимает и отдаёт json, и выполняет процедуры по запросы. Статья на хабре, Дока спринга, куча вопросов на тостере, S/O, более поздних статей на хабре тому подтверждение.
Даже попытка автора термина, Роя Филдинга, напомнить людям что то что любая http апишка не является REST успехом, вобщем то, не увенчалась — roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven, мода ведь, синдром утенка проявляющийся у новичков, которые уже «выучили» что такое REST по приведенным выше стаье с хабра/spring.io и подобных
Каждый подход характеризуется своим набором приемов, но если вы используете неизменяемые объекты — это ещё не значит, что это функциональное программирование.
Основное в каждой парадигме — это абстракция моделирования, а не приемы и организация кода.
То есть, если у меня есть персонаж в игре, который умеет что-то делать, то это ООП, он хранит свои данные и умеет выполнять какие-то действия, которые основаны на этих данных и на входящих параметрах (или сообщениях, если кому-то принципиально смотреть на это именно так).
Но если у нас есть MVC в вебе в каком-то его виде, то Контроллер — это просто группа методов, и то, что они представлены классом — это просто организация кода.
Вот уже в Модель можно что-то моделировать на основе ООП, если в этом есть смысл.
Но объективно, в большей части ситуаций действующее лицо — это "Deus ex machina", который уже управляет группой связанных объектов, который мы обычно и называем уровнем Сервиса, тогда мы приходим к тому, что данные отдельно, функции отдельно и это уже не ООП.
Я лично полностью согласен с автором. «Плоская» структура С-кода даёт гораздо больше свободы и возможностей, чем С++ или Java аналоги.
Ядро Линукса, кстати, несмотря на используемый язык насквозь объектно-ориентированно.
… limiting your project to C means that people don’t screw that up, and also means that you get a lot of programmers that do actually understand low-level issues and don’t screw things up with any idiotic “object model” crap.
Лично мне в этот цитате Линуса сложно заметить любовь к ООП. Конечно, некоторые адепты ООП могут искать в ядре Линукса следы «сквозного ООП», но как мы знаем при желании и при наличии определённой доли фантазии можно найти что угодно и где угодно. А где подтверждение любви к ООП и его использования в Linux Kernel от самого автора?
Не знаю насчёт фантазий и любви, но как вы думаете, откуда взялось название структуры struct kobject
и почему она так часто используется?
А я предпочитаю смотреть код, а не читать чужие мнения...
Когда я стал читать книги по паттернам, то оказалось, что к некоторым из них я сам пришёл задолго до чтения. И авторы сборников паттернов, глядя на мой код, вполне могли сказать "да, это ещё один пример этого паттерна, только названия не как у нас".
Я к тому, что к вопросу о том является ли Линукс примером ООП надо обращаться к экспертам по ООП. Может Линус и не подозревает, что "всю жизнь разговаривал прозой", а может подозревает, но не хочет это принимать :)
Например, я написал код, где встречаются элементы ООП, элементы ФП и какие-то общеизвестные паттерны. Но я назвал это MyVeryOwnParadigm.
Вопрос — корректно ли мне навязывать мнения типа «да ты просто изобрёл велосипед, это тот же ООП»?
Лично я считаю, что автор сам в праве определять как называть и классифицировать его творение. Если он не хочет, чтобы его OOP-like-Paradigm называли ООП, то зачем так делать?
Вам навязывать некорректно, но приводить другим ваш код в качестве оригинальной интерпретации идей ООП — почему нет?
Даже если этот пример будет приводить автор ООП это будет не авторитетно? :)
Хотя, конечно, зависит от его формулировок. Одно дело "хотя автор этого кода явно исказил мои идеи ООП, но это всё равно пускай плохой, но ООП код" и совсем другое "автору этого кода явно приходили в голову те же базовые идеи, что и мне, поэтому этот код можно считать примером ООП, хотя развил он эти идеи в другом направлени".
физики: мы пишем на C#, у нас контроллеры — просто мешок процедур, а на выход-выход — голые DTO
лирики: ну так это ООП, просто вы не используете полиморфизм и инкапсуляцию. А если бы использовали — у вас было бы все сильно лучше
физики: вот в Haskell никакого ООП нет, и живут люди
лирики: тип данных — это инкапсуляция, классы типов — полиморфизм, вы пишете на ООП
Ну и так далее.
У меня вот в коллективе, термин «ООП» вообще несёт смысл «малолетние дурачки придумали архитектуру». Ну типа «я глянул к ним в код, там полный ООП» — значит код раздули кучей абстракций, не несущих никакой пользы.
ООП понимают не только лишь все
Ну, скучно же, ребята!!! Один пишет про ошибки, допущенные другим в статье про ошибки третьих… спор о том, кто больше неправ продолжился в комментариях. Ну неправ, и что? Лучше бы написали о каком-нибудь крутом и красивом решении, которое показывает как XXX парадигма расширяет наш инструментарий, да с примерами, да так чтобы захотелось попробовать! А то "тут ООП, тут не ООП..." Как говорил Жванецкий: "Воруйте с прибылей!"
Так это же называется модульность, а не ООП?
Именно это называется ООП, и именно изоляция сложности привела, в каком-то смысле, к революции, т.к. стало возможным писать гораздо более сложные программы.
Почему! Почему? С изоляцией сложности, стало писать сложнее? Требуется больше теорий и прочего. Или это сформировавшийся стереотип вокруг ООП, что для ООП нужно знать ООП, а не только как оно ложиться, обертывая обычное функциональное программирование и всю ту же теорию?
:)
Без изоляции сложности сложно написать сложную программу. :)
Основная, наверное, цель ООП — путём изоляции сложности упростить написание сложных программ.
И, кстати, лично я активно использую смесь процедурного и ОО программирования: точка входа — скрипт, который создаёт небольшой (для уровня скрипта) набор объектов и дёргает их методы, не "подозревая", что за ними скрываются графы объектов из тысяч вершин. Это изоляция ООП? :) На некоторых ООП языках сделать такого не получится и придётся оборачивать подобные процедуры в, например, классы-обёртки со статическими методами.
ООП — один из возможных способов изоляции. Почему-то на данный момент он, имхо, самый популярный. Как по мне, то из-за хорошего баланса порога вхождения (в версиях теперь уже мэйнстрим-языков) и уровня возможной изоляции.
Уж взялся писать, так пиши нормально. Сейчас это просто комментарий.
… всю статью можно перечеркнуть буквально следующим.
Следующее
(непереводимая игра слов)
Это фиаско, бро. Тут даже обсуждать нечего… А я так надеялся (
То, во что превратилось ООП сегодня, является следствием непонимания целей разработки софта: борьба со сложностью.
А мне казалось, что целью разработки ПО является не борьба со сложностью, а решение реальных задач. И часто сложные задачи для их решения требуют, увы, сложного ПО.
Высокая сложность и есть проблема возникающая при разработке сложного ПО, поэтому управление сложностью и встаёт, по сути, на первое место, реализовать фичу А — не проблема. Реализовать фичу А в уже написанном и сложном приложении — проблема.
p.s. Речи про специфичные области где главное фичу реализовать, а код и переписать кучу раз можно, не идёт.
Хотел бы посмотреть на лучший ООП код автора статьи
Автор статьи должен специально для статьи какой-нибудь проект на несколько лет человеко-часов сделать чтобы доказать что в интернете кто-то не прав?
В этом нет особого смысла, потому что это будет лишь попытка что-то доказать людям которые не разбираются в вопросе исключительно из-за своего нежелания в нем разбираться, и если красивым и убедительным словцом навязать им новую парадигму, какой бы хорошей она не была, без усилий этих людей, писать лучший код они не станут.
Материалов то за пол века для изучения накопилось предостаточно.
Возможно те, кто действительно хочет разобраться в теме обратят внимание что стоило бы послушать Армстронга И Кея, а не только сомнительного Поляка который пилит блокчейн на Rust`е.
Ну и да, полностью согласен с автором, что спорить «X vs Y», или «X — хорошо, нет, или не очень?» не имея формального определения X бессмысленно, а именно так и выглядит большинство споров на текущий момент:
— «Я считаю что ООП не очень из-за наследования»
— «Но ведь в ООП не обязательно наследование»
— …
Всегда пожалуйста:
- Synca: https://github.com/gridem/Synca
- Subjector: https://github.com/gridem/Subjector. См. также [2] Асинхронность 3: Субъекторная модель
Допустим, я увидел прикольный завуалированный способ объявлять глобальные переменные. Конечно, все обвешано шаблонами, фиктивными параметрами, чтобы наплодить возможность реализации одной хрени для разных типов. Но в какой момент эта штука возьмет и начнет давать преимущество огромному проекту, в котором десяток программистов и не только лишь все такого же уровня, как я, и мало кто такого, как Вы? И конечно, этот синглтон станут использовать сначала правильно — пока Вы смотрите, но как только Вы отворачиваетесь — его начинают использовать для любой хрени. Глобальные переменные же плохо! Давайте спрячем их, завуалируем и сделаем так, что хрен пойми когда они будут инициализироваться. Кто первый дернул за веревочку, тот и прав. Пусть еще это будет размазано по коду, запрятано в библиотеки. Постепенно, проект выйдет из под Вашего контроля и черти в аду перекрестятся, когда им предложат этот код для в качестве орудия пыток программистов-грешников. А между тем, мы обсудили всего несколько строчек такой «архитектуры». И даже не начали обсуждать ООП, к которому, я уверен, в таком простом проекте у меня не будет претензий. Но все мы знаем, что в действительности все не так, как на самом деле.
Про ООП