Комментарии 89
Последнее выражение, по идее, должно всегда выдавать 4
Да с чего бы? А я вот говорю, что 2+2 должно всегда выдавать 22, ну или в крайнем случае 13. Кто вам, собственно, сказал, что сложение в десятеричной системе?
Статья фигня, а вот похожие статьи, которые выдал хабр заинтересовали.
А потом начинает критиковать Java и C#. Мягко говоря, критика инструментов, в которых автор даже сам не считает себя специалистом, редко когда выглядит убедительно.
Что важнее для программиста — писать надёжный или понятный код? Давайте посмотрим на условные экстремумы — не очень надёжный, но очень понятный код; и очень надёжный, но не очень понятный код.
deadline:
- code0 works
- code1 works. Sometime.
новая фича:
- code0 works.
- code1 works. Better than last time.
Новый программист реализует новую фичу:
- code0 broken. No one can understand why.
- code1 works, even better.
Насчёт последнего же — Но что будет, когда проект разрастется? OOP – это мина замедленного действия, которая неизбежно рванет, когда код проекта достигнет определенных размеров. Проекты начинают задерживаться, дедлайны срываться, разработчики падают без сил, а добавление новых фич становится практически невозможным.
Простите, это судьба любого большого проекта. Либо люди надрываются и проект живёт, либо отпускают и проект дохнет. OOP тут совершенно ни при чём, потому что миску лапши можно сделать даже из кода на haskell..
… Пожалуй, из кода на haskell можно сделать особо забористую миску лапши...
Нет, не перепутано. code0 написан правильно, а code1 — с ошибками, но понятно. После многократных рефакторингов code1 уже совсем хорош, code0 всё так же работает. А потом приходит "смена курса", и code1 легко мигрирует, а code0 — нет, потому что он хорошо работает и на этом список его достоинств закончен — поправить его уже нельзя, потому что свойства "быть понятным" ему недодали.
>не очень надёжный, но очень понятный код; и очень надёжный, но не очень понятный код.
поэтому неявно «не очень надёжный, но очень понятный код» ассоциируется с code0, а code1 наоборот. То что вы их дальше перенумеровали — далеко не очевидно.
А все дело в том, что изначально задан абсолютно не верный постулат о том, что программист должен писать "простой код". Ничего подобного! Программист должен писать код, обеспечивающий минимальную стоимость владения. Что означает, что код должен легко тестироваться, легко изменяться (читаемость кода входит именно сюда) и легко расширяться. Все. Больше программист никому ничего не должен.
Если уж на то пошло, программист не должен и писать "код, обеспечивающий минимальную стоимость владения", он должен писать код, выполняющий задачи бизнеса. Если уменьшение TCO в эти задачи не входит — то и код так писать не надо.
А ещё лучше, удалять код для решения задач бизнеса :)
Ненаписанный код выполняется мгновенно, не содержит багов, прост в понимании и использовании.
Обладает только одним минусом — обычно не выполняет требуемую задачу.
Но к этому идеалу нужно стремиться!
Нужно различать "должен по служебным обязанностям" (как наёмный работник) и "должен" в рамках этики. Если их путать, то и получаются "минимальные стоимости владения" (которые к этике программиста никак не относятся).
Внутри реакта, конечно, ООП, но снаружи этого не заметно.
ООП нет, а бинарники вдесятеро больше =)
Но да, ООП усложняет линкеру жизнь, что приводит к увеличению бинарника. Но к скорости загрузки либо исполнения обычно отношение имеет мизерное.
Кого в 2020 волнует размер бинарника? Ну кроме эмбедд устройств?
Да и размер он не просто так появляется, это же и время линковки, минутное уже напрягает.
Вы на всякий чих делаете гигабайтные бинарники, а нам потом делать CI/CD, которое должно смочь собрать билды скажем на 100 коммитов в день, потом еще их хранить какое-то время, потом билды которые кандидаты в релиз, но еще не релизные уже хранить полгода. И еще чтобы это все быстро копировалось и разворачивалось где-то в облаке. И вот уже проблема в том, что гигабит — не очень быстро.
Вы меня немного заставили подзависнуть пытаясь представить что там за гигабайтные бинари в облаке разворачиваются. При таких объемах логики и функционала запиханного внутрь бизнесу уже надо понимать что гигабит действительно мало и думать в сторону 40/100G. Да и вообще гиговый монолит для девопса должен выглядеть немного дико, где вся микросервисная архитектура и все вот это.
PS. Извиняюсь, я действительно не мог представить что там можно все раздуть до таких неприличных размеров. У нас высоконагруженное серверное решение развернутое на сотнях bare-metal и обмолачивающее 40г трафа написано на GO и весит около 20метров, так что с такими проблемами с деплоем я не сталкивался.
Был монолит. В дистрибутив были запихнуты 3rd party библиотеки.
По модной тенденции было принято идти в микросервисы. Монолит разделился на десятки микросервисов, но при этом КАЖДЫЙ тащит весь пакет 3rd party библиотек, вместо того чтобы выделить их в отдельный архив.
Повлиять на это сложно — команд разработчиков несколько, никто не хочет согласовывать версии зависимостей, системный архитектор был завален бизнес реквестами, разгребаться в этом не хотел. Так и жили.
Вот только не надо про раздутый размер бинарников Delphi. Они адекватны программам той сложности, для которых Delphi было спроектировано. То, что в качестве побочного эффекта получался 200кб бинарник у одного приложения из пустой формы — это побочный эффект. Кроме того, был способ уменьшить размеры, если рантайм ставить отдельно. Рантайм visualstudio либо идёт в комплекте к Винде, либо ставится при установке приложения, чем комплект bpl хуже?
Просто помню что перевод был на хабре habr.com/ru/company/ruvds/blog/462483
"Наш мозг развивался в сторону «делания чего-то», а не в сторону выстраивания сложной иерархии из абстрактных объектов"
Эм, так человек же социальное животное, и для выживания строил сложные социальные схемы в голове, когда ещё обезьяной был. Тот вожак, того не тронь, того почеши, с тем за ягодами сходить можно завтра.
То есть, как раз на обработку достаточно большого количества объектов и связей мозг заточен. Есть число Данбара, согласно с которым 150-200 обьектов и связей между ними человек в голове удерживает.
Иначе говоря, в реальной работе нужно выбирать те инструменты и технологии, которые используют окружающие тебя люди, ну или, по крайне мере, самые профессиональные и опытные из них.
ООП явно увеличивает количество программистов и помогает компаниям их менять в случае чего.
OOP считается самым большим бриллиантом в короне компьютерной науки.
Кем? Где цитаты и рейтиниги всех бриллиантов?
Лучшим способом организации кода.
Где это сказано? Кем? В каком контексте?
И все остальное в том же ключе. Нигде не приведено источник, кто так утверждает причем утверждает именно в таких формулировках.
Главной целью любого разработчика является написания надежного кода.
Спорно.
А какой самый лучший способ написать надежный код? Сделать код простым.
То есть сперва кто-то утверждал что ООП самый лучший способ написать код. Теперь самый лучший способ — это написать простой код.
А если ТЗ
Простота – это противоположность сложности. Отсюда следует, что первоочередная цель разработчика – уменьшить сложность кода.
Даже в fizzbuzz люди делают ошибки. А значит простой код — не самый лучший способ написать надежный код.
На самом деле надежность кода повышают тесты, в том числе и юнит тесты. Которые в ООП писать зачастую проще, чем в «простом неструктурированном коде».
Ибо моя цель не оскорбить, а указать на проблемы, которые есть в OOP.
Прежде чем указывать на проблемы, следовало бы понять его преимущества.
ООП не просто так стал стандартом в индустрии. Но ВСЕ вышеуказанные фразы с нападками на ООП выглядят так, словно человек нашел самые плохие реализации ООП, от человека который сам не освоил где и что ООП решает. Как при этом что-то критиковать — неясно.
Может, мне надо было научиться еще паре десятков «паттернов программирования»? В конце концов, у меня на всё это просто не осталось сил.
Паттерны — это вообще не ООП. Это просто архитектурный шаблон для решения часто встречающейся задачи. Паттерн можно реализвать в любой парадигме.
То, что изучая ООП нужно изучать и паттерны — это почти случайное совпадение. Просто в ООП паттерны легко описать.
Эта статья расскажет вам, какой путь длинной в десятилетие я прошел от OOP к функциональному программированию. С тех пор я не припомню случая, чтобы мне попался проект, для которого бы именно OOP подходил лучше всего.
Чисто на всякий случай. У вас были проекты, которые разрабатывались командой из более чем 50 разработчиков? А из 100? А проекты на больше чем 100 тысяч строк, а лучше на пару миллионов строк кода?
Цель создания OOP была одна – справиться со сложностью процедурного кода.
Но с тех пор не было приведено ни одного свидетельства в пользу того, что OOP лучше старого процедурного программирования.
Если разработчик правильно осилил ООП, у него код будет ЛОГИЧНО делится на классы. Не будет суперклассов. не будет зависимостей, когда джуниор поправил метод и это зааффектило и твой продукт и всех зависимых сервисов незаметно для всех.
Потому что юнит тесты не позволят это сделать, потому что четко видно где именно проблема и что аффектилось.
В процедурном программировании данные и функци, которые их обрабатывают не являются единым целым (это и есть смысл ООП), поэтому при правке какой-либо функции, нужно хорошо знать архитектуру проекта, чтобы знать что и где может быть задето. Нет четкой обратной связи между функцией и тем кто и где ее может вызвать.
Регулярное внесение изменений, смена состава разработчиков, включая ключевых сотрудников, миграция, апгрейды, обратная совместимость — с этим проще справляется ООП.
А сложность кода IMHO тут вообще не причем.
Именно поэтому в
Ну и да, есть определенная профессиональная деформация разработчиков из крупных проектов, которые потом пилят ООП везде, где надо и не надо. Но не ООП же за это ругать.
Реализация ООП в языках программирования — это уже совсем другое, и возможно к этому и можно придраться, но вот в вашей статье нет НИ СЛОВА про это. Есть только общая критика некоего ООП в вакууме.
Так вот. Основная суть ООП заключается вот в чем:
Есть данные. Есть методы которые напрямую работают с этими данными. Они должны быть в одном «модуле», который в ООП называют классом. Все внешние методы не имеют права обращаться к данными и аффектить их — они обращаются к различным геттерам или сеттерам. Именно поэтому легко и логично соблюдается разграничение ответственности. Легко реализовывается надежная версионность — например стандартное решение вместо правки метода сделать еще один с новым функционалом, сохраняя обратную совместимость.
И не будет такого, что в классе поменяли тип данных с int на float и поломался неизвестный никому компонент, который в этом классе что-то делал. Потому что в ООП он не может что-то делать. Он будет обращаться к геттерам и сеттерам, которые наружу возвращают тот тип данных, который и был ранее.
Такая вот абстракция, которая заменила «модульность».
Если взять некое ТЗ и реализовать его хорошими, опытными разработчиками в ООП и функциональном стиле, то я предполагаю, что все варианты в ООП будут либо очень похожи либо идентичны. А в функциональном — будут весьма отличаться.
А вот всякие интерфейсы, наследования и так далее — это уже больше относится к реализации в конкретных языках, чтобы увеличить гибкость и уменьшить повторяемость кода. Но к этому как раз можно было бы и придраться… но для этого надо быть разработчиком, а я им не являюсь.
Паттерны — это вообще не ООП. Это просто архитектурный шаблон для решения часто встречающейся задачи. Паттерн можно реализвать в любой парадигме.
То, что изучая ООП нужно изучать и паттерны — это почти случайное совпадение. Просто в ООП паттерны легко описать.
Паттерны есть и в ФП, просто более высокоуровневые. Паттерны java-style ООП в ФП это просто функции(по большей части).
Чисто на всякий случай. У вас были проекты, которые разрабатывались командой из более чем 50 разработчиков? А из 100? А проекты на больше чем 100 тысяч строк, а лучше на пару миллионов строк кода?
Зачем спрашивать про большие проекты с кучей разработчиков, если даже небольшие проекты ( < 10 разработчиков за весь жизненный цикл; ~300000строк кода) уже очень сложно поддерживать, с огромными проблемами в производительности и согласованности логики?
Реализация ООП в языках программирования — это уже совсем другое, и возможно к этому и можно придраться, но вот в вашей статье нет НИ СЛОВА про это. Есть только общая критика некоего ООП в вакууме.
В статье критика Java-style ООП(Хотя изначально оно появилось в C++, а может и раньше). И да, этот вариант ООП сейчас самый популярный — в том же C# или PHP, или куче других языков используется именно он.
Так вот. Основная суть ООП заключается вот в чем
Уже не один год работаю с java и до сих пор не понимаю(раньше думал что понимал) в чём суть. Модульное программирование и без ООП существует, полиморфизм подтипов тоже.
Есть данные. Есть методы которые напрямую работают с этими данными. Они должны быть в одном «модуле», который в ООП называют классом. Все внешние методы не имеют права обращаться к данными и аффектить их — они обращаются к различным геттерам или сеттерам.
Обращение к геттерам и сеттерам это и есть прямой доступ к данным, просто с другим синтаксисом(в 99% случаев).
А смешивание данных и функций практически всегда только мешает
Именно поэтому легко и логично соблюдается разграничение ответственности. Легко реализовывается надежная версионность — например стандартное решение вместо правки метода сделать еще один с новым функционалом, сохраняя обратную совместимость.
Вместо нормального рефакторинга — 10 одинаковых методов с разными версиями? Что-то не очень выглядит. И в большинстве случаев это просто не будет работать.
Например, добавилось новое свойство, которое изменит поведение методов в некоторых случаях. И что делать отдельные методы, которые будут вести себя по-новому? А то, что старый функционал будет неправильно работать под новые требования?
И не будет такого, что в классе поменяли тип данных с int на float и поломался неизвестный никому компонент, который в этом классе что-то делал. Потому что в ООП он не может что-то делать. Он будет обращаться к геттерам и сеттерам, которые наружу возвращают тот тип данных, который и был ранее.
Такая вот абстракция, которая заменила «модульность».
Т.е. у Вас private float x; а геттеры и сеттеры int x? Нет, так не бывает, сигнатуру геттеров и сеттеров в этом случае тоже поменяют. Потребуется рефакторинг, который значительно легче проводить в языках вроде hs.
Если взять некое ТЗ и реализовать его хорошими, опытными разработчиками в ООП и функциональном стиле, то я предполагаю, что все варианты в ООП будут либо очень похожи либо идентичны. А в функциональном — будут весьма отличаться.Очень сомневаюсь, ведь набор хороших практик ООП — это описание того, как не надо делать, а не посыл «как надо».
В итоге появляются всякие мутанты с anemic моделью и hibernate entity торчащими из сервисов.
Обращение к геттерам и сеттерам это и есть прямой доступ к данным, просто с другим синтаксисом
Неа. Обращение к геттерам и сеттерам — это обращение к некоему внешнему API объекта, которое внутри себя что-то уже делает, а вот что — потребителям знать не надо.
Вместо нормального рефакторинга — 10 одинаковых методов с разными версиями?
На самом деле, лучше, конечно, не десять методов, а десять интерфейсов — так ориентироваться удобнее. Но это не суть.
Например, добавилось новое свойство, которое изменит поведение методов в некоторых случаях. И что делать отдельные методы, которые будут вести себя по-новому?
Если вам надо сохранить обратную совместимость — а речь именно о таком случае, потому что зачем иначе вся эта боль — то да, именно так.
Т.е. у Вас private float x; а геттеры и сеттеры int x? Нет, так не бывает, сигнатуру геттеров и сеттеров в этом случае тоже поменяют
Да нет, вполне бывает. Да, придется подумать, как себя вести геттеру, когда внутри не-целое число — но это проблема того, как вообще себя должно вести API с обратной совместимостью.
Неа. Обращение к геттерам и сеттерам — это обращение к некоему внешнему API объекта, которое внутри себя что-то уже делает, а вот что — потребителям знать не надо.В теории. Беда в том, что на практике это не всегда так. Геттеры и сеттеры в основном напрямую меняют свойства, а когда нет — потребитель может и не ожидать такого поведения.
Если вам надо сохранить обратную совместимость — а речь именно о таком случае, потому что зачем иначе вся эта боль — то да, именно такНовые функции для других версий можно добавлять и в процедурном коде, не вижу в чём тут профит от ООП.
Да нет, вполне бывает. Да, придется подумать, как себя вести геттеру, когда внутри не-целое число — но это проблема того, как вообще себя должно вести API с обратной совместимостью.не спорю, плохой код везде бывает(хотя конкретно такого кейса я не видел). Но геттеры и сеттеры для его сокрытия совершенно не нужны. Точно так же можно использовать разные версии структуры данных, с разным набором полей.
Беда в том, что на практике это не всегда так.
На практике любой принцип хорош ровно настолько, насколько хороши люди, которые им пользуются.
Геттеры и сеттеры в основном напрямую меняют свойства
Геттеры и сеттеры — это и есть свойства.
потребитель может и не ожидать такого поведения.
Ну это как бы дело потребителя, чего он ожидает или нет? Потребитель и от функции может чего-то "не ожидать".
Новые функции для других версий можно добавлять и в процедурном коде, не вижу в чём тут профит от ООП.
В связности. ООП явным образом группирует данные и операции над ними.
Но геттеры и сеттеры для его сокрытия совершенно не нужны
Конечно, не нужны. Но с ними проще.
Точно так же можно использовать разные версии структуры данных, с разным набором полей.
Можно. Но будет ли это удобно?
Геттеры и сеттеры — это и есть свойства.Свойства(не в смысле property, как синтаксического сахара для accessor'ов) в данном случае хранятся в полях объекта, а геттеры и сеттеры лишь предоставляют к ним доступ.
Ну это как бы дело потребителя, чего он ожидает или нет? Потребитель и от функции может чего-то «не ожидать».Это дело создателя API — предусмотреть что от него будет ожидать потребитель. Сделать его понятным. Если я сделаю метод getSomething, который полезет что-то менять в базе — то это будет совсем не очевидно. От функции calcSomething я тоже не ожидаю что она полезет в сеть, и т.д.
Конечно, есть языки где намерения функции можно явно описать в сигнатуре, но в java-style ООП это всё на уровне соглашений.
В связности. ООП явным образом группирует данные и операции над ними.Их можно группировать в модуле, для этого не нужно ООП.
Можно. Но будет ли это удобно?Вполне себе удобно.
Свойства(не в смысле property, как синтаксического сахара для accessor'ов) в данном случае хранятся в полях объекта, а геттеры и сеттеры лишь предоставляют к ним доступ.
В том-то и дело, что вы немножко выворачиваете. У доменного объекта есть доменные свойства (т.е., свойства, которые бизнес-пользователь наблюдает у сущности, с которой взаимодействует). А геттеры и сеттеры — это способ их выражения в конкретном языке с ОО-парадигмой. А вот поля — это никого не интересующая деталь реализации.
Это дело создателя API — предусмотреть что от него будет ожидать потребитель. Сделать его понятным.
Ну так оно и есть понятно и описано. Но это никак не мешает пользователю иметь свои собственные ожидания.
При этом достаточно очевидно, что нельзя описывать все, потому что это нарушит принцип сокрытия информации.
От функции calcSomething я тоже не ожидаю что она полезет в сеть, и т.д.
Ну вот смотрите. Вы не ожидаете. А если я вызываю операцию (не функцию, это важно) "calcCurrentETA", я вполне ожидаю, что она может полезть куда-нибудь за состоянием трафика, доступностью машин и так далее.
Их можно группировать в модуле, для этого не нужно ООП.
Оно не нужно, оно появляется. Потому что ООП — это принцип группировки данных и операций над ними.
Вполне себе удобно.
Ну вот а кому-то удобно делать то же самое в ООП.
В том-то и дело, что вы немножко выворачиваете. У доменного объекта есть доменные свойства (т.е., свойства, которые бизнес-пользователь наблюдает у сущности, с которой взаимодействует). А геттеры и сеттеры — это способ их выражения в конкретном языке с ОО-парадигмой. А вот поля — это никого не интересующая деталь реализации.С этим я, вроде, не спорил. И говорил об accessor'ах, которые напрямую обращаются к полям, в которых записаны значения свойств. Видимо, выразился не понятно.
Другой вопрос, что поля тоже можно вытащить наружу и тогда они будут напрямую представлять свойства, без геттеров и сеттеров.
Ну так оно и есть понятно и описано. Но это никак не мешает пользователю иметь свои собственные ожидания.Всё описывать может и нельзя, но в данном случае речь о том, что информации недостаточно(для понимания пользователем).
При этом достаточно очевидно, что нельзя описывать все, потому что это нарушит принцип сокрытия информации.
Ну вот смотрите. Вы не ожидаете. А если я вызываю операцию (не функцию, это важно) «calcCurrentETA», я вполне ожидаю, что она может полезть куда-нибудь за состоянием трафика, доступностью машин и так далее.В ФП у меня есть возможность явно указать на возможное поведение функции, а не гадать по её названию.
Оно не нужно, оно появляется. Потому что ООП — это принцип группировки данных и операций над ними.ООП это просто другое название для модульного программирования?
И говорил об accessor'ах, которые напрямую обращаются к полям, в которых записаны значения свойств.
Ну так это частный случай, не надо по нему судить. Важно то, что геттер — это публичный API. Он может внутри лезть в поле, а может что-то делать, и вам, как потребителю, это не важно.
Другой вопрос, что поля тоже можно вытащить наружу и тогда они будут напрямую представлять свойства, без геттеров и сеттеров.
Можно. Но это как раз нарушение сокрытия — вы больше не можете изменить поведение свойства прозрачно для потребителей.
Всё описывать может и нельзя, но в данном случае речь о том, что информации недостаточно(для понимания пользователем).
Ну так описывайте так, чтобы было достаточно, в чем проблема-то?
В ФП у меня есть возможность явно указать на возможное поведение функции
Угу. То есть в первой версии функции вы укажете, что она считает внутри, во второй — что она лезет в БД, а в третьей — что она лезет в БД и внешний сервис. И каждый раз вам придется править вызывающий код, правильно?
Понимаете ли, один из способов борьбы со сложностью — это не знать, что делает вызываемый код (если это не важно). Вы предлагаете от этого отказаться.
ООП это просто другое название для модульного программирования?
Нет.
Ну так это частный случай, не надо по нему судить. Важно то, что геттер — это публичный API. Он может внутри лезть в поле, а может что-то делать, и вам, как потребителю, это не важно.
Почему потребителю это не важно? Ещё как важно.
Можно. Но это как раз нарушение сокрытия — вы больше не можете изменить поведение свойства прозрачно для потребителейЕсли тут и нарушение чего-либо, то точно не принципа сокрытия. Ведь accessor'ы точно так же дают доступ потребителю, ничего фактически они не скрывают. К тому же принципов программирования столько, что нарушать их — это норма. Accessor'ы, к примеру, нарушают принципы KISS и YAGNI.(А огромное количество boilerplate кода в Java мешают навигации по коду)
Ну так описывайте так, чтобы было достаточно, в чем проблема-то?Как мне описать геттер таким образом, что-бы потребитель был уверен, что ничего сверхъестественного не произойдёт?(вернётся значения поля)
Угу. То есть в первой версии функции вы укажете, что она считает внутри, во второй — что она лезет в БД, а в третьей — что она лезет в БД и внешний сервис. И каждый раз вам придется править вызывающий код, правильно?Да, и это правильно. Например, я поменял функцию calcSomething так, что она теперь лезет в базу. Всё отлично, до тех пор пока код, который этого не знал, не решит обратиться к методу calcSomething из другого потока, в котором не установлен контекст для работы с БД. И узнаю я об этом только в рантайме.
Понимаете ли, один из способов борьбы со сложностью — это не знать, что делает вызываемый код (если это не важно). Вы предлагаете от этого отказаться.Предлагаю отказаться тогда, когда это важно.
Почему потребителю это не важно?
Потому что сокрытие ненужной реализации. Зачем потребителю это знание?
Ведь accessor'ы точно так же дают доступ потребителю, ничего фактически они не скрывают.
Почему не скрывая-то? Accessor — это метод, если он не скрывает, то у вас вообще невозможно никакое сокрытие.
Как мне описать геттер таким образом, что-бы потребитель был уверен, что ничего сверхъестественного не произойдёт?
Никак. Потому что это противоречит задаче геттера.
Но если вам так хочется — идете и пишете в документации.
Да, и это правильно
А что делать, если есть три разных реализации, которые выбираются на этапе конфигурации приложения?
не решит обратиться к методу calcSomething из другого потока, в котором не установлен контекст для работы с БД
А не надо устанавливать контекст в потоке.
Предлагаю отказаться тогда, когда это важно.
И как это сделать, если по функции всегда должно быть понятно, какие у нее есть побочные эффекты?
Потому что сокрытие ненужной реализации. Зачем потребителю это знание?По моему, я это уже объяснил
Почему не скрывая-то? Accessor — это метод, если он не скрывает, то у вас вообще невозможно никакое сокрытие.Сокрытие это ложная цель.
Никак. Потому что это противоречит задаче геттера.Лучше продолжу изучение инструментов, которые позволяют решать такие задачи.
Но если вам так хочется — идете и пишете в документации.
А что делать, если есть три разных реализации, которые выбираются на этапе конфигурации приложения?Предусмотреть ограничение, включающее эти возможные варианты.
А не надо устанавливать контекст в потоке.Почему? А если это hibernate entity — не подскажите, как с ним из другого потока работать(даже если контекст установлен)?
И как это сделать, если по функции всегда должно быть понятно, какие у нее есть побочные эффекты?в ООП — не знаю.
По моему, я это уже объяснил
Нет, не объяснили.
Сокрытие это ложная цель.
Что же в ней ложного? Как еще вы предлагаете управлять сложностью?
Предусмотреть ограничение, включающее эти возможные варианты.
То есть теперь мы будем видеть ограничение, которое говорит "у функции может быть любой побочный эффект", но не знаем, какой конкретно побочный эффект она имеет?
Почему?
Потому что ambient context — очень спорный паттерн.
А если это hibernate entity — не подскажите, как с ним из другого потока работать(даже если контекст установлен)?
Ничего не знаю про hibernate entities.
в ООП — не знаю.
А я сейчас спрашиваю не про ООП, а про предлагаемую вами парадигму.
Нет, не объяснили.Тогда повторю: АПИ декларирует что при доступе к свойству никаких побочных эффектов не будет.
Что же в ней ложного? Как еще вы предлагаете управлять сложностью?Как и обычно — разделить данные и функции, а не смешивать их.
На самом деле при использовании анемичной модели именно так и происходит, и accessor'ы там смотрятся как нечто совершенно не нужное.
То есть теперь мы будем видеть ограничение, которое говорит «у функции может быть любой побочный эффект», но не знаем, какой конкретно побочный эффект она имеет?можно «любой», можно просто один из вариантов. Зависит от слоя и уровня АПИ.
Потому что ambient context — очень спорный паттерн.Но он, тем не менее, используется. К тому же threadlocal контекст не единственная из возможных проблем. Даже если контекст удалось установить, он может находиться в нежелательном состоянии(Например, не запущена транзакция, или требуется авторизация).
А я сейчас спрашиваю не про ООП, а про предлагаемую вами парадигму.
getSomething :: (MonadDatabase m, MonadNetwork m) => m Something
или
getContractById :: MonadDocumentFlow m => ContractId -> m Contract
а уже в ограничениях реализации монады MonadDocumentFlow указывается, какой контекст ей требуется. И без контекста БД её запустить не получится.
Тогда повторю: АПИ декларирует что при доступе к свойству никаких побочных эффектов не будет.
Где декларирует?
Как и обычно — разделить данные и функции, а не смешивать их.
Как вам это поможет бороться со сложностью реализации самих функций?
можно «любой», можно просто один из вариантов.
"Один из вариантов" — нельзя, мы не знаем, какой будет в рантайме.
Но он, тем не менее, используется.
Ну так много что используется, что не надо бы. Это же не повод на это ссылаться как на ошибку парадигмы.
а уже в ограничениях реализации монады MonadDocumentFlow указывается, какой контекст ей требуется
А теперь пользователь говорит: нет, я хочу знать, какие побочные эффекты потенциально возможны, и к какому контексту я могу или не могу обращаться.
(и да, вы только что продемонстировали, что сокрытие — не ложная цель)
Где декларирует??
Как вам это поможет бороться со сложностью реализации самих функций?как минимум будет известно в каких местах точно не произойдёт ошибки.
«Один из вариантов» — нельзя, мы не знаем, какой будет в рантайме.можно запросить сразу несколько в конкретной реализации монады. Сделать прокси-монаду, которая будет выбирать конкретную реализацию(если требуется переключение в рантайме).
Ну так много что используется, что не надо бы. Это же не повод на это ссылаться как на ошибку парадигмы.повод, если это реальный пример. К тому же я и другие варианты предложил.
А теперь пользователь говорит: нет, я хочу знать, какие побочные эффекты потенциально возможны, и к какому контексту я могу или не могу обращаться.он это и так знает, все ограничения описаны в функции запуска конкретной реализации монады.
runDocumentFlowT :: (MonadDatabase m, MonadNetwork m) => DocumentFlowT m a -> m a
?
Вы приводите "АПИ декларирует что при доступе к свойству никаких побочных эффектов не будет." как ответ на мой вопрос "Зачем потребителю это знание?"
Я не понимаю связи.
как минимум будет известно в каких местах точно не произойдёт ошибки.
Это, простите, как? У вас разработчики не ошибаются?
Или вы имеете в виду "не произойдет ошибки конкретного типа"?
Сделать прокси-монаду, которая будет выбирать конкретную реализацию(если требуется переключение в рантайме).
Ну так в этот момент вы вернулись к ситуации, когда вызывающий код не знает, с чем он работает.
повод, если это реальный пример.
Реальный пример чего? Ambient context — это не ОО-специфичный паттерн.
он это и так знает, все ограничения описаны в функции запуска конкретной реализации монады.
Тогда вы не решили задачу сокрытия, которую, как вы говорили, можно решить.
Так и ходим по кругу, да.
А знаете, что самое грустное? Самое грустное то, что это не про ООП. Это про явные и неявные побочные эффекты.
Я не понимаю связи.значит я отвечал не на тот вопрос. Зачем потребителю это знание? Затем, что потребитель — программист, и он использует функционал, описываемый АПИ. В сказочном мире он ни о чём знать не должен — ну возвращает метод Something, какая разница откуда он это берёт? В реальности это имеет значение. Не всегда программа может находиться в нужном состоянии, может быть соединение с базой не установлено, или из определённого места не предполагается доступ к файлам и иные побочные эффекты. В случае, когда в АПИ описан доступ через поле — программист знает, что никаких дополнительных условий не требуется.
Или вы имеете в виду «не произойдет ошибки конкретного типа»?runtime error скорее.
Реальный пример чего? Ambient context — это не ОО-специфичный паттерн.Реальный пример того, как состояние контекста приводит к ошибкам при вызове метода.
Тогда вы не решили задачу сокрытия, которую, как вы говорили, можно решить.Во первых я говорил что это ложная цель. Во вторых — почему не решил? Вполне себе. Вы пишите функцию
calcSomething :: MonadDocumentFlowT m => ContractId -> m Int
calcSomething cId = do
c <- getContractById cId
return (field1 c + field2 c)
которая что-то там считает по контракту. Ей всё равно, откуда берётся контракт. Но запустить это функцию там, где не установлен контекст для работы с БД(или что там требуется для запуска реализации) — не получится.
А знаете, что самое грустное? Самое грустное то, что это не про ООП. Это про явные и неявные побочные эффекты.Самое грустное что ООП не решает реальные проблемы, и я говорю именно об этом.
Зачем потребителю это знание? Затем, что потребитель — программист, и он использует функционал, описываемый АПИ.
Пусть код прочитает, если ему интересны детали. И, как программист, понимает, что код — это не контракт.
В случае, когда в АПИ описан доступ через поле — программист знает, что никаких дополнительных условий не требуется.
А разработчик этого АПИ знает, что в этот момент он подписался под слишком строгим контрактом, и постарается этого избежать.
Реальный пример того, как состояние контекста приводит к ошибкам при вызове метода.
Вот только это не ОО-проблема.
Ей всё равно, откуда берётся контракт. Но запустить это функцию там, где не установлен контекст для работы с БД(или что там требуется для запуска реализации) — не получится.
… и в этот момент, она не знает, какие побочные эффекты произойдут при ее выполнении.
Самое грустное что ООП не решает реальные проблемы
Да нет, ООП решает реальные проблемы — в частности, реальную проблему управления сложностью. Если бы оно их не решало, им бы не пользовались. Да, оно решает их не идеально, с этим никто не спорит. Ну так языки программирования и не стоят на месте.
Пусть код прочитает, если ему интересны детали. И, как программист, понимает, что код — это не контракт.
А разработчик этого АПИ знает, что в этот момент он подписался под слишком строгим контрактом, и постарается этого избежать.Какой хитрый разработчик. Пользователь прочитает код, а разработчик в следующем релизе ему что-то сломает.
Только, как инженер могу сказать что ни к какому качеству и надёжности это отношение не имеет. И программисты должны избегать такого подхода, а не считать его чем-то хорошим.
Вот только это не ОО-проблемаЭто именно ОО-проблема, ведь ООП не предлагает механизма для решения подобных задач. То, что там внутри threadlocal контекст — лишь деталь реализации, верно?
… и в этот момент, она не знает, какие побочные эффекты произойдут при ее выполнении.это вопрос или что? Да, не знает. Зато программист знает что некоторые эффекты есть, а какие конкретно он знает когда запускает реализацию монады.
Компилятор просто не позволит ему использовать БД там, где для неё не установлен контекст.
Да нет, ООП решает реальные проблемы — в частности, реальную проблему управления сложностью. Если бы оно их не решало, им бы не пользовались. Да, оно решает их не идеально, с этим никто не спорит. Ну так языки программирования и не стоят на месте.По моему ООП чаще создаёт больше проблем чем решает. Это, кстати, объясняет популярность совсем не ОО анемичной модели.
Какой хитрый разработчик. Пользователь прочитает код, а разработчик в следующем релизе ему что-то сломает.
Если поведение не было явно обещано — оно не сломано. Не надо закладываться на что-то за пределами контракта.
Только, как инженер могу сказать что ни к какому качеству и надёжности это отношение не имеет.
Ну да, потому что это имеет отношение к простоте изменения. И эти вещи друг с другом конфликтуют, да.
Это именно ОО-проблема, ведь ООП не предлагает механизма для решения подобных задач.
Предлагает: передавайте контекст явно.
То, что там внутри threadlocal контекст — лишь деталь реализации, верно?
Это не-ОО-специфичная деталь реализации. Контексты существовали (и широко использовались) в "чистом" процедурном программировании. ОО, если уж на то пошло, пытается их уменьшить.
Зато программист знает что некоторые эффекты есть, а какие конкретно он знает когда запускает реализацию монады.
Это два разных программиста — тот, который написал код внутри "плагина", и тот, который этот код вызвал. Первый не знает, как будет устроена монада, на которую он опирается, а второй не знает, какими конкретно эффектами монады воспользовался первый.
Компилятор просто не позволит ему использовать БД там, где для неё не установлен контекст.
Вы мне еще скажите, что компилятор не позволит не использовать БД, если она передана.
По моему ООП чаще создаёт больше проблем чем решает.
Ну, по-вашему — так. По-моему — не так. Никакого способа формально это проверить нет.
Если поведение не было явно обещано — оно не сломано. Не надо закладываться на что-то за пределами контракта.Если ни на что не опираться, то так не сложно и упасть. Без каких либо гарантий — это плохой код.
Предлагает: передавайте контекст явноSomething getSomething(). Куда передавать контекст?
Это не-ОО-специфичная деталь реализации. Контексты существовали (и широко использовались) в «чистом» процедурном программировании. ОО, если уж на то пошло, пытается их уменьшить.Контекст существует в любой программе, не только процедурной. Тут вопрос в управлении контекстом.
В любом случае пример был реальный, с настоящим ОО кодом — реализацией JPA, так что тут глупо спорить.
Это два разных программиста — тот, который написал код внутри «плагина», и тот, который этот код вызвал. Первый не знает, как будет устроена монада, на которую он опирается, а второй не знает, какими конкретно эффектами монады воспользовался первый.да, программист, написавший calcSomething знает только интерфейс монады. В этом месте он абстрагируется от вызова конкретного кода.
Но он знает что ожидать от вызова getContractById.
В случае же с геттером, который начал лезть в базу и упал с exception — никто этого не ожидает, этого нет в АПИ.
Вы мне еще скажите, что компилятор не позволит не использовать БД, если она передана.?
Без каких либо гарантий — это плохой код
Ну почему же сразу "без каких-либо". Определенные гарантии у него есть.
Something getSomething(). Куда передавать контекст?
getSomething(context)
, если вы хотите явное, или Something(context).getSomething()
, если вы хотите внешнее.
В любом случае пример был реальный, с настоящим ОО кодом — реализацией JPA, так что тут глупо спорить.
Неа, это не "настоящий ОО-код". Это код, написанный на ОО-языке.
Но он знает что ожидать от вызова getContractById.
И чего же?
?
Вот у вас есть функция, которая, по сигнатуре, возвращает результат вычисления, зависящий от БД. Может ли она, на самом деле, от БД не зависеть?
Ну почему же сразу «без каких-либо». Определенные гарантии у него есть.Минимум гарантий.
getSomething(context), если вы хотите явное, или Something(context).getSomething(), если вы хотите внешнее.
отлично, как мне понять, что getSomething не надо обвешивать try/catch и опасаться за производительность?
Неа, это не «настоящий ОО-код». Это код, написанный на ОО-языке.Детали реализации, опять же. В том плане что «потребителю не важно откуда берётся значение» — вполне себе ОО код.
И чего же?Что может произойти runtime ошибка, например.
Вот у вас есть функция, которая, по сигнатуре, возвращает результат вычисления, зависящий от БД. Может ли она, на самом деле, от БД не зависеть?может.
Минимум гарантий.
Не "минимум", а сколько вам даст разработчик АПИ.
отлично, как мне понять, что getSomething не надо обвешивать try/catch и опасаться за производительность?
А более-менее никак. Детали будут зависеть от языка и среды выполнения.
В том плане что «потребителю не важно откуда берётся значение» — вполне себе ОО код.
Неа, это не ОО-код. Это код с абстракцией. В функционально-ориентированных языках та же самая фигня: мы вызываем order |> getCustomer |> context.bind
(потому что getCustomer
возвращает монаду, который нужен контекст), и мы больше не знаем, откуда что взялось.
Что может произойти runtime ошибка, например.
Каким образом, если этот getContractById
зависит от БД, а БД может быть недоступна — и мы получим ошибку недоступности БД?
может.
Ну вот видите. Это значит, что вы глядите на сигнатуру, но вы не знаете, от чего зависит поведение. Вы знаете максимум, но не минимум.
Не «минимум», а сколько вам даст разработчик АПИ.
А более-менее никак. Детали будут зависеть от языка и среды выполнения.Хорошо
Неа, это не ОО-код. Это код с абстракцией. В функционально-ориентированных языках та же самая фигня: мы вызываем order |> getCustomer |> context.bind (потому что getCustomer возвращает монаду, который нужен контекст), и мы больше не знаем, откуда что взялось.Вам виднее что ОО, а что нет, так что соглашусь. Таким образом мы разобрались с тем что геттеры это норм.
Про монаду не очень понял.
Каким образом, если этот getContractById зависит от БД, а БД может быть недоступна — и мы получим ошибку недоступности БД?АПИ декларирует недоступность БД, например, как один из вариантов ошибки. Либо более общий тип ошибки, если логика этого требует.
Ну вот видите. Это значит, что вы глядите на сигнатуру, но вы не знаете, от чего зависит поведение. Вы знаете максимум, но не минимум.Да, Вы правы.
Таким образом мы разобрались с тем что геттеры это норм.
А я вроде всегда говорил, что геттеры — это нормально.
Про монаду не очень понял.
Я не помню точно функциональную терминологию, но смысл в том, что вы все равно куда-то прячете операцию с контекстом, и конкретный код, который достает что-то, что зависит от этого контекста через три посредника, об этом не знает (или, если знает, то не знает, как именно оно внутри работает).
АПИ декларирует недоступность БД, например, как один из вариантов ошибки.
… и это ошибка времени выполнения. Так что вы от нее никуда не избавились.
А я вроде всегда говорил, что геттеры — это нормально.так и есть
Я не помню точно функциональную терминологию, но смысл в том, что вы все равно куда-то прячете операцию с контекстом, и конкретный код, который достает что-то, что зависит от этого контекста через три посредника, об этом не знает (или, если знает, то не знает, как именно оно внутри работает).разница в том, что контекст работы функции описан в её сигнатуре.
… и это ошибка времени выполнения. Так что вы от нее никуда не избавились.действительно
значит я отвечал не на тот вопрос. Зачем потребителю это знание? Затем, что потребитель — программист, и он использует функционал, описываемый АПИ. В сказочном мире он ни о чём знать не должен — ну возвращает метод Something, какая разница откуда он это берёт? В реальности это имеет значение. Не всегда программа может находиться в нужном состоянии, может быть соединение с базой не установлено, или из определённого места не предполагается доступ к файлам и иные побочные эффекты. В случае, когда в АПИ описан доступ через поле — программист знает, что никаких дополнительных условий не требуется.
Потребитель — может быть программистов совершенно другого компонента. Ему не нужно разбираться как конкретно свойство класса используется внутри класса и другими методами класса. Если он пишет свой метод, и ему нужны какие-то данные, он просто получает их через геттер и может рассчитывать, что конкретно этот геттер всегда будет возвращать конкретный тип данных.
В этом и заключается главная вещь, что разработчик, который меняет что-либо в классе, уверен что все свойства используются только внутри класса, и может свободно ими оперировать. Для него важно, чтобы все публичные методы и свойства не менялись, чтобы не поломать чьи-то зависимости.
Про геттер: это методы получения значения по большому счёту. Будет оно доставаться из поля самого объекта, из поля родителя или вложенных объектов, браться из базы или вычисляться на лету из всего вышеперечисленного — дело самого объекта, его пользователей это волновать не должно (на уровне функциональных ожиданий, как минимум). Разумные ожидания разве что в синхронном однопоточном коде сразу же после вызова сеттера одноименный геттер вернёт то же значение, без значимых побочных эффектов (авторизация, кэшировпние, логирование и т. п. допустимы) Всё остальное, имхо, от слишком долгого сидения на проектах, где анемичная модель — архитектурное требование если не де-юре, то де-факто — логику мы пишем в сервисах.
Про базу: в нормально спроектированном приложении, объект, которому нужна база, получает контекст или при конструировании, или параметром вызова метода. Вы не сможете вызвать метод, пускай выглядящий как тупой геттер и получить рантайм ошибку неустановленного контекста, если объект отвечает базовым, имхо, требованиям к дизайну: явные зависимости и консистентные состояния. Приходится их иногда нарушать, обычно в ситуациях "нужно вчера" или "ужасно тормозит, конверсия упала на порядок", но это технический долг.
Ещё: ООП можно рассматривать как вариацию модульного программирования, если допустить, что у модуля могут быть несколько инстансов со своим стейтом, но с дополнительными ограничениями, типа эти стейты наружу не экспортируются, остальной код может их получать или изменять (неявно, через сеттеры) только через вызовы функций ь процедур модуля.
дело самого объекта, его пользователей это волновать не должнВ какой-то теории — может быть. На практике — ещё как волнует, в этом и вопрос.
Про базу: в нормально спроектированном приложении, объект, которому нужна база, получает контекст или при конструировании, или параметром вызова методаВ hibernate сущность привязана к threadlocal контексту, и достать её оттуда — нельзя. Насколько hibernate нормально спроектирован — судить не могу.
(И да, я попробовал инъекцию в сущность после нашего прошлого обсуждения — с hibernate это довольно геморно, приходится даже менять флаги запуска в jvm; там суть в том что меняется поведение java и некий агент в classloader'е получает возможность выполнять код после конструирования объекта)
ООП можно рассматривать как вариацию модульного программированияпо мне так это действительно просто модульное процедурное программирование, с прикрученным сбоку механизмом полиморфизма подтипов и дутой теорией.
Потому что ООП — это принцип группировки данных и операций над нимиТочнее, ООП — это способ удобно работать с многими состояниями системы. То есть локализация стейтов. А как там вокруг этих стейтов методы с полями группируются — дело десятое. Обычно это сделано коряво.
Не десятое как раз. Вместе, иначе неудобно и смысла в ООП мало, достаточно структур данных и функций с ним работающих.
Переводчик ни с кем не сражается. Переводчик просто перевел, ошибочно предположив, что это здесь кому-то нужно.
Объектно-ориентированное программирование – катастрофа за триллион долларов. Часть 1