Обновить
4
0

Пользователь

Отправить сообщение
Наконец-то я начал понимать, что такое ООП
Да, видимо действительно не нужный.
А я вроде всегда говорил, что геттеры — это нормально.
так и есть
Я не помню точно функциональную терминологию, но смысл в том, что вы все равно куда-то прячете операцию с контекстом, и конкретный код, который достает что-то, что зависит от этого контекста через три посредника, об этом не знает (или, если знает, то не знает, как именно оно внутри работает).
разница в том, что контекст работы функции описан в её сигнатуре.
… и это ошибка времени выполнения. Так что вы от нее никуда не избавились.
действительно
Не «минимум», а сколько вам даст разработчик АПИ.
А более-менее никак. Детали будут зависеть от языка и среды выполнения.
Хорошо
Неа, это не ОО-код. Это код с абстракцией. В функционально-ориентированных языках та же самая фигня: мы вызываем order |> getCustomer |> context.bind (потому что getCustomer возвращает монаду, который нужен контекст), и мы больше не знаем, откуда что взялось.
Вам виднее что ОО, а что нет, так что соглашусь. Таким образом мы разобрались с тем что геттеры это норм.
Про монаду не очень понял.
Каким образом, если этот getContractById зависит от БД, а БД может быть недоступна — и мы получим ошибку недоступности БД?
АПИ декларирует недоступность БД, например, как один из вариантов ошибки. Либо более общий тип ошибки, если логика этого требует.
Ну вот видите. Это значит, что вы глядите на сигнатуру, но вы не знаете, от чего зависит поведение. Вы знаете максимум, но не минимум.
Да, Вы правы.
Ну почему же сразу «без каких-либо». Определенные гарантии у него есть.
Минимум гарантий.
getSomething(context), если вы хотите явное, или Something(context).getSomething(), если вы хотите внешнее.

отлично, как мне понять, что getSomething не надо обвешивать try/catch и опасаться за производительность?
Неа, это не «настоящий ОО-код». Это код, написанный на ОО-языке.
Детали реализации, опять же. В том плане что «потребителю не важно откуда берётся значение» — вполне себе ОО код.
И чего же?
Что может произойти runtime ошибка, например.
Вот у вас есть функция, которая, по сигнатуре, возвращает результат вычисления, зависящий от БД. Может ли она, на самом деле, от БД не зависеть?
может.
если это объявление происходит в не статическом методе класса A(допустим), то в качестве значения info1 будет присвоен объект (не статического)анонимного класса
new Info(){} // это синтаксис создания анонимного класса
Не статический анонимный класс содержит ссылку на класс, в методе которого был объявлен.

public class A {
    public Info m() {
        var info1 = new Info() {{
            email = "email@email.com";
            phone = "79998888888";
        }};
        return info1;
    }
}
...
A a = new A();
doSomething(a.m());

