Pull to refresh

Comments 35

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

И главное:

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

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

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

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

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

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

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

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

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

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

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

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

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

Articles