Как стать автором
Обновить

Комментарии 335

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

Во-вторых, как наличие образовательной программы курса/книжки, использующей модель аэропорта как какой-то учебный проект, делает ООП ОБЯЗАННЫМ решать ВСЕ вопросы? Метод вам вообще ничего не должен, кроме как решать поставленную перед ним абстрактную задачу - вы же не говорите, что какой-то мат. метод обязан решать ВСЕ задачи. (Оптимальность и корректность выбора метода полностью лежит на том, кто метод выбирает и использует)

В-третьих, описание классами процессов, да, вы, верно, просто быстро писали и имели ввиду иное, однако описание ПРОЦЕССОВ классами, конечно, иногда удобно, но обычно говорят о каких-либо акторах, сущностях и тп. Т.е. нет класса "процесс регистрации", обычно есть класс "регистрационная форма" или "регистрационная стойка" с соотв. методами.

Некоторые люди не понимают, что программист это инженер.

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

Усложнять, то что должно упрощать жизнь, это не инженерный подход.

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

описать классами все процессы аэропорта

Хороший тамада. И конкурсы интересные

ООП это всего лишь инструмент

а может это даже и не инструмент, а метод, в смысле способ?

Способ был бы, если бы в Java можно было бы писать другим способом. Это не способ, и даже не инструмент, а парадигма, в рамках которой мы ВЫНУЖДЕНЫ писать программы на JAVA, а для того, чтобы обходить проблемы, навязываемые этой парадигмой, приходится выдумывать паттерны, фреймворки и велосипеды, тысячи их!

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

Но результат будет однозначно неудобен.

Да ладно вам, если по комментариям прям тут, на Хабре, походить, то для кого-то обязательно везде использовать ООП, для других - процедурное программирование, третьи приемлют только функциональщину. Все мы лохи, только для разных людей.

Я приемлю чужой код в любой парадигме. Но у меня уже сложилось привычки.

Я пишу (по мелочи) на 6 языках. Но некоторые использую чаще.

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

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

Да, это будет большой рост требований к памяти и железу, но так оно на каждом новом уровне. Тот же ООП тоже потребовал больше ресурсов.

А вот и представитель новой, четвёртой группы :р

представитель новой, четвёртой группы 

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

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

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

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

ну писать с нуля функционал, оно интересно, я тоже хотел анимации на С закодить, но я конечно сохранил задумку, но по ощущениям оно того не стоит когда рядом можно запустить std::array или std::vector, ну а разрабатывать что-то уровня хронос, это извините высший пилотаж как и по С так и по С++, тут хотябы всё просто собрать ) в итоге солидарен с вами, просто уже пользуешься инструментом без изучений для того для чего он создан, ну а вайб не знаю, вот я 3 дня курировал ИИ ради кусочка кода просто потомучто лень было не на своём языке разбираться как сортировать. в итоге просто по шагам по алгоритму ему задал промпт и сделали )

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

вот только что переписал свою либу матеши для 3д с С на С++ по канонам ООП радость нескончаемая, наконечно всё читаемо всё как должно быть )

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

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

Экзотермичен ли Ад?

Если брать, например, PHP с его экосистемой (Composer, существующие фреймворки, библиотеки), то писать придется именно ООП.

На JS/TS функциональный подход скорее более популярный (правда тут у меня статистики нет). По крайней мере, в отличие от PHP, писать функциями довольно естественно для языка.

Одно название там от функционального подхода, ФП не сводится к использованию функций.

А вот, кстати, можете кратко в двух словах пояснить, в чём именно суть функционального программирования? Спрашиваю для себя, так как не понимаю. Процедурное программирование понимаю, ООП тоже в принципе. Я понимаю, что ФП это какой-то следующий уровень абстракции, но какой именно?

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

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

Например, классическое:

  • вызов чистой функции может быть заменён на результат её вызова

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

  • вызовы чистых функций гарантированно можно переставлять местами

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

Не компьютеру, а компилятору. Плюс ему проще ошибки детектировать. И дебажить тоже.

А тут вопрос, эта оптимизация она только на стадии компиляции делается целиком или и на этапе выполнения тоже? Я имею в виду возможный параллелизм и т.д.

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

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

Зависит от реализации компилятора, библиотек и сложности вычислений.

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

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

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

Простейший пример - если вы записали c=a*b, где все участники - матрицы, вполне возможно, что при размерах матриц миллион на миллион среда сама раскинет это на 100 компов в кластере. А вам не надо было писать все итерирования строк-столбцов, вы просто попросили результат - произведение матриц.

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

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

Примечательно, что практике обычно наоборот.

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

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

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

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

В таком случае присваивание выглядит и ведёт себя как просто name shadowing, и старое имя можно просто удалить — в чём его смысл, если оно никуда не захвачено?

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

Продемонстрирую кодом на Rust:

fn foo() -> i32 {
    let x = 2;
    if true {
        let x = 3;
    }
    x
}

fn bar() -> i32 {
    let mut x = 2;
    if true {
        x = 3;
    }
    x
}

fn main() {
    dbg!(foo()); //[src/main.rs:18:5] foo() = 2
    dbg!(bar()); //[src/main.rs:19:5] bar() = 3
}

Так это разные переменные, и никакого присваивания снова нет. foo α-эквивалентна этому:

fn foo() -> i32 {
    let x = 2;
    if true {
        let y = 3;
    }
    x
}

Но ведь name shadowing как раз и означает существование двух разных переменных с одним именем...

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

Применение функций к набору объектов? Всё это map-reduce?

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

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

Основные "киты" современного функционального программирования:

  1. функции как значения - тут в JS всё в порядке;

  2. функции в математическом понимании, они же чистые функции - тут язык вообще не помогает;

  3. работа с данными через сопоставление с образцом, оно же паттерн-матчинг - в зачаточном состоянии;

  4. комбинирование функций через стандартные комбинаторы (включаю сюда знаменитую do-нотацию для монад) - в JS просто отсутствует;

  5. программа как доказательство - в JS просто отсутствует, в TS даже не старались;

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

оно же паттерн-матчинг - в зачаточном состоянии;