Таким образом метод m() вернёт объект info1 со ссылкой на объект a. Дальше можно положить info1 в какую-нибудь коллекцию, и таким образом существенно продлить жизненный цикл объекта a(вызвать утечку памяти).
Если поведение не было явно обещано — оно не сломано. Не надо закладываться на что-то за пределами контракта.
Если ни на что не опираться, то так не сложно и упасть. Без каких либо гарантий — это плохой код.
Предлагает: передавайте контекст явно
Something getSomething(). Куда передавать контекст?
Это не-ОО-специфичная деталь реализации. Контексты существовали (и широко использовались) в «чистом» процедурном программировании. ОО, если уж на то пошло, пытается их уменьшить.
Контекст существует в любой программе, не только процедурной. Тут вопрос в управлении контекстом.
В любом случае пример был реальный, с настоящим ОО кодом — реализацией JPA, так что тут глупо спорить.
Это два разных программиста — тот, который написал код внутри «плагина», и тот, который этот код вызвал. Первый не знает, как будет устроена монада, на которую он опирается, а второй не знает, какими конкретно эффектами монады воспользовался первый.
да, программист, написавший calcSomething знает только интерфейс монады. В этом месте он абстрагируется от вызова конкретного кода.
Но он знает что ожидать от вызова getContractById.
В случае же с геттером, который начал лезть в базу и упал с exception — никто этого не ожидает, этого нет в АПИ.
Вы мне еще скажите, что компилятор не позволит не использовать БД, если она передана.
?
Пусть код прочитает, если ему интересны детали. И, как программист, понимает, что код — это не контракт.
А разработчик этого АПИ знает, что в этот момент он подписался под слишком строгим контрактом, и постарается этого избежать.
Какой хитрый разработчик. Пользователь прочитает код, а разработчик в следующем релизе ему что-то сломает.
Только, как инженер могу сказать что ни к какому качеству и надёжности это отношение не имеет. И программисты должны избегать такого подхода, а не считать его чем-то хорошим.
Вот только это не ОО-проблема
Это именно ОО-проблема, ведь ООП не предлагает механизма для решения подобных задач. То, что там внутри threadlocal контекст — лишь деталь реализации, верно?
… и в этот момент, она не знает, какие побочные эффекты произойдут при ее выполнении.
это вопрос или что? Да, не знает. Зато программист знает что некоторые эффекты есть, а какие конкретно он знает когда запускает реализацию монады.
Компилятор просто не позволит ему использовать БД там, где для неё не установлен контекст.
Да нет, ООП решает реальные проблемы — в частности, реальную проблему управления сложностью. Если бы оно их не решало, им бы не пользовались. Да, оно решает их не идеально, с этим никто не спорит. Ну так языки программирования и не стоят на месте.
По моему ООП чаще создаёт больше проблем чем решает. Это, кстати, объясняет популярность совсем не ОО анемичной модели.
Я не понимаю связи.
значит я отвечал не на тот вопрос. Зачем потребителю это знание? Затем, что потребитель — программист, и он использует функционал, описываемый АПИ. В сказочном мире он ни о чём знать не должен — ну возвращает метод Something, какая разница откуда он это берёт? В реальности это имеет значение. Не всегда программа может находиться в нужном состоянии, может быть соединение с базой не установлено, или из определённого места не предполагается доступ к файлам и иные побочные эффекты. В случае, когда в АПИ описан доступ через поле — программист знает, что никаких дополнительных условий не требуется.
Или вы имеете в виду «не произойдет ошибки конкретного типа»?
runtime error скорее.
Реальный пример чего? Ambient context — это не ОО-специфичный паттерн.
Реальный пример того, как состояние контекста приводит к ошибкам при вызове метода.
Тогда вы не решили задачу сокрытия, которую, как вы говорили, можно решить.
Во первых я говорил что это ложная цель. Во вторых — почему не решил? Вполне себе. Вы пишите функцию
calcSomething :: MonadDocumentFlowT m => ContractId -> m Int
calcSomething cId = do
  c <- getContractById cId
  return (field1 c + field2 c)

которая что-то там считает по контракту. Ей всё равно, откуда берётся контракт. Но запустить это функцию там, где не установлен контекст для работы с БД(или что там требуется для запуска реализации) — не получится.
А знаете, что самое грустное? Самое грустное то, что это не про ООП. Это про явные и неявные побочные эффекты.
Самое грустное что ООП не решает реальные проблемы, и я говорю именно об этом.
Где декларирует?
?
Как вам это поможет бороться со сложностью реализации самих функций?
как минимум будет известно в каких местах точно не произойдёт ошибки.
«Один из вариантов» — нельзя, мы не знаем, какой будет в рантайме.
можно запросить сразу несколько в конкретной реализации монады. Сделать прокси-монаду, которая будет выбирать конкретную реализацию(если требуется переключение в рантайме).
Ну так много что используется, что не надо бы. Это же не повод на это ссылаться как на ошибку парадигмы.
повод, если это реальный пример. К тому же я и другие варианты предложил.
А теперь пользователь говорит: нет, я хочу знать, какие побочные эффекты потенциально возможны, и к какому контексту я могу или не могу обращаться.
он это и так знает, все ограничения описаны в функции запуска конкретной реализации монады.
runDocumentFlowT :: (MonadDatabase m, MonadNetwork m) => DocumentFlowT m a -> m a
Нет, не объяснили.
Тогда повторю: АПИ декларирует что при доступе к свойству никаких побочных эффектов не будет.
Что же в ней ложного? Как еще вы предлагаете управлять сложностью?
Как и обычно — разделить данные и функции, а не смешивать их.
На самом деле при использовании анемичной модели именно так и происходит, и accessor'ы там смотрятся как нечто совершенно не нужное.
То есть теперь мы будем видеть ограничение, которое говорит «у функции может быть любой побочный эффект», но не знаем, какой конкретно побочный эффект она имеет?
можно «любой», можно просто один из вариантов. Зависит от слоя и уровня АПИ.
Потому что ambient context — очень спорный паттерн.
Но он, тем не менее, используется. К тому же threadlocal контекст не единственная из возможных проблем. Даже если контекст удалось установить, он может находиться в нежелательном состоянии(Например, не запущена транзакция, или требуется авторизация).
А я сейчас спрашиваю не про ООП, а про предлагаемую вами парадигму.

getSomething :: (MonadDatabase m, MonadNetwork m) => m Something

или
getContractById :: MonadDocumentFlow m => ContractId -> m Contract

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