В языке нет, и неизвестно когда будет (сам удивляюсь). Но есть внешние реализации в соответствии с пропозалом. Я остановился на этой (https://github.com/shuckster/match-iz), чего и всем рекомендую.

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

Ну... В ООП основное средство выражения абстракций - объекты, в ФП - функции. Это не следующий уровень абстракции, они примерно эквивалентны (в ООП можно сделать объект "функция", который будет вести себя как функция, в ФП можно сделать функцию obj: (State,Args) -> (NextState,Result), которая преобразовывает состояние "объекта").
Для практического примера использования ФП можно посмотреть, как используется map() на python (или на scala, или на другом языке):
```

ys = list(map(lambda x:x*2),xs)

или, в ООП-стиле:

ys=[]
for x in xs:
  ys.append(x.__mul__(2))

Здесь мы передали функцию lambda x:x*2 в типовой цикл преобразования списка в другой список, реализованный в функции map(). Точно так же могли передать метод (2).__mul__вместо анонимной функции.

Это не вся суть ФП, но наиболее частоиспользуемая, очевидная и полезная.

Так вроде бы и в обычном (нефункциональном) программировании можно было бы передать алгоритму ссылку на функцию и реализовать то же самое, разве нет?

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

Ну так функции первого порядка и методы типа map или reduce как раз пришли из функционального программирования.
Само по себе функциональное программирование не сильно сложнее процедурного, просто требует другой логики. Иммутабельность переменных, отсутствие итеративных операторов (for, while), pattern matching.

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

И вот у вас и в C++ уже много лет как есть самые настоящие замыкания (лямбды), и периодически пытаются завести в комитет пропозалы на pattern matching и, с другой стороны, есть вроде как считающаяся функциональной Scala, на которой вполне себе можно писать и в императивном стиле.

Scala - как раз смесь ООП и ФП. Имхо, очень удобно. Даже немного жаль, что сейчас она не нужна практически не используется, т.к. есть java, kotlin.

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

Так вроде бы и в обычном (нефункциональном) программировании можно было бы передать алгоритму ссылку на функцию и реализовать то же самое, разве нет?

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

в какой-то статье про ФП была связка с какой-то более абстрактной математической теорией.

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

Обычно на практике погружаются не слишком глубоко (без всяких "моноидов" и т.п.), ограничиваются например передачей функций/замыканий, pattern matching, иммутабельность стараются использовать. Это практически во всех современных языках доступно.

import operator
# …
ys.append(operator.mul(x, 2))

:)

или, в ООП-стиле:

Это не ООП-стиль, а процедурный (он же императивный). ООП ортогонально императивности или функциональности.

В процедурном стиле не было бы методов.

На пальцах. Нестандартное объяснение.

ФП замораживает время. Все время снаружи. А функции полноправные жители: создаются, принимаются и отправляются. И как аргумент и как результат.

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

Нет. Формально, в ФП результат это связь результата с запросом. Это неважно, что процессор будет какое-то время её искать. Она уже есть. Результат полностью предопределен. А значит корректность можно доказать заранее

... Как только определите pure версии функций fread и random)

Это всё равно только инструкции для рантайма. Но я не говорил что они pure

"Чистые" версии таких штук выносятся в монаду IO и возвращают не результат операции, а нечто вроде предписания эту самую операцию совершить. Само предписание исполняется внешним циклом уже после завершения main.

Именно потому выше и писали, что в ФП время "заморожено" и находится снаружи программы.

"Чистые" версии таких штук выносятся в монаду IO и возвращают не результат операции, а нечто вроде предписания эту самую операцию совершить

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

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

С UI сложнее, потому что нужна совсем другая архитектура, но тоже возможно.

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

Но ведь это и называют джейсоноперекладчиком же?

В нашей деревне джейсоноперекладчиком называют тупой CRUD, который гоняет данные json из БД на фронт и обратно без существенной обработки:)

Джейсоноперекладчиком называют любую программу, которая гоняет данные без существенной обработки и хотя бы с одной стороны использует формат json. HTTP тут используется или gRPC, СУБД или очередь сообщений - вообще нет разницы, принципы работы тут одни и те же.

на типичном бэке-джейсоноперекладчике подобный подход должен работать просто прекрасно

Он там отлично работает!

это ещё кое-как работает в задачах на "посчитать", но совсем негодно для UX (фронта) или там сетевых сервисов (бэка).

А разве популярные языки что во фронте, что в беке не затаскивают в себя в последнее время промисы и всякие async/await приколы, к примеру?)

Чем вам не монада)

Вот прекрасный способ начать погружение в тему

https://frontendmasters.com/courses/functional-javascript-v3/

Если станет интересно, дальше можно копнуть в сторону https://github.com/MostlyAdequate/mostly-adequate-guide

Ну а там уже сами определите куда дальше)

Было бы логично, чтобы этот комментарий написал человек с ником php5, но никак не php7.

Лично я гораздо чаще слышу: "используешь ООП -- лох". И полно таких статей на Хабре. Отсюда и холивары.

"не принимайте критику от того, у кого вы не спросили бы совета." ©

Заходишь в новый офис, а помидор подкатывает на кресле на колесиках и с прищуром такой «А ты кто по жизни вообще? Ровный ООПэшник или чушпан-функциональщик? Пояснить за деструкторы сможешь? Что выберешь - вилкой в глаз или ссылку в класс?»

Ядро Linux написано на Си, а в Си нет никакого синтаксического сахара для поддержки ООП. По мнению этих говорящих голов выходит, что вся команда Торвальдса — лохи? :)

По мнению этих голов, вероятно, да. Но зачем на них ориентироваться?

ООП не нужен: смотри на Linux

Цитируя мой же коммент к предыдущей статье:

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

Да там теперь даже Rust есть…

С учётом развития ИИ, может лучше не надо rust развивать.

Пусть ИИ си код проверяет, rust всё равно чуть медленне будет.

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

Чего бы Расту всегда медленнее быть? А «ИИ» как раз в таких деталях и не справляется, когда нужно общую картину в голове представлять.

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

Конечно, в ядре Linux не до полиморфизма.

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

Вы, видимо, имеете ввиду динамический полиморфизм в Linux ядре. Но он есть и в Haskell и в Rust, но ни тот ни другой не поддерживает ООП (нет наследования).

Наследование доступно и на практике используется в C, как это и продемонстрировано по моей ссылке в разделе "Inheritance"

Я не говорил, что наследование нельзя реализовать в C. Но то, что оно используется в Linux Kernel - я не знал.

Многие просто путают ООП и языки с встроенной поддержкой ООП. Поэтому у них Linux без ООП.

Вы просто пользуетесь им как молотком по клавиатуре

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

Так тогда вообще нет никаких проблем...

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

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

Тут я вслух прокричал "конечно стоит выбрасывать любую идею если она делает синтаксис сложнее"

Иногда усложнение синтаксиса сильно упрощает понимание.

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

ООП должно делать код надёжнее, понятнее и проще в работе. Если этого не происходит, то надо подумать и сделать нормально.

Либо же вообще в этом месте не применять, если не получается

НЛО прилетело и опубликовало эту надпись здесь

вы просто не умеете их готовить

Я умею их не готовить когда не нужно

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

А что не так с договорами и их методами?

Ну например, если в общих чертах, то физический реальный договор это не тоже самое, что запись в БД. Под действием "подписать" может пониматься, проставить в записи о договоре отметку, что клиент собственноручно расписался на обоих бумажных экземплярах, и приложить фотографию. Т.е. мы говорит про изменение состояние записи в приложении, которое отвечает за какой-то аспект договорных отношений. Нет никакого "договора" в приложении. Есть запись, отражающая информационное состояние. И как показала моя практика (в различных проектных командах), это создаёт у разработчиков путаницу в определениях и сложному и совершенно не нужному дизайну классов. Там где нам надо было управлять состоянием, мы пытаемся вести себя как объекты реального мира, имитировать их. Это приводит к тому, что мы пытаемся контролировать на уровне класса, что контролировать там не нужно. Например, то же самое подписание, это с точки зрения бизнеса атомарное действия, либо подписал, либо нет, а с точки зрения операционного учёта, это транзакция с комплексом событий, оркестрация всего этого добра с подтверждениями и согласования между разными системами.

А что такое "физический реальный договор"? Растровый скан распечатанного текста? Физическая симуляция взаимодействия молекул чернил с молекулами бумаги? Договор - это прежде всего объект бизнес процесса, абстракция.

Метод "подписать" формирует цифровую подпись текста и прикрепляет её к договору. Когда все подписанты это сделали, договор считается заключённым. Всё. Через какие там события под капотом происходит синхронизация состояния договора между разными системами - не особо важно.

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

И когда пытаются создать класс "Договор" или "Клиент", и описать в нём методы 1 в 1 соответствующие реальности, это не работает просто из-за нарушения понятий.

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

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

Поэтому метод "Подписать" в классе это искажение реальности

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

Новички об это часто спотыкаются

Новички вообще обо всё спотыкаются, потому что они новички.

Можно без ООП раскидать апдейт записи в сотню разных мест

Почему вдруг оно будет в сотне разных мест, если действие "Подписать договор" у бизнеса одно?

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

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

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

Если логика одинаковая, то будет вызываться один метод, даже если он находится не в сущности.

Вот вы пришли к тому, что нужен один метод, и почему бы ему не быть в сущности "договор"?

Потому что полная реализация бизнес-логики требует зависимости для ввода-вывода и другие сущности, а это создает разные сложности. И почему логика должна быть именно в "Договор", почему бы не сделать метод "Пользователь.подписатьДоговор()"?

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

Так он не только состояние договора меняет. В разной бизнес-логике он может например менять флаг пользователя "Договор подписан", создавать сущности для истории изменений "ПодписаниеДоговора", создавать записи "УчастникДоговора" для связи многие-ко-многим, с transactional outbox создавать сущности "УведомлениеНаЭлектроннуюПочту".

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

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

Как вообще можно вызвать метод "создатьЗаказ" у объекта заказа, который еще не создан. Будете помещать в конструктор всю логику, включая взаимодействие с платежным провайдером?

Для меня логичнее и понятнее выполнять действия НАД сущностью, и группировать методы по этому принципу. Вот класс Договор, в нем методы что с ним можно сделать, в этих методах изменяется состояние сущности Договор, в БД происходит апдейт преимущественно записей с договорами.
В вашем варианте заранее ограничено ЧЕМ или КЕМ выполняется действие - в классе Пользователь будет изменяться состояние множества разных ничем не связанных сущностей, потому что юзер может делать много чего. А потом окажется, что не только Пользователь может выполнить данное действие, но и кто-то другой - придется дублировать метод. Или же пользователя еще нет в вашей системе, он какой-то человек со стороны - и начнутся проблемы.
Но я не вижу ничего плохого, если внутри Пользователь.подписатьДоговор() будет вызываться Договор.подписать(), - обычно так и делают, если нужны действия именно сущностью.

В вашем варианте в классе Пользователь будет изменяться состояние множества разных ничем не связанных сущностей

Это не мой вариант, я просто привел пример вопроса "Куда поместить бизнес-логику", который возникает, когда в процессе участвует и изменяется несколько сущностей. Мой вариант это не помещать ни в какую. Бизнес задает правила, как выполнять какой-то бизнес-процесс, а не какая-то отдельная сущность.

А с логикой в сущности есть аналогичная же проблема - она превращается в God-object, потому что содержит абсолютно все связанные с ней методы бизнес-логики - для пользовательской части, для админки, для импорта через консольную команду, для обработки сообщений из Кафки.

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

Так просто не то моделируют. Информационная система должна моделировать бизнес сущности и бизнес процессы.

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

Что-то я не вижу как из одного следует другое.

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

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

Представьте, что бумажный паспорт имеет функцию "получить деньги на кассе" или "заключить сделку". Абсурд? Но когда мы пишем класс, с подобными методами, уже не абсурд :)

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

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

нет никакой надобности делать класс "Договор" с методом "Подписать" и другими методами, которые пытаются имитировать действия над договором в реальном мире

Такая необходимость - это работа с требованиями. Гораздо проще жить имея бизнес-слой, который со стороны вызовов будет читаться по буквам требований, а под капотом - осуществлять привязку к технической реальности, вроде схемы БД.

Да, все верно. Чувствуется опыт.

до более сложных "договор" с методами "подписать", "принять", "отклонить"

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

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

Методы объекта как раз и должны быть действиями над этим объектом.

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

Давайте так. Вместо того, чтобы писать комменты как всё будет хорошо, лучше сравним код.
У меня есть эта статья, там описаны бизнес-требования. Покажите, как будет выглядеть код с "действиями над объектом" для действий "Сохранение товара" и "Отправка на ревью". Я думаю, лучше написать 200 строк кода, чем 200 строк комментов.

Вообще-то договор как объект должен быть не изменяемым. Изменяться должны состояния договора - которые вроде как отдельные сущности. Далее по идее должен быть Актер, который выполняет действия над Договором, следуют каким-то Полиси. В приципе это все объекты:).

Тут в соседней ветке с представлением простого графического редактора не можем разобраться -взять несколько фигур и нарисовать на канвасе, а вы тут про высокие материи: договор, бизнес процесс …

Вообще-то договор как объект должен быть не изменяемым.

Если его можно заполнить и подписать, значит он может изменяться.

взять несколько фигур и нарисовать на канвасе

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

Возникло чувство, будто это пост-ответ на другую недавнюю публикацию) Совпало или действительно так, но в любом случае интересно наблюдать за развернувшейся полемикой

Ты знал, ты знал. Писал как ответ. Лучше лить воду на свою мельницу чем на чужую :). Да и
Ответ получается более структурированный.

ООП не нужен: смотри на Linux

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

Классы нужны чтобы неявно передавать данные класса (поля) в процедуры класса (методы). Если вместо неявной передачи данных передавать их в явном виде, код станет только понятнее. И классы станут не нужны.

Во-первых, есть языки (Python, Rust) где аналог параметра this передаётся явно, и к его полям обращение идёт тоже явно. Но менее ООП они от этого не становятся.

Во-вторых, от явных вызовов вида obj->vtbl->foo(obj, …) код понятнее не становится. Любая необходимость дублировать параметр делает код сложнее, а не проще.

менее ООП они от этого не становятся

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

от явных вызовов вида ... код понятнее не становится

А моя многолетняя практика свидетельствует о том, что явное всегда лучше и понятнее неявного.

Согласен, для формошлёпства и перекладывания из DTO в JSON, ООП не нужен, всё это от лукавого.

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

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

Кажется, вы как-то странно понимаете что такое контейнер для данных.

А моя многолетняя практика свидетельствует о том, что явное всегда лучше и понятнее неявного.

Моя же многолетняя практика свидетельствует о том, что запись вида obj.foo(…) достаточно явная для понимания, и если кому-то доставляют сложности осознание откуда при этом выpове берётся this - ему надо учить язык, а не рассуждать о явном и неявном.

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

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

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

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

По моему если из класса убрать методы, [...]

Погодите, а где я вообще писал про "убрать методы"? Я ж вам два языка привел, в которым обращения к полям явные, но и методы все на месте.

Вы вообще с чем спорите?

Что принципиально меняет замена obj.foo на this.foo в вашем примере [...]

Как вы умудряетесь терять контекст диалога когда весь диалог прекрасно умещается на одном экране? Мои примеры были вот такие:

  1. "страшная" неявная передача this: obj.foo(bar, baz)

  2. "чудесная" явная передача без всяких ООП-излишеств: obj->vtbl->foo(obj, bar, baz)

Вы вообще с чем спорите?

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

Мои примеры были вот такие

Почему во втором примере obj->vtbl->foo(obj, bar, baz) а не просто foo(obj, bar, baz) ? Зачем сюда тащить излишние ООП конструкции? ))

Потому что иногда динамическая диспетчеризация всё-таки нужна. Кстати, это и на первое ваше утверждение отвечает.

Эта необходимость в динамической диспетчеризации вызвана ограничениями определённого языка программирования, или же это глобальная необходимость всего computer science как такового?

Просто если эта необходимость является фундаментальным принципом, это одно, а если это просто недоработка какого то конкретного языка, может быть надо просто bug report завести?

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

Хотя соглашусь что классическое ООП использует подобный подход чаще чем требуется из-за ограничений языка.

А моя многолетняя практика свидетельствует о том, что явное всегда лучше и понятнее неявного.

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

Я высказал свои соображения. Можете их отрицать, можете быть несогласны. Дело ваше. Убеждать я вас ни в чём не буду))

Ой, ну всё. То, что лежит на поверхности и сказал один человек, то никак не может подумать и сказать другой человек? Нельзя так. Мир победившего копирайта и бесконечных судебных тяжб за нарушения авторских прав, но не до такой же степени!

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

Вы впадаете в крайности.

Эх внутрях 1с не ковырялся. Там в порядке вещей 5 вложенных вызовов, в которые передаются структуры, на каждом следующем вызове эти структуры перекладываются в другие. В общем жутко получается. И это еще без динамических приколов.

Классы нужны чтобы неявно передавать данные класса (поля) в процедуры класса (методы)

Вам нужны?

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

мне без вектора в С тяжко буду признателен, поэтому ООП )

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

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

Есть одно мерило хорошего кода – он работает так, как ожидается и за него заплатили столько, сколько ожидалось. Все остальное лирика и демагогия в чистом виде. Хорошего, с точки зрения бизнеса. С точки зрения разработчика хорошего кода не существует в природе, потому что всегда можно что-то улучшить, реализовать по другому, эта игра в догонялки с тенью.

Есть одно мерило хорошего кода – он работает

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

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

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

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

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

Я сейчас наговорил кучу банальных вещей, простите.

Стоит всё же кое что добавить к сказанному. Споры вокруг ООП чаще всего напоминают спор глухого со слепым. Огромный пласт работы, в которой задействованы программисты, это императивная логика, построенная на вызове методов, аннотациях и условиях, а классы это 99% контейнеры данных, контейнеры логики (контроллеры например), просто наборы методов для внедрения в DI. Нет тут никакой потребности в инкапсуляции, что и от кого скрывать? Это верхнеуровневая логика, прямо за ней уже находится пользователь, а не другой программист. Код призван решать конкретную бизнес задачу. Он типовой, похож на копипасту, но для бизнеса он крайне важен, так как закрывает таску в джире и открывает новую фичу. В этих условиях нужно понять разработчика, ему этот ООП как филькина грамота. Даёшь функциональщину! Вот это дело. Паттерн матчинги, лямбды, вот это всё нужно. А ваши ООП, это для дедов :)

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

Инкапсуляция тут нужная для следующей цели: явно сказать другому программисту, что вот к этим данным/логике доступ есть только у класса by design, и если кому-то зачем-то вдруг потребовалось получить к этому доступ из вне, то он явно что-то делает не так и должен ещё раз подумать, а не фигню лю он делает. И бить по рукам компилятором за любые попытки игнорировать логику создателя класса.

"Извне" работает только если программист реализует компонент, который используется другими программистами, при реализации логики. А зачастую это компонент, который уже зацеплен к презентации, не будет это использовать другой программист, это конечная логика для пользователя. Т.е. это JSON для фронта, или отображаемая форма с контролами, или HTML.

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

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

Это всё понятно, тут наверное и вопросов нет нужно ли ООП, зачем ООП, кому всё это нужно, кому нужны эти наследования и инкапсуляции, давайте писать просто функции и структуры :)

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

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

Я профессиональный погромист, если что.

мне кажется это одно и то же (я бы даже сказал больше - простота рефакторинга это следствие хоршо использованного ООП).

Ну ну то есть DataProduct - не обладает ни одним из свойств, а OOPProduct - обладает сразу обоими потому, что оба этих свойства взаимосвязаны:

class DataProduct {
public:
  int price;
};

class OOPPrucuct {
...
public:
  int getPrice() const {... }
};


ему этот ООП как филькина грамота. Даёшь функциональщину! Вот это дело. Паттерн матчинги, лямбды, вот это всё нужно. А ваши ООП, это для дедов :)

А это ООП или функциональщина?

db.Categories    
  .GroupJoin(db.Products,
      Category => Category.CategoryId,
      Product => Product.CategoryId,
      (x, y) => new { Category = x, Products = y })
  .SelectMany(
      xy => xy.Products.DefaultIfEmpty(),
      (x, y) => new { Category = x.Category, Product = y })
  .Select(s => new
  {
      CategoryName = s.Category.Name,     
      ProductName = s.Product.Name   
  });

А это ООП или функциональщина?

Императивщина на ограниченном спец языке, изобретенном потому что основной язык не имеет понятия 'стандартного аргумента/входа' для функций и нельзя написать (как в шеллах)
GroupJoin(аргументы) | SelectMany(аргументы) | Select(аргументы)
и поэтому приходится нужный функционал методами объекта эмулировать.

У нас, видимо, разное понимание «императивщины». Для меня императивщиной было бы, если бы этот код был написан на for'ах и переменных (периодически вижу такое на всяких форумах).

Функциональным можно считать что-то такое

p1 = GroupJoin(...)
p2 = SelectMany(...)
p3 = Select(...)
db.Categories.chain(p1, p2, p3)

А в том виде, что, написано - чисто пошаговое выполнение, просто промежуточные переменные в выражение спрятали и сделали безымянными;

a1 = db.Categories.GroupJoin(...)
a2 = a1.SelectMany(...)
a3 = a2.Select(...)

Или даже так

a1 = Categories.Group(db, ...)
a2 = SelectMany(a1, ...)
a3 = Select(a2, ...)

Вы так пишете, как будто в ФП редко используют "пошаговое" выполнение. А ведь в Хаскеле даже оператор для подобных случаев изобрели - $

Вы вырвали часть мысли и исказили, увы.

Нет. Написать работающий код любой дурак может.

Это пока бизнес-контекст относительно прост.

Код пишется людьми и для людей

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

Читаемость кода - это не лирика и не демагогия, как вам показалось.

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

Технический долг - это не страшилки программеров.

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

Понятный код способен ускорить на порядок разработку и снизить на порядок стоимость поддержки проекта

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

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

Я сейчас наговорил кучу банальных вещей, простите.

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

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

Есть одно мерило хорошего кода – он работает так, как ожидается и за него заплатили столько, сколько ожидалось. Все остальное лирика и демагогия в чистом виде. Хорошего, с точки зрения бизнеса. 

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

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

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

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

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

Самое обидное, что Вы даже не поймете в чем здесь дело.

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

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

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

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

@Vasjen Да, Вы правы не существует метрик читаймости. И примеру с новым сотрудником сложно что-то противопоставить.

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

Это чувство проявляется тем сильнее, чем больше времени прошло с момента последнего внесения изменений.

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

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

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

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

Код пишется для бизнеса

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

С чем вы хотите поспорить? С тем что код чаще читается, чем пишется? Ну удачи.

Что значит "для бизнеса"? Не для бизнеса.

Я так тоже могу упростить до крайностей, смотрите: Бизнесу нет дела до кода, бизнес делает деньги. И всё, что ему нужно - это деньги. Код ему не нужен. Код - это вообще-то затраты на поддержку, а бизнес затраты не любит. Вывод: бизнес должен избегать написание кода.

Нет метрик этой читаемости, увы.

Да всмысле? Вот же сами пишете:

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

Именно так. Вот тот самый пресловутый wtf/min. Есть ещё kloc, глубина наследования, связность классов.

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

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

Вы можете сказать

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

И тем более, приравнивать читаемость к каким-то абстрактным практикам. Я выбрал термин "читаемость" и я на нём настаиваю. Я не имел в виду религиозное соблюдение солида или что-то в этом роде. Я говорил про читаемость. Про понятность.

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

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

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

С одной стороны согласен с вами (хотя я работаю в системном программировании и у нас тут куча объективных perf- и compatibility- требований).

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

Да нельзя сказать С КАКОЙ ПЕСЧИНКОЙ ИМЕННО появилать "куча песка".
Но несомненно существует "это - несколько песчинок, а это - уже куча песка".

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

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

В машиностроении и строительстве есть такое понятие как "ГОСТ" на оформление чертежей и "нормоконтроль". И те и другие были сформированы в позапрошлом веке.

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

В ИТ вообще и во многих шарагах в частности, творится бардак. Однако во многих компаниях уже начинают наводить порядок, и по-сути, вводят нормоконтроль на оформление кода. По различным постам на Хабре - в Сбере (причем в негативном ключе, с точки зрения авторов), и в Гугле (насколько я понял, прочитав "Делай как в Гугл").

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

ООП замечательно тем, что его можно хоронить бесконечно.

Напомнило анекдот, извините :)

Самолет терпит аварию, в живых на необитаемом острове остаются капитан, штурман и стюардесса. Через неделю капитан говорит: "Хватит разврата" и убивает стюардессу.

Еще через неделю капитан говорит: "Хватит разврата" и закапывает стюардессу.

Еще через неделю капитан говорит: "Хватит разврата" и выкапывает стюардессу

А, так вот откуда взялась стюардесса. Я просто часто встречал фразу а-ля "зачем вы снова откопали стюардессу" и не мог понять откуда она.

Может быть и хорошо, что Вы не знали.

Да на код свой гляньте. Если вам реально нужны vtables, ну ок. Иначе ооп только compile time приблуда. Вы задачи решаете или тексты пишете?

Вспомнились холивары моей студенческой юности, "что лучше, c++ или Дельфи"

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

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

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

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

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

Классика :)

Есть замечательный старый анекдот.

Американский форум. Задаёшь вопрос, тебе отвечают.
Израильский форум. Задаёшь вопрос, тебе задают вопрос.
Русский форум Habrahabr. Задаёшь вопрос, тебе долго рассказывают,
какой ты мудак.

Который совсем не анекдот, поэтому я обычно дублирую "я сделал" посты на Reddit.

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

Анекдот не совсем жизовый, я как минимум видел жалобы на крайней степени токсичность у коммьюнити stackoverflow))

Это верно, можно и 100 лет генерить говнокод и ничего не понять, можно гораздо быстрее набраться жизненного опыта и шишек набить... не в "стаже" дело, но что точно объединяет "ламеров" (хороший термин из прошлого) - это склонность авторитетно (нет) критиковать то, что не смог осилить.

Да. Да. В этой части Вы прямо апостол.

Инструмент хорош, когда его используют по назначению.

Универсальная максима. Одной этой фразой можно заканчивать почти любой холивар. )

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

Вот самый банальный пример: "всё есть объект". Попытка глобального обобщения разбивается о суровую реальность. Когда "всё объект", технически это приводит к необходимости вот это "всё" держать в куче, чтобы была ссылка на объект. Но это не всегда рационально, местами крайне избыточно и сильно бьёт по производительности. И начинаются пляски, ну ладно "не всё объект" или по крайней мере не всегда, давайте вот отдельно выделим примитивы (Java), или объекты-структуры (ValueType, C#).

Переосмыслить назначение — норм. Делать глобальные обобщения, возводить в абсолют — не норм. Это, в принципе, простая вещь, можно и попыток было не делать. )

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

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

«Все есть объект и все надо держать в куче» это издержки реализации ООП в Java. Остальные мейнстримовые языки не лучше в этом смысле. Если не ошибаюсь Додо сих пор единственным ООП языком в «классическом» понимании можно считать только Simula. Где как раз и была реализована идея «посылки сообщения» объекту. Вы могли любое сообщение(вызов функции объекта в терминах C++, Java,C#) послать любому объекту. Далее объект либо обрабатывал его, либо возвращал not-implemented.

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

В этом смысле Erlang/Elixir - которые вроде как более функциональные языки - и выглядят как продолжатели чисто ООП подхода (только ногами не бейте :).

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

У меня в свое время долго была путаница в понимании объекта и класса, поскольку в C++/Java де-факто классов нет, есть только тип, который назвали классом.

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

Ну далеко не только в Java же. Но везде есть свои, как говорится, приколы.

Про посылку сообщений объекту, ну это как бы тоже уровень абстракции. Чем строчка с вызовом метода у объекта по его ссылке не "отправка сообщения"? Если метод виртуальный, или вообще ЯП динамический, то в ответ также получим либо исключение, либо какой-нибудь undefined. Почему от абстракции посылки сообщений вообще везде ушли, наверное потому что это дико неудобно и сложно. Но также, не ручаюсь за именно такое объяснение. Однако имеем что имеем. На языках ООП, написаны горы полезных продуктов, в том числе те, которыми мы ежедневно пользуемся.

чем строчка с вызовом метода у объекта по его ссылке не "отправка сообщения"?

Тем, что такой подход плодит полную дичь, когда начинаются споры, подобные таким, как были тут комментариях к одной статье на тему ООП.
- pizza.bake / - oven.bake pizza
- cook.turn_on oven / oven.turn_on
- box.wrap_up pizza / pizza.wrap_up_in_box
- и т.п.

Мышление на уровне отправки сообщений уменьшает вероятность того, что программист будет говорить пицце "приготовься". Так легче осознать зоны ответственности. Где данные (как пицца), а где Акторы.

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

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

В любом мало-мальском современном ЯП можно использовать такой подход, но удовольствия в этом мало, если применять вообще везде. Такое нужно применять когда в этом есть необходимость. Например, для распределённых вычислений, берём Akka или Orleans. Для стримов берём кафку, если синхронный реквест/реплай нужен, то городим огород на AMQP или берём Nats. Ну а если это монолит, то очереди в памяти, в гошке те же ченнелы.

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

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

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

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

Подписываетесь на сообщения "Пользователь создан " и отправляете сообщение "Создать пользователя", добавляя уникальный маркер. Когда пришло сообщение "Пользователь создан" с этим маркером, отправляете сообщение "Добавить в корзину".

Это уже событийная архитектура получается, что не тождественно отправке сообщений.

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

А за что тут ногами бить? Алан Кэй сам написал "Erlang is much closer to the original ideas I had about “objects” and how to use them"

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

Я предпочитаю называть два основных стиля ООП - "активные объекты" и "пассивные объекты". В случае Simula/etc. имеем первый. В случае C++, Java, C#, тысячи их - второй. Да, они разные.

В Эрланге же у вас появляется класс gen-Server, который из которого создается процесс (объект).

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

Когда "всё объект", технически это приводит к необходимости вот это "всё" держать в куче, чтобы была ссылка на объект.

Это, разумеется, не так. Простой пример на D:

import std;

void main() {
    
    uint foo = 1; // 1
    auto bar = Complex!float( 3, 4 ); // 3+4i
    
    writeln( foo.init, " ", bar.init ); // 0 nan+nani
    writeln( foo.sizeof, " ", bar.sizeof ); // 4 8
    writeln( foo.max, " ", bar.im.max ); // 4294967295 3.40282e+38
    
    writeln( (++foo).stringof ); // foo += cast(uint)1
    writeln( std.math.stringof ); // package math
    
}

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

Пример на C#

object v = 42;
// вызов метода общего для всех-всех объектов
Console.WriteLine(v.ToString()); // выведет: 42

Компилятор "не знает" чей ToString() я вызываю, в рантайме вызывается метод, вычисляемый для конкретного экземпляра, через vtable.

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

Разве C# не использует девиртуализацию и инлайнинг? Хоть он и с GC, но вроде он все ещё компилируемый.

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

Конкретно в вашем случае объект оказался в куче не из-за вызова ToString, а из-за приведения к object, прямой вызов 42.ToString() без особых проблем выполняется на стеке.

И даже если усложнить пример, сделав так чтобы был вызван именно метод базового класса - он всё равно будет вызван на стеке, спасибо механизму constrained call:

Foo foo;
Console.WriteLine(foo.ToString());
// …
struct Foo {}

42.ToString() выполняется на стеке только благодаря костылю .NET, в виде ValueObject, который с одной стороны наследуется от Object, с другой стороны не является ссылочным типом, пока его не присвоили в object (boxing). И это простой пример, потому как обычно, в стек много не положишь, а объектов нужно всяких много. Где же им ещё размещаться, как не в куче?

А с чего вдруг значимые типы (value types) костыль-то?

Это моё ИМХО :) Введение значимых типов в язык это очень хорошо для производительности, но из стройной иерархии "всё есть объект" очень выбивается. В последних версиях дотнета большой упор на поддержку структур, стековые ссылки, readonly-структуры. Но выглядит всё как костыль, хоть и добротный. Взять например отсутствие возможности закрыть дефолтный конструктор и определить свой иницилизатор, подковёрные игры с duck typing (в using), всё это хорошо, но очень непрозрачно.

std.stdio.write - шаблонная функция, полагаю, вам намекают на статический полиморфизм. Кстати, в том же C# компилятор умеет делать мономорфизацию для аргументов-типов в дженериках с where T: struct, что позволяет обходиться без боксинга для значимых типов.

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

А это уже не проблема или ограничение C#. Если надо - кладите структуру в другую структуру и в Span/stackalloc, язык это позволяет.

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

Переосмысление это что-то из разряда истины, которая рождается в споре. А холивар это специфический вид спора, в котором кроме срача обычно ничего не рождается. Священная война - это битва догматов. Или догм и матов )) Если в споре родилось что-то разумное, доброе, вечное, то это уже не холивар, но дискуссия. Или диспут, если хотите.

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

«Никто не знает, что такое ООП»

Благо в php этот вопрос легко решён: если в коде есть стрелочки "->", значит это ООП.

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

Хах :) Вопрос с подвохом.

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

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

Но если вам так нравятся мыслительные упражнения

Давайте без таких излишне полемических приёмов.

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

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

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

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

Касаемо развития и сопровождения, всё зависит от используемого языка. Например, в php-коде отсутствие ООП - это признак легаси. А вот в js применение ООП уместно разве что для разграничения пространства имён функций.

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

Мне тут пришлось на php программировать. Сложилось впечатление что ООП там гвоздями приколотили потому что было модно. Мне показалось что в чисто процедурном виде все выглядит гораздо лучше чем при попытке ООП использовать. Оно вроде есть, но как в «стандартных языках» (с++, С#)

Всё зависит от того, что за проект. Например, если взять WordPress или Drupal 7 и более ранней версии, то там ООП практически не используется. Если взять OpenCart или Битрикс, то там ООП привинчен практически без всякого понятия, именно чтобы было.

Но если брать Laravel, Symfony, Drupal 8 и более поздних версий, то там всё сделано вокруг ООП, и всё красиво, модно, элегантно, а главное понятно.

Я сам начинал писать на php ещё 12 лет назад на Drupal 7. Там всё было полностью процедурно, а если и были какие-то объекты, то это был stdClass (в php так называется самый базовый класс, от которого наследуются все остальные). И когда я в 2016-2017 стал переходить на Drupal 8, по началу мне было тяжело, потому что там повсеместно (хоть и не везде) был ООП-подход. Помню тогда очень многие плевались, что стало якобы непонятно и тяжело. Но набив руку и разобравшись как с самим ООП, так и с новым API, я понял, насколько огромные преимущества даёт ООП. Если в Drupal 7 и более ранних версиях функционал каждого модуля писался в огромном файле, где было 2-3 тысячи строк и несколько десятков функций, то сейчас файлов стало больше, но они очёнь чётко структурированы, разложены по пространствам имён и т.д. Структура настолько понятная, что даже если мне надо посмотреть, как что-то сделано в незнакомом мне модуле, я безошибочно могу с ходу открыть нужный мне файл, не читая документацию и не запуская отладчик.

ООП может не нравиться тем, кто использует обычный текстовый редактор, типа Notepad++ или Sublime Text вместо нормальной IDE. В таком случае приходится набирать вручную много лишнего текста, который в IDE подставляется автокомплитом. При написании типизированного ООП кода, находясь в методе, чтобы получить что-то из аргумента, тебе IDE подскажет методы, доступные для этого аргумента. Или подчеркнёт красным, если ты этот аргумент передашь куда-то, где на вход ожидается другой тип. В функциональном и слабо типизированном коде мне надо лезть в доки или дебагер, смотреть, что тут за тип у аргумента. И чтобы с ним что-то сделать, нужно вызывать одну из многих тысяч глобально видимых функций, и опять же в доках этих функций сверять тип принимаемого аргумента.

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

Если я не пользуюсь самолётами, а езжу только поездами, можно ли считать, что в моём мире не существует самолётов?

Обсуждения на тему понимания ООП похоже также бессмертны, как и "Были ли американцы на луне"

Никто и не говорит, что ООП мертв. Мертвы люди которые ООП использовали :)

В нынешнем проекте к примеру, объекты все сплошь и рядом тупые контейнеры для данных. Причем все value objects, одноразовые. Вся логика для работы с ними в совсем других классах. "Dog" не гавкает и базовый класс у него не "Animal". Инкапсуляция вроде бы и есть, но class это скорее просто какой то stateless модуль с набором функций.

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

Из-за выше перечисленных двух пунктов и наследования как такового не используется. Чаще только в каких нибудь утилитарных классах.

Ещё и реакторное функциональное программирование все больше и больше проникает в java

Я не говорю, что ООП мертво, скорее как и во всем, побеждает более прагматичный подход. В нулевых к примеру, что бы сделать RPC вызов, надо было бороться с какой нибудь CORBA, описывать все в XML, а сейчас достаточно http и Json в 80% случаев.

ООП огромен, на все случаи жизни, но чаще жизнь простого программиста проста и неказиста.

Всё зависит от применения. Прикладная логика редко требует разработки классов. В основном это чистый императив и DTO. На этой почве чаще всего и звучат "ООП нинужен", когда основная часть работы это перекладывание данных из JSON в DTO и запросы в БД. При чём на плечах огромной кучи библиотек и платформы, с кучей классов под капотом. По той же логике можно с уверенностью сказать, что микроэлектронника не нужна. Да, у меня есть телефон, куча аппаратуры, я просто кнопки жму, зачем мне нужна ваша микроэлектронника? :)

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

Утверждения про "побеждает", это примерно как щупать в слепую слона за хобот и утверждать, что хобот, это всё что нужно, хобот победил :)

В нулевых к примеру, что бы сделать RPC вызов, надо было бороться с какой нибудь CORBA, описывать все в XML, а сейчас достаточно http и Json в 80% случаев.

На самом деле это больше была проблема инструмента, а не подхода. Так-то в итоге с теми же http и Json индустрия пришла в целом к тому же - contract first, кодогенерация по возможности. И к ООП тут вообще ничего не привязано.

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

ООП не нужен: смотри на Linux

Смешно тут то, что авторы таких утверждений не понимают (или не хотят признавать), что Linux, и ядро любого Unix, и ядро Windows - они все на ООП как раз классического пассивного, не-Кеевского типа. Полиморфизм на виртуальных функциях - в полный рост. Уровень сисколлов: есть универсальный read() на всех, write(), close(), fcntl(), вплоть до epoll и io_uring, ко всем применяются одинаковые методы для большинства работ, внутри происходит переключение. В реализации read(), например, ret = file->f_op->read_iter(&kiocb, &iter); - f_op это Virtual Method Table.

Инкапсуляция делается только на границе user land / kernel land, да. Но это ограничение языка. Был бы даже очень урезанный C++ - всякие private и protected были бы везде. А пока следят на уровне верификации кода (начиная с визуальной).

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

Так что вы с этим

Конечно, в ядре Linux не до полиморфизма

неправы, Linux ещё больше играет за вашу же команду: ООП нужен - там, где удобен.

Наследования в явном виде очень мало, да.

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

ООП, как вы привели пример из Линукса - это всего лишь организация кода, и подозреваю, не единственно возможный. Довольно странно реализовывать и зашивать ПРЯМО В ЯЗЫК эту организацию.

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

Имхо, достаточно было бы в Си добавить средства, облегчающие организацию VMT, и на этом стоило бы закончить.

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

Имхо, достаточно было бы в Си добавить средства, облегчающие организацию VMT, и на этом стоило бы закончить.

…и эти средства называются как? Правильно, виртуальные методы. Которым нужны классы, и вот уже ООП в язык подъехало.

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

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

Так у Apple вроде был Objective-C- C with classes, вместо «православного» С++, но народ в общем и целом предпочел C++

Имхо, достаточно было бы в Си добавить средства, облегчающие организацию VMT, и на этом стоило бы закончить.

VMT - раз.
private - два (надо же защищать от внешнего неавторизованного доступа).
namespaces - три (есть тут у меня один компонент, названия типа rrx_sub_trr_pkt_new задалбывают).

Вот уже именно что языковые средства и 1/5...1/4 от C++ уже есть.

И что странного в зашивке в язык, если там всё равно уже зашито 100500 всего начиная с типов, а это конкретно - используется очень значительной частью софта?

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

Нэймспейсы не особо относятся к ООП. Вполне можно их реализовать без ООП.

Я и не говорил такого. Я говорил о том, что полезно перетащить из C++. Хотя сейчас скажу, что неймспейсы таки косвенно имеют отношение к ООП: как и класс, это средство структурирования.

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

А как данные привязать к каждому конкретному объекту?

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

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

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

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

Темы про ООП мне кажутся как темы плоскоземельщиков.

В смысле, отрицатели ООП - плоскоземельщики? Полностью согласен 😆

Моя практика показывает, что очень многие просто не умеют в ООП. Ну, то есть они выучили всякие ключевые слова типа class, interface, extends/implements и так далее, и даже на теоретические вопросы типа "что означает буква D в принципах SOLID" умеют отвечать, но реально в ООП стиле не умеют ни думать, ни кодить. А это ключевой момент, как оказывается. То, что код обмазан ключевыми словами, не делает его автоматически ООП, и все те крутые возможности, которые парадигма могла бы дать, просто теряются. И при этом эти люди, что удивительно, и чего я не могу никак понять, искренне убеждены, что они используют ООП, а на попытки показать, что можно лучше/проще/понятнее/etc. реагируют достаточно негативно

Один мальчик был дебилом. Но он, как и положено дебилу, не знал, что он дебил. И поэтому жил счастливо и вполне неплохо. А другой мальчик был очень умным. Но, как положено умным, находился в поисках и терзаниях. Поэтому жил тревожно и неуверенно. Однажды они встретились:
— Жизнь прекрасна! — воскликнул мальчик-дебил.
— Спорно, — ответил умный мальчик.
— Почему? — спросил мальчик-дебил.
И умный мальчик стал долго и подробно объяснять законы диалектики, философствовать и объяснять материи, неподвластные уму мальчика-дебила. Но тот внимательно выслушал умного мальчика, пожал плечами и спросил:
— Ты что, дебил?

Скорее это классическое - "горе от ума", правда не великого ума. По настоящему умный человек ответил бы - "Ты прав, она прекрасна!". Это субъективная эмоциональная оценка, и с т.з. "мальчика-дебила", она верна на все 100%.

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

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

Так и знал, что при обсуждении ООП начнётся обсуждение дебилов :)

Да, все верно. Вообще ООП не является ценностью.

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

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

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

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

Вычислительные процессы - это не кошки с собаками.

Вычислительные процессы = данные + процедуры их обработки. Пихать их в один объект - крайне сомнительная идея.

  1. Процедуры обработки данных редко состоят из обработки только одного типа данных. Очень редко они имеют вид inc(T t). Практически всегда процедуры обработки имеют вид T t + R r. Как прикажете это реализовать? Метод add класса T с параметром типа R? А почему не наоборот? А если этих типов много? А если в процедура обрабатывает не два, а три, четыре параметра четырех разных типов? Кто их этих типов главный?

  2. Как развивать класс? У метода add класса T в проекте надо добавить тип K. Менять класс T? А если он библиотечный? Ладно, в шарпе придумалы костыль с расширением класса (или как он там называется?).

Все это приводит к тому, что ООП вырождается в систему, когда есть рекорды без методов (в Java таки это признали и создали костыль records), и классы-обработчики (процессоры, сервисы, контроллеры), которые определенным образом обрабатывают объекты-данные. В обработчики разрешается вкладывать только инфраструктурные классы. ВЕСЬ Spring на этом построен.

Если взять пример с кошечками-собачками, то в реальных проектах ВСЕГДА делается следующим образом:

public record Cat(float meat, float bone, int eye) {}
public record Dog(float meat, float bone, int eye) {}
public record HotDog(/*...*/) {}
public record KittensPie(/*...*/) {}
public record Meat(float quantity) {}
public record GroundMeat(float quantity) {}
public class MeatGrinder {
    // ссылки на сторонние зависимости
    public GroundMeat grind(Meat meat) {
        return new GroundMeat(100); //примерно
    }
}
public class Kitchen {
    private final MeatGrinder meatGrinder;

    public Kitchen(MeatGrinder meatGrinder) {
        this.meatGrinder = meatGrinder;
    }

    public HotDog makeHotDog(Dog[] dogs, float doughQuantity) {
        // использование meatGrinder
        return new HotDog();
    }

    public KittensPie makeKittensPie(Cat[] kittens, float doughQuantity) {
        // использование meatGrinder
        return new KittensPie();
    }
}

Где здесь ООП? Кто-нибудь видел?

Не всё то что class - ООП.

Секретный секрет: классы описывают объекты.

Но ООП не сводится просто к наличию объектов.

Тут как говорил Ленин - формально верно, по сути издевательство (ну или как-то близко к тому говорил, по памяти воспроизвожу)

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

Но ведь на чистом C тоже можно писать в ООП-стиле. Более того, первый компилятор C++, Cfront, так и делал: переписывал C++ на C и вызывал сишный компилятор.

Можно, не спорю. Я просто С привел как яркий пример ЯП заточенного не под ООП. Понятно что если прямо захотеть то и на С можно что-то почти ООП написать.

Ява просто позиционируется как ультра ООП такой, в ней все Объект (с небольшими оговорками), но получается что на ней сейчас часто пишут совсем не в ООП стиле.

Это все вообще без особых проблем перекладывается на чистый С (без ++)

Первый компилятор с С++ просто переводил текст в С и компилировал его компилятором С. Любая корректная программа на любом языке может быть переписана в машинный код. ООП - это не язык...

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

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

играть в darts ;-) звучит интересно

Единственный объективный минус ООП это его низкая производительность. Например, инструкция x = a + b в десятки раз быстрее чем x = a.plus(b)

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

Это если стрелять базукой по мухам. Какой-нибудь

some_func()
.and_then(do_something_else)
.and_then(/**/)
.on_error(handle_error_kind)

без ООП будет выглядеть сильно не очень, обмазывая код штуками а ля if err != nil и goto handle_error. При этом производительность страдать не будет никак ибо оптимизировать подобный код достаточно тривиально.

без ООП будет выглядеть сильно не очень

В смысле, что будет выглядеть как-то так?

try {
  const res1 = await some_func();
  const res2 = await do_something_else(res1, ...);
  /**/
} catch (error: ErrorKind) {
  /**/
}

А причём тут ООП?

Что первый, что второй вариант - это класс некоторых Future/Promise с кучкой скрытых методов, которые вызываются раннером асинхронных тасок. То есть без ООП у вас await бы даже не появился и гоняли бы циклы ручками. .and_then, on_error, resolve и прочие - это всё методы класса, которые неявно делают всю ту магию асинхронщины, инкапсулируя поведение. Если интересно как оно выглядит без ООП, то посмотрите на какой-нибудь асинхронный код на Си (например) с ручным ивентлупом и колбэками.

С чего вы взяли, что это класс (в ООПшном смысле класс, а не в смысле категории)? Вы думаете, конечный автомат без ООП не написать?

есть инкапсуляция, есть полиморфизм, что вам ещё для ооп класса надо? да и мой псевдокод явно не подразумевал какой-нибудь Idris/Haskell, а что-нибудь вроде js/ts/rust/kotlin/zig/go

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

То, что вы это можете с ними написать — не означает, что не написать без них.

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

Да причём тут поллинг? Мы, вроде бы, про асинки, реализовать которые вполне можно и без ООП, а не только «классом с кучей методов». Т.е., и сама по себе концепция async/await — не ООП, и реализация её внутри — тоже не обязана быть ООП.

Мой изначальный поинт был про то что без ООП мой псевдокод выше вгылядел бы как лапша из тыщ колбэков и двузначной цикломатической сложностью, как это происходит в Си. То что асинхронный код можно реализовать без ООП никто вроде и не оспаривал - полнота по Тьюрингу это вроде как гарантирует. async/await это синтаксический сахар, который работает вокруг классов Future/Promise, инкапсулируя часть поведения и позволяя оборачивать в них колбэки с произвольной сигнатурой, то бишь реализуя полиморфический интерфейс, который, в зависимости от языка, в том числе может быть реализован через наследование.

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

Или как соответствующая монада + do-нотация.

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

который работает вокруг классов Future/Promise, инкапсулируя часть поведения и позволяя оборачивать в них колбэки с произвольной сигнатурой

Покажите мне промисы и фьючеры в го)

Future или Promise - иногда полезные сущности, когда у тебя есть асинхронщина / коррутины.

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

В том числе даже на C и C++.

Или как соответствующая монада + do-нотация.

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

Покажите мне промисы и фьючеры в го)

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

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

вот они в си и не прибиты, ибо там в принципе нет этого на уровне синтаксиса. В современном C++ c co_yield/co_await у вас уже появляются промисы/футуры. В несовременном либо что-то аналогичное изобреталось обычно, либо как в Си. Можно по теме посмотреть Антона Полухина, он там рассказывает как они в userver stackfull корутины делали.

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

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

топик за (не)мёрвый ООП не знаю чего вы к тем монадам/нотациям пристали.

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

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

---

Лирическое отступление - представь, что мы где-то в начале нулевых и ты говоришь что-то в духе:

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

"Видите, ну это никак нельзя нормально сделать без классов, инкапсуляции и полиморфизма. Для этого и нужен ООП!"

Но на самом деле - это не какое-то замечательное применение ООП.

В современных языках (даже в том же современном C++ или в Go) ты не будешь городить тут никакие интерфейсы, а просто передашь замыкание.

В те годы в тех языках вещи делались через ООП с виртуальными функциями просто потому что в те годы в тех языках по другому ну никак нельзя было сделать)

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

---

Возвращаемся из лирического отступления - твой пример с .and_then - цепочкой - примерно того же вида, как мне кажется.

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

Действительно, при этом в некоторых языках async-await'ы завязаны на "подкапотные" классы с определёнными методами. Но строго говоря, это не обязательно - смотри тот же го.

В случае, если мы хотим разработать язык, в котором такие вещи (optional / async-исполнение / работа с io - штуки, имеющие много общего) - будут "выразимы" средствами самого языка - скорее всего заходить придётся со стороны очередного заимствования из мира экспериментальной функциональщины - какие-нибудь "эффекты" завезут.

***

Inb4: я тут даже не говорю, что ООП не нужен. Я комментирую конкретный пример.

В этом примере

  • ООП не является необходимым

  • ООП явно не блистает

Поэтому я до примера и докопался.

В современных языках (даже в том же современном C++ или в Go) ты не будешь городить тут никакие интерфейсы, а просто передашь замыкание

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

Все они так или иначе используются как в std::function, так и альтернативных реализациях...

ООП нужен, чтобы цепочку асинхронных вызовов нормально написать, а не лапшой колбеков, без них было бы ужасно

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

Видите, ну это никак нельзя нормально сделать без классов, инкапсуляции и полиморфизма. Для этого и нужен ООП!

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

ты не будешь городить тут никакие интерфейсы, а просто передашь замыкание.

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

Но на самом деле - это не какое-то замечательное применение ООП.

Вот любите вы всякие эпитеты. А какое тогда замечательно или блистательное? Разные подходы - это всё компромисы, позволяющие разруливать сложность кода не снижая продуктивность программиста. Пойти путём go в JS не могли из-за легаси, поэтому сделали как сделали. У Python похожая история. В Rust попробовали - не понравилось, сделали как в JS (почти). В С++ посмотрели на остальных и пошли как обычно своим путём. Всякие Java/C# изначально плясали с ООП, так что у них закономерно ничего неожиданного.

Покажите мне промисы и фьючеры в го)

В Го используется их обобщение - каналы. Только их зачем-то сделали особыми объектами. В итоге люди пишут такие костыли. На ООП.

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

Каналы - механизм множественной синхронизации исполнения сопрограмм. Промисы - однократная синхронизация в конце сопрограммы. Многопоточность тут вообще ни при чём.

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

Промисы к синхронизации примерно никак. Это класс, которая позволяет дождидаться завершения исполнения некоторой задачи.

Канал же создаёт двусторонюю связь между независимыми потоками исполнения. Псевдокод:

// создаём концы канала
(send, recv) = make_channel();

// spawn_worker спавнит независимый поток
// исполнения будь то thread или корутина

// один конец канала отдаём одному
spawn_worker(|send|{
  while (provider->has_data()) {
    send <- provide-> pull_some_data();
  }
  send.close();
});
// второй конец канала отдаём другому
spawn_worker(|recv|{
  while (recv->is_open()) {
    data_chunk <- recv.read();
    process(data_chunk);
  }
  send.close();
});

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

Ещё раз: код с асинк-авейтами — не объектно-ориентированный, и он как раз и «без ООП», и не «лапша из тыщ колбеков».

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

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

Оптимизирующие компиляторы эту проблему отчасти решают.

Почему только "отчасти"?

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

Например, инструкция x = a + b в десятки раз быстрее чем x = a.plus(b)

После встраивания-инлайнинга вторая запись превращается в первую. Если вообще она не является базовой, как в любых языках с разрешённой перегрузкой операторов.

Единственный объективный минус ООП это его низкая производительность. Например, инструкция x = a + b в десятки раз быстрее чем x = a.plus(b)

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

В C++, например, a+b внутренне превращается в a.operator+(b) или operator+(a,b), где operator+ - такое имя функции, аналогичное вашему plus. А дальше выполняется обычный себе код соответствующей операции (например, для complex это два примитивных сложения, для real и imag части).

Так что "десятки раз" - сказки, и, хм, интересно их происхождение. Поделитесь.

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

Что ООП не всегда уместно - факт. Но его хаятели очень преувеличивают недостатки.

А в этой статье точно-точно не про отдельно взятую java?

Статья 1999 года. С Java 1.2 - когда ещё даже базовый JIT толком не был отработан. С основным источником задержек в виде замены примитивных типов на "ящики", ещё и генерализованные до предела. Когда никто из грамотных не предлагал всерьёз переводить математику на такой стиль (хм, сейчас уже вроде можно, если осторожно.) При том, что с тех пор в эту сферу за почти 30 лет вложены огромные средства для оптимизации и ускорения, а теория и практика далеко шагнула вперёд.

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

И к теме, как писать - a+b или a.plus(b), это имеет отношение только в контексте такой же древней явы, но никак не ООП в целом.

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

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

Это даже не "Мнение". Это "реплика". Для него лучше подошло бы оформление в виде поста.

ООП — не серебряная пуля.

Проблема возникает когда ООП превращается в РОП. Ритуальное-обрядное программирование. Методы ООП становятся магическими пассами, а терминология становится заклинаниями. Некоторые руководители считают, что если делать "особенные действия", и произносить загадочные слова, то проект волшебным образом преобразится.

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

И так же естественно есть 💯 способов выстрелить себе в ногу с ООП

Я прямо стесняюсь копировать прямо сюда, дам ссылкой. LLM таки нагенерила список из сотни способов

https://LLMshare.syntxai.net/3db76e28-6009-fbab-85a88fd0

Почитал список проблем в основном из серии «сам себе злобный Буратино».

Если вы хотите создать себе проблем используя «слишком глубокую иерархию наследования», оставляя все поля объекта в публичном доступе и т.д. То причем здесь ООП и ООД, о которых товарищ Буч целую книгу написал как из правильно готовить?

Да да...

К простому казалось бы принципу прилагается вагон и маленькая тележка разъяснений

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

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

Сложность задачи - величина постоянная и она не зависит от метода её решения. Сложность решения - величина переменная и она, как раз, зависит от его (решения) метода...

Я на протяжении 12 лет наблюдаю жизнь PL/SQL кода в одной системе.

"Сделайте вот это!" "Ща, пять минут!"

Через два месяца "Чего-то данных не видно!" Через пару часов "Блин, бажина, надо чинить, дописывать" Через два дня "Вроде работает"

Через полгода "У нас появился новый клиент, у него всё то же самое, только чуть-чуть по другому" "Напишем IF..." Через месяц "Не, нужно отдельную процедуру лепить, Ctrl-C/Ctrl-V"

Через пару месяцев "Блин, у нас тут, оказывается, в самом начале баг - нужно всех клиентов править" "Ёлки, у всех по-разному написано...'

Через три года "Нужно после процедуры сохранять всё в истории". Через два месяца "Чё та в историю не всё попадает..."

Через двенадцать лет. "Процедура длиной в четыре тысячи строк? Да вы ох...ты боже мой..." "А они что, у каждого клиента в схеме разные?" "Да нет, там разница небольшая - ты на IF'ы смотри!"

Ну, вы меня поняли... Схематичненько так...

Нет кармы поставить плюсик, поэтому только так: какая же жиза, бох ты мой.

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

А кто мешает использовать модули? И хранимые процедуры? Кстати Oracle Pl/sql поддерживает объекты и работу с ними. Правда через одно место. Похоже писали чтобы было и прибивали гвоздями. Из серии можно-молодёжно, пусть будет.

А кто мешает использовать модули? И хранимые процедуры?

Так о них и речь - думаете, это что-то меняет?... Вместе с тысячестрочными процедурами... Просто, чтобы это всё правильно использовать, нужно худо-бедно заниматься проектированием - а кто там об этом думает, когда надо быстро и вчера? Проект начинали, грубо говоря, два человека - DBP и DBA (database programmer и database administrator)...

Кстати Oracle Pl/sql поддерживает объекты и работу с ними

Ну, это ещё не объектно-ориентированный, а, скорее, чисто объектный подход... В общем, в PL/SQL нет полной парадигмы, так, что-то похожее накручено. И снаружи этим не воспользуешься (из Oracle Forms, скажем)

Похоже писали чтобы было и прибивали гвоздями.

Не, ну так о том и речь - оно и есть... до сих пор, причём двенадцать лет это я только наблюдаю, а сам проект больше двадцати лет существует... Я в своё время пытался спросить "вы чё делаете?" На меня смотрели (и до сих пор смотрят), как на идиота, который ничего не понимает в fast stupid programming (а я действительно не понимаю). Но дело ещё в том, что сам процедурный подход стимулирует такой стиль.

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

Зато без этого вашего мерзкого ООП :)

За то у нас в сетях передачи данных нет такого бардака.

Завидуйте)

Ну, может есть немножко конечно…

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

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

Наследование уже в прошлом, в современном ООП уже перешли к composition over inheritance.

Это альтернатива, известная изначально, но пригодная далеко не всегда. "Перешли" - некорректный термин.

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

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

Предвзятость - да, безусловно согласен.

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

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

Было - TMyCalss* object = new TMyCalss(); object.method(); delete object;

Стало - TMyDataStruct* data = MyDataInit(); MyDataMethod(data); MyDataClose(data);

Все то же самое (а код стал даже понятнее, как минимум ключевых слов страшных меньше). В итоге из плюсов остаются только синтаксические удобства конкретного языка вроде автоматического вызова конструктора при выходе объекта выделенного на стеке из зоны видимости в С++. У идеи того что уже эти плюшки на 100% оправдывают разведение объектов во все поля, конечно, есть сторонники. Но нужно понимать что и они на самом деле не сторонники ООП, а просто любители всех этих плюшек, даже если сами этого не осознают.

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

Полиморфизм не является следствием наследования аж по 2 причинам:

  1. Есть (как минимум) аж 3 типа полиморфизма: ad hoc (перегрузка функций), параметрический (шаблонные структуры данных) и подтипы (наследование).

  2. Давайте представим такую ситуацию: у вас в языке есть только 2 вида классов: виртуальные интерфейсы (их объекты нельзя создать), и все остальные, которые обязаны быть ненаследуемыми (final / sealed, вот это вот). Наследование в обычном смысле тут отсутствует, может быть только реализация интерфейса. Вы можете произвольно комбинировать разные интерфейсы, которые хотите реализовать, но не можете от "уже рабочего" условного Animal унаследовать Cat - есть только композиция. Так устроены и успешно работают, например, Rust и Go. Тем не менее, вы можете создать, например, лист с объектами, реализующими условный ICat, и всем им сделать obj.meow(), вне зависимости от их фактического типа - хоть Domestic, хоть SnowLeopard, хоть IgrooshkaIzKitaya, то есть воспользоваться полиморфизмом в вашем изначальном смысле.

Разные функции с одинаковым названием называют полиморфизмом только формально (чтобы усложнить понимание).

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

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

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

Дай мне определение ООП и я скажу что ты не прав

"Да, Strategy можно заменить на Func " - кажется, от этого оно не перестанет быть стратегией, ведь нет ограничения на реализацию, а делегат есть по факту интерфейс одной функции.
Это вообще название, для просто мощного приема современного программирования.
Так то там что хочешь - стратегия. Половина других паттернов - стратегия. Стратегия обхода коллекции - итератор, стратегия иерархической обработки - компоновщик, стратегия создания объектов - фабрики, про команду вообще молчу.

Да, тоже забавно читать рассуждения про бессмысленность ООП. Вполне рабочая парадигма до сих пор. Не единственная, и я бы не заигрывался в пуризм. Но стопроцентно живая. Просто нет смысла играть в неё в некоторых (обычно простых) случаях. На свете есть очень много вещей, которые просто реализуются функцией, и не надо натягивать их на ООП. Меня в своё время поразил раздолбайский подход, принятый в Python. Разумеется, ООП там поддерживается, и часто активно используется, но на нижнем уровне часто очень много сделано просто функциями без какой-либо потуги на ООП. К этому стилю я после многих лет C++ привыкал, но потом оценил. Впрочем, иерархии классов я там всё равно использую, просто только там, где это действительно проще и понятнее. Даже многие паттерны проектирования там принято реализовывать совсем иначе. Но и там ООП много. Некоторые абстракции всё-таки хорошо реализуется именно средствами ООП. Но не все.

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

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

То, что классы -- это иногда просто контейнеры? Ну неймспейсы с функциями -- это тоже контейнеры. Зачем тогда ООП?

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

То, что GoF-паттерны -- проверенные временем костыли? Почему бы не использовать решения, в принципе не требующие костыли?

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

Может, конечно, не там смотрел. Был бы рад другим примерам из мира ООП.

Comming soon… « следите за моим телеграмм каналом» за публикациями. Готовлю статью с конкретными объектами и разными парадигмами программирования. Не переключайтесь, будет интересно :)

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

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

var db = new Database(connectionstring);
int count = db.Execute("select count(*) from users");

Я не выбираю драйвер ODBC, OleDb, OpenClient или ещё что-то. Мне это вообще не интересно. Объект db сам определит драйвер по строке подключения.

Я не вызывает Connect(). Объект db имеет внутреннее состояние и сам вызывает Connect() при необходимости.

Я не делаю явных преобразований типов и не уточняю их при запросе. Метод Execute() возвращает тип QueryResult, который умеет в неявное преобразование типов.

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

Если надо обрабатывать ошибку на месте - то у QueryResult есть поле Error, а у Database есть LastQuery.

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

А если забыл вызвать Disconnect() - ничего страшного, при финализации db сам это сделает.

Это пока не надо нормально транзакциями управлять и любая БД как файловое хранилище на стероидах используется.

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

Спасибо, но я про это вот всё не просто читал - у меня был курс по СУБД в университете, по которому я курсовую работу писал, защищал и защитил на "отлично". И обёртку эту писал тоже сам. А там, где нужна более сложная логика в SQL - предпочитаю хранимые процедуры. Чтобы на уровне бизнес-логики всё по-прежнему помещалось в одну строчку.

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

Спор "ООП vs ФП vs ПП" бессмысленен и неконструктивен. Вообще, ИМХО, нет особого смысла выделять отдельные парадигмы, учитывая ту мультипарадигмальную солянку, которая используется по факту на местах. Гораздо интереснее история "императивная vs декларативная разработка".

Публикации