удачи с отловом утечек памяти потом.
дело самого объекта, его пользователей это волновать не должн
В какой-то теории — может быть. На практике — ещё как волнует, в этом и вопрос.
Про базу: в нормально спроектированном приложении, объект, которому нужна база, получает контекст или при конструировании, или параметром вызова метода
В hibernate сущность привязана к threadlocal контексту, и достать её оттуда — нельзя. Насколько hibernate нормально спроектирован — судить не могу.
(И да, я попробовал инъекцию в сущность после нашего прошлого обсуждения — с hibernate это довольно геморно, приходится даже менять флаги запуска в jvm; там суть в том что меняется поведение java и некий агент в classloader'е получает возможность выполнять код после конструирования объекта)
ООП можно рассматривать как вариацию модульного программирования
по мне так это действительно просто модульное процедурное программирование, с прикрученным сбоку механизмом полиморфизма подтипов и дутой теорией.
Потому что сокрытие ненужной реализации. Зачем потребителю это знание?
По моему, я это уже объяснил
Почему не скрывая-то? Accessor — это метод, если он не скрывает, то у вас вообще невозможно никакое сокрытие.
Сокрытие это ложная цель.
Никак. Потому что это противоречит задаче геттера.

Но если вам так хочется — идете и пишете в документации.
Лучше продолжу изучение инструментов, которые позволяют решать такие задачи.
А что делать, если есть три разных реализации, которые выбираются на этапе конфигурации приложения?
Предусмотреть ограничение, включающее эти возможные варианты.
А не надо устанавливать контекст в потоке.
Почему? А если это hibernate entity — не подскажите, как с ним из другого потока работать(даже если контекст установлен)?
И как это сделать, если по функции всегда должно быть понятно, какие у нее есть побочные эффекты?
в ООП — не знаю.
Ну так это частный случай, не надо по нему судить. Важно то, что геттер — это публичный API. Он может внутри лезть в поле, а может что-то делать, и вам, как потребителю, это не важно.

Почему потребителю это не важно? Ещё как важно.
Можно. Но это как раз нарушение сокрытия — вы больше не можете изменить поведение свойства прозрачно для потребителей
Если тут и нарушение чего-либо, то точно не принципа сокрытия. Ведь accessor'ы точно так же дают доступ потребителю, ничего фактически они не скрывают. К тому же принципов программирования столько, что нарушать их — это норма. Accessor'ы, к примеру, нарушают принципы KISS и YAGNI.(А огромное количество boilerplate кода в Java мешают навигации по коду)
Ну так описывайте так, чтобы было достаточно, в чем проблема-то?
Как мне описать геттер таким образом, что-бы потребитель был уверен, что ничего сверхъестественного не произойдёт?(вернётся значения поля)
Угу. То есть в первой версии функции вы укажете, что она считает внутри, во второй — что она лезет в БД, а в третьей — что она лезет в БД и внешний сервис. И каждый раз вам придется править вызывающий код, правильно?
Да, и это правильно. Например, я поменял функцию calcSomething так, что она теперь лезет в базу. Всё отлично, до тех пор пока код, который этого не знал, не решит обратиться к методу calcSomething из другого потока, в котором не установлен контекст для работы с БД. И узнаю я об этом только в рантайме.
Понимаете ли, один из способов борьбы со сложностью — это не знать, что делает вызываемый код (если это не важно). Вы предлагаете от этого отказаться.
Предлагаю отказаться тогда, когда это важно.
В том-то и дело, что вы немножко выворачиваете. У доменного объекта есть доменные свойства (т.е., свойства, которые бизнес-пользователь наблюдает у сущности, с которой взаимодействует). А геттеры и сеттеры — это способ их выражения в конкретном языке с ОО-парадигмой. А вот поля — это никого не интересующая деталь реализации.
С этим я, вроде, не спорил. И говорил об accessor'ах, которые напрямую обращаются к полям, в которых записаны значения свойств. Видимо, выразился не понятно.
Другой вопрос, что поля тоже можно вытащить наружу и тогда они будут напрямую представлять свойства, без геттеров и сеттеров.
Ну так оно и есть понятно и описано. Но это никак не мешает пользователю иметь свои собственные ожидания.

При этом достаточно очевидно, что нельзя описывать все, потому что это нарушит принцип сокрытия информации.
Всё описывать может и нельзя, но в данном случае речь о том, что информации недостаточно(для понимания пользователем).
Ну вот смотрите. Вы не ожидаете. А если я вызываю операцию (не функцию, это важно) «calcCurrentETA», я вполне ожидаю, что она может полезть куда-нибудь за состоянием трафика, доступностью машин и так далее.
В ФП у меня есть возможность явно указать на возможное поведение функции, а не гадать по её названию.
Оно не нужно, оно появляется. Потому что ООП — это принцип группировки данных и операций над ними.
ООП это просто другое название для модульного программирования?
Геттеры и сеттеры — это и есть свойства.
Свойства(не в смысле property, как синтаксического сахара для accessor'ов) в данном случае хранятся в полях объекта, а геттеры и сеттеры лишь предоставляют к ним доступ.
Ну это как бы дело потребителя, чего он ожидает или нет? Потребитель и от функции может чего-то «не ожидать».
Это дело создателя API — предусмотреть что от него будет ожидать потребитель. Сделать его понятным. Если я сделаю метод getSomething, который полезет что-то менять в базе — то это будет совсем не очевидно. От функции calcSomething я тоже не ожидаю что она полезет в сеть, и т.д.
Конечно, есть языки где намерения функции можно явно описать в сигнатуре, но в java-style ООП это всё на уровне соглашений.
В связности. ООП явным образом группирует данные и операции над ними.
Их можно группировать в модуле, для этого не нужно ООП.
Можно. Но будет ли это удобно?
Вполне себе удобно.

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

Паттерны есть и в ФП, просто более высокоуровневые. Паттерны 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 торчащими из сервисов.
Нет, там как раз ироничное название. И вся статья в целом — высмеивание аргументов со стороны «сторонников» ООП.
Просто помню что перевод был на хабре habr.com/ru/company/ruvds/blog/462483

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Зарегистрирован
Активность