Я в течение десятилетий программировал на объектно-ориентированных языках. Первым из них стал С++, затем был Smalltalk, и наконец .NET и Java. Я фанатично использовал преимущества наследования, инкапсуляции и полиморфизма, этих трёх столпов парадигмы объектно-ориентированного программирования. Мне очень хотелось воспользоваться обещанным повторным использованием и прикоснуться к мудрости, накопленной моими предшественниками в этой новой и захватывающей сфере. Меня волновала сама мысль о том, что я могу мапить объекты реального мира в классы и думал, что весь мир можно аккуратно разложить по местам.
Я не мог ошибаться сильнее.
Наследование — первый павший Столп
На первый взгляд, наследование — главное преимущество парадигмы ООП. Все приводимые нам упрощённые примеры иерархий, казалось, имели смысл.
А «повторное использование» — вообще термин дня. Нет, пожалуй, даже года, а то и больше. Я всё это проглатывал и бежал реализовывать в реальных проектах моё новообретённое видение.
Проблема бананов, обезьян и джунглей
С верой в сердце и проблемами, ждущими решения, я начинал создавать иерархии классов и писать код. И мир был в гармонии. Никогда не забуду тот день, когда я собирался извлечь всю пользу из повторного использования путём наследования существующего класса. Я долго ждал этого момента.
Появился новый проект, я не забывал о своей идее с классом и испытывал большой энтузиазм. Без проблем. Повторное использование спешит на помощь. Нужно просто взять класс из другого проекта и применить его. Ну… вообще-то… не просто класс. Понадобится родительский класс. Но… Но это всё. Гхм… погодите… кажется, нужен будет ещё родитель родителя… А потом… в общем, нужны ВСЕ родители. Хорошо… хорошо… Я с этим разберусь. Без проблем.
Ну замечательно. Теперь не компилируется. Почему??? А, понятно… Этот объект содержит этот другой объект. Так что он мне тоже нужен. Без проблем. Погодите… Мне не нужен всего лишь тот объект. Мне нужен его родитель, и родитель родителя, и т.д. Для каждого вложенного объекта мне нужны родители, а также их родители, и их родители, родители… Мда. Джо Армстронг, создатель Erlang, когда-то сказал прекрасные слова:
Проблема с ОО-языками заключается в том, что они тянут за собой всё своё окружение. Вы хотели всего лишь банан, но в результате получаете гориллу, держащую этот банан, и все джунгли впридачу.
Решение проблемы банана, обезьян и джунглей
Можно выйти из ситуации, не создавая слишком глубоких иерархий. Но поскольку наследование является ключом к повторному использованию, то любые накладываемые на этот механизм ограничения однозначно уменьшат преимущества, получаемые от него. Верно? Верно. Так что же делать бедному объектно-ориентированному программисту, который слепо поверил обещаниям? Агрегировать (contain) и делегировать (delegate). Об этом чуть позже.
Проблема ромба
Рано или поздно эта проблема поднимет свою уродливую и, в зависимости от языка, неразрешимую голову.
Большинство ОО-языков это не поддерживают, хотя оно и выглядит вполне здраво. Так в чём дело? Посмотрим на этот псевдокод:
Class PoweredDevice {
}
Class Scanner inherits from PoweredDevice {
function start() {
}
}
Class Printer inherits from PoweredDevice {
function start() {
}
}
Class Copier inherits from Scanner, Printer {
}
Обратите внимание, что классы
Scanner
и Printer
оба реализуют функцию start
. Тогда какую функцию start
унаследует класс Copier
? Та, что реализована классом Scanner
? Или классом Printer
? Не могут же они обе унаследоваться.Решение проблемы ромба
Решение очень простое: не делайте так. Вот именно. Большинство ОО-языков не позволят так сделать. Но… что если мне нужно смоделировать такую ситуацию? Мне нужно моё повторное использование! Тогда вы должны агрегировать и делегировать.
Class PoweredDevice {
}
Class Scanner inherits from PoweredDevice {
function start() {
}
}
Class Printer inherits from PoweredDevice {
function start() {
}
}
Class Copier {
Scanner scanner
Printer printer
function start() {
printer.start()
}
}
Класс Copier теперь содержит экземпляры
Printer
и Scanner
. Он делегирует реализацию функции start классу Printer
. А может так же легко делегировать Scanner
’у. Эта проблема — ещё одна трещина в Столпе Наследования.Проблема хрупкого базового класса
Итак, я уменьшаю свою иерархию и не позволяю ей становиться цикличной. Проблема ромба решена. И всё снова стало хорошо. Пока вдруг… Однажды мой код перестал работать. При этом я его не менял. Ну, наверное, это баг… Хотя, погоди… Что-то изменилось… Но этого не было в моём коде. Похоже, изменение произошло в классе, от которого я наследовал. Как изменение в базовом классе могло сломать мой код??? А вот как… Возьмём следующий базовый класс (он написан на Java, но вам будет легко разобраться с кодом, даже если вы не знаете этого языка):
import java.util.ArrayList;
public class Array
{
private ArrayList<Object> a = new ArrayList<Object>();
public void add(Object element)
{
a.add(element);
}
public void addAll(Object elements[])
{
for (int i = 0; i < elements.length; ++i)
a.add(elements[i]); // this line is going to be changed
}
}
ВАЖНО: Обратите внимание на выделенную строку. Позднее она изменится и всё сломает. У этого класса две функции интерфейса: add() и addAll(). Функция add() добавляет одиночный элемент, а addAll()— несколько элементов посредством вызова функции add(). А вот производный класс:
public class ArrayCount extends Array
{
private int count = 0;
@Override
public void add(Object element)
{
super.add(element);
++count;
}
@Override
public void addAll(Object elements[])
{
super.addAll(elements);
count += elements.length;
}
}
Класс
ArrayCount
— специализация общего класса Array
. Единственная разница в их поведении заключается в том, что ArrayCount
содержит счётчик количества элементов. Давайте подробнее разберём оба класса.Array add()
добавляет элемент в локальныйArrayList
.Array addAll()
вызывает локальныйArrayList
для добавления каждого элемента.ArrayCount add()
вызывает свой родительскийadd()
, а затем инкрементирует счётчик.ArrayCount addAll()
вызывает свой родительскийaddAll()
, а затем инкрементирует счётчик по количеству элементов.
И всё работает замечательно. А теперь критическое изменение выделенной строки:
public void addAll(Object elements[])
{
for (int i = 0; i < elements.length; ++i)
add(elements[i]); // эта строка была изменена
}
Что касается владельца базового класса, то он работает так, как заявлено. И проходит все автотесты. Но владелец не обращает внимание на производный класс. И владелец производного класса сильно разочарован. Теперь
ArrayCount addAll()
вызывает родительский addAll()
, который внутри себя вызывает add()
, управление которым уже было ПЕРЕХВАЧЕНО производным классом. В результате счётчик инкрементируется при каждом вызове add()
производного класса, а затем СНОВА инкрементируется по количеству элементов, добавляемых addAll()
производного класса. ТО ЕСТЬ, ЭЛЕМЕНТЫ ПОДСЧИТЫВАЮТСЯ ДВАЖДЫ.Раз такое может произойти, автор производного класса должен ЗНАТЬ, как был реализован базовый класс. И он должен быть информирован о каждом изменении в базовом классе, поскольку это может иметь непредсказуемое влияние на производный класс. Гхм! Эта огромная трещина будет всегда угрожать стабильности драгоценного Столпа Наследования.
Решение проблемы хрупкого базового класса
И снова на помощь спешат агрегирование и делегирование. Благодаря им мы переходим в программировании от стратегии «белого ящика» к стратегии «чёрного ящика». В первом случае мы должны следить за реализацией базового класса. А во втором случае мы можем полностью игнорировать эту реализацию, потому что не можем внедрить код в базовый класс, перехватив управление одной из его функций. Мы должны работать только с интерфейсом.
Это раздражающая тенденция… Ведь наследование должно было стать огромной победой с точки зрения повторного использования. Агрегирование и делегирование непросто реализовать в ОО-языках. Ведь они были разработаны с прицелом на облегчение наследования. Вероятно, вы уже испытываете удивление насчёт этого наследования. Но важнее то, что это могло уже поколебать вашу уверенность в мощи классификации посредством иерархий.
Проблема иерархии
Каждый раз, создавая новую компанию, мне приходится решать проблему, связанную с выбором места для документации компании. Нужно ли мне сделать папку Документы, а внутри неё создать папку Компания? Или мне нужно сделать папку Компания, а внутри неё — Документы? Оба варианта рабочие, но какой из них правильный? Какой лучше?
Идея иерархии “общее/частное” заключалась в том, чтобы были некие общие базовые классы (родители), и их специализированные версии — производные классы (дети). И даже ещё более специализированные, если следовать по цепочке наследования (см. схему иерархии форм в начале статьи). Но если родитель и ребёнок могут произвольно меняться местами, то с этой моделью явно что-то не так.
Решение проблемы иерархии
А не так с ней то… что иерархия “общее/частное” не работает. Так для чего тогда хороши иерархии?
Для включения (Containment).
Если посмотреть на реальный мир, то вы везде будете натыкаться на иерархии включения. А чего вы не найдёте, так это иерархии “общее/частное”. Не будем пока на этом останавливаться. Объектно-ориентированная парадигма изначально была взята из реального мира, наполненного объектами. Но потом она начала использовать порочные модели, вроде иерархии “общее/частное”, аналогов которым в жизни не существует. Зато мир наполнен иерархиями включения. Хороший пример — ваши носки. Они лежат ящике, который находится внутри шкафа, который находится внутри вашей комнаты, которая находится внутри вашего дома и т.д.
Другой пример иерархий включения — папки в компьютере. Они содержат файлы. Как мы их категорируем? Если вспомнить о документах компании, то не особо-то и важно, куда их класть, в папку Документы или в папку Барахло. Важно то, что я категорирую их с помощью тэгов. Я помечаю файл такими тэгами:
- Document
- Company
- Handbook
Тэги не имеют порядка или иерархии. К слову, это решает и проблему ромба. Тэги аналогичны интерфейсам, потому что вы можете присвоить одному документу много разных тэгов. И с учётом такого количества трещин, похоже, Столп Наследования обрушился. Прощай, наследование.
Инкапсуляция, второй обрушившийся столп
На первый взгляд, инкапсуляция — второе важнейшее преимущество объектно-ориентированного программирования. Переменные объекта защищены от доступа снаружи, то есть они инкапсулированы в объект. Больше не нужно беспокоиться о глобальных переменных, к которым обращается кто ни попадя. Инкапсуляция — это сейф для ваших переменных. Инкапсуляция просто НЕВЕРОЯТНАЯ!!! Долгой жизни инкапсуляции… До тех пор, пока не возникнет…
Проблема ссылки (The Reference Problem)
Ради повышения производительности, функциям передаются не значения объектов, а ссылки на них. Это означает, что и сами функции передают не объекты, а ссылки. Если ссылка на объект передаётся конструктору объектов, то он кладёт её в приватную переменную, защищённую инкапсулированием. Но переданный объект небезопасен! Почему? Потому в какой-то части нашего кода содержится указатель на объект, то есть код, вызывающий конструктор. Но он ДОЛЖЕН иметь ссылку на объект, в противном случае он не сможет передать её конструктору.
Решение проблемы ссылки
Конструктору придётся клонировать переданные объекты. Причём клон должен быть полным, то есть клонироваться будут все объекты, содержащиеся в переданном объекте, и все объекты, содержащиеся в этих объектах, и т.д. И всё ради повышения эффективности. Но дело в том, что не все объекты могут быть клонированы. Некоторые занимают ресурсы операционной системы, которые с ними ассоциированы, так что в лучшем случае клонировать их бесполезно, а в худшем — невозможно. В каждом из мейнстримовых ОО-языков существует такая проблема. Прощай, инкапсуляция.
Полиморфизм, третий обрушившийся Столп
Полиморфизм — это рыжеволосый пасынок Объектно-Ориентированной Троицы. Вроде Ларри Файна в этой компании. Куда бы они не отправились, он был с ними, но всегда на вспомогательных ролях. Это не значит, что полиморфизм плох, просто для его использования вам не нужен ОО-язык. Его могут предоставить интерфейсы. Причём безо всякой дополнительной объектно-ориентированной нагрузки. Кроме того, в случае с интерфейсами вы не ограничены количеством возможных поведений, которые можно сочетать. Так что без лишних разговоров прощаемся с ОО-полиморфизмом, и приветствуем полиморфизм на основе интерфейсов.
Нарушенные обещания
ООП в последнее время наобещало нам немало. Из-за этих обещаний наивные программисты сидят в учебных аудиториях, изучают блоги и записываются на онлайн-курсы. Мне понадобились годы, чтобы осознать, как ООП лгало мне. Я тоже смотрел на него широко раскрытыми глазами, был неопытен и доверчив. И я обжёгся. Прощай, объектно-ориентированное программирование.
И что теперь?
Привет, функциональное программирование. Мне очень понравилось работать с тобой в последние годы. Чтобы вы знали: я не принимаю любые ваши обещания за чистую монету. Я сначала проверю их, чтобы поверить. Обжёгшись на молоке, будешь дуть и на воду. Ну вы поняли.
Комментарии (325)
symbix
05.08.2016 13:38+44Эту ересь уже несколько раз обсуждали в комментариях. :)
Товарищ сделал плохую архитектуру и жалуется на парадигму. Ну-ну. При этом он еще и не понимает, что главное в ООП — это именно интерфейсы и есть, а наследование вообще необязательно.
Посмотрим, как через пару лет он напишет то же самое про ФП :)shamanis
05.08.2016 14:02+21Что вы еще ожидали в блоге майл.ру :)
Плохая архитектура это их конек.herr_kaizer
06.08.2016 09:21Эм, на основании чего делаются такие утверждения?
msnake
08.08.2016 12:03+1На основании кучи лет наблюдений за продуктами компании, статьями в журналах, выступлениями на техконференциях. Или ты думаешь что мейлру просто так свою репутацию заслужило?)
G-M-A-X
05.08.2016 14:22+2Тогда зачем было лепить из ООП дуру, если три кита, на которых он стоит — фигня? :)
П.С.
ООП использую в меру, без фанатизма.symbix
05.08.2016 15:13+4Это смотря что считать китами.
«Actually I made up the term „object-oriented“, and I can tell you I did not have C++ in mind.» — Alan Kay
areht
05.08.2016 18:59+3Кто вам сказал, что именно это киты ООП?
GoF 20 лет назад разжевали, что наследование использовать не надо, и как именно не надо.BlackRaven86
05.08.2016 22:59
poxu
06.08.2016 14:25+1Главное в Объектно Ориентированном Программировании это, как несложно догадаться, объекты. Объект это такая штука, которая имеет поведение и внутреннее состояние. Интерфейсы в общем — то это поведение и есть, но лучше раскрывать мысль полностью.
Что любопытно, конкретная реализация интерфейсов например в Java — плохая. Использовать для интерфейсов отдельное ключевое слово implements — плохо. Но это, конечно, не меняет того факта, что поведение со скрытым состоянием в ООП — главное.
IIvana
07.08.2016 07:40Догадаться несложно — поверить тяжело )
Мне жаль, что давным давно я использовал термин «объект» для этой темы, потому что из-за этого многие люди фокусируются на меньшей из идей. Большая идея это «сообщения» Ключ к созданию хороших масштабируемых систем это проработка механизмов общения модулей, а не проработка их внутренних свойств и поведения.
poxu
08.08.2016 15:49Поведение это собственно и есть то, как модули общаются друг с другом. В цитате про внутреннее поведение же. Интерфейсы они о том поведении, которое наблюдается снаружи.
Source
07.08.2016 14:46Добавлю ссылку на первоисточник цитаты из сообщения IIvana: http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html
Полный текст интереснее.
kohus
05.08.2016 13:40+14Проблема повторного использования вообще не касается ООП. В функциональном программировании при использовании чужого кода тоже надо знать, как он работает. И да, в следующей версии он может поменяться и все сломать.
arvitaly
05.08.2016 14:10-6Проблема повторного использования — единственная проблема в программировании (речь не только о чужом коде, но и о своем). И да, в ФП тоже меняются интерфейсы, но происходит это значительно реже, так как меняется лишь одна функция. Если же в ООП меняется один метод или публичное свойство, то все остальные тоже идут на свалку, так как меняется интерфейс всего объекта. Мало того, если меняется интерфейс любого из родителей, то на свалку идет вся эта гора. Грубо говоря, ФП позволяет делать детали максимально мелкими. Это добавляет сложности композиции, но это уже к вопросу об умственных способностях программистов. Собственно, нет вопроса, что эффективнее, есть вопрос — где взять столько умов. Поэтому, простые языки, типа Go, в промышленности эффективнее, гораздо проще менять разработчиков.
lair
05.08.2016 14:13+8Проблема повторного использования — единственная проблема в программировании
Да ладно. В зависимости от того, чьими глазами вы смотрите, либо "единственная проблема" — это управление сложностью, либо проблем две, и это именование и инвалидация кэша.
arvitaly
05.08.2016 14:22-8А нет никакой сложности в написании линейной программы, ей не нужны никакие именования, а инвалидация кэша не относится к задачам программирования.
XPC_Ro_D12
06.12.2016 18:27Автору на будущее. Пишется не P.S.S, а P.P.S.
arvitaly
05.08.2016 14:41-14> Во-первых, это само по себе не правда. Во-вторых, только очень маленькое количество программ линейно.
Все программы линейны.
> Это тоже не правда. Во-первых, она сама должна быть поименована, во-вторых, используемые ей операции должны быть поименованы. Это необходимый минимум.
Не должна. Не должны.
>Почему это?
Потому что, тезисы должен доказывать тот, кто их выдвигает.lair
05.08.2016 14:44+10Все программы линейны.
В таком случае тезис "нет никакой сложности в написании линейной программы" ложен. Потому что если все программы линейны, то этот тезис эквивалентен "нет никакой сложности в написании любой программы", а это заведомо не так.
Не должна. Не должны.
То есть используемая вами программа не имеет никакого имени, и используемые в ней операции — тоже не имеют никаких имен?
Потому что, тезисы должен доказывать тот, кто их выдвигает.
Ну вот и доказывайте тезис о том, что "инвалидация кэша не относится к задачам программирования" — вы же его выдвинули?
arvitaly
05.08.2016 14:49-15> а это заведомо не так.
Только в вашей вселенной. Это ваш тезис.
> То есть используемая вами программа не имеет никакого имени, и используемые в ней операции — тоже не имеют никаких имен?
Именно так. К моей задаче программиста не входит выдумывать никакие названия. Кроме как для целей повторного использования кода.
>Ну вот и доказывайте тезис о том, что «инвалидация кэша не относится к задачам программирования» — вы же его выдвинули?
Нет, вы выдвинули тезис, что это задача программирования. Если вы уже забыли об этом, у вас действительно все плохо.lair
05.08.2016 14:53+4Только в вашей вселенной. Это ваш тезис.
То есть вы утверждаете, что нет никакой сложности в написании любой программы?
Именно так.
Приведите пример. Я не знаю ни одной программы, не имеющей названия.
Нет, вы выдвинули тезис, что это задача программирования.
Я его не выдвинул, я процитировал весьма известное высказывание (жаль, что вы его не знаете). Впрочем, доказывается это утверждение просто: за последнюю неделю я, как программист, дважды столкнулся с необходимостью сделать инвалидацию кэша (строго в рамках своей работы); следовательно, эти задачи входят в задачи программирования.
arvitaly
05.08.2016 15:04-11> То есть вы утверждаете, что нет никакой сложности в написании любой программы?
Именно это я и утверждаю. Естественно, если вы, опять же, не забыли про декларированную мною «единственную сложность» и целью не является выдрать фразу из контекста.
> Приведите пример. Я не знаю ни одной программы, не имеющей названия.
Опять я должен доказывать ваш тезис. Бритва Оккама плачет. Вы решили, что программе нужно название, а я должен доказывать, что Бога нет. Печаль. Но ок, вот вам программа без названия
Достать руку из кармана
Положить ее на клавиатуру
Включить мозг и перестать троллить, и так слишком много в мире нерешенных интересных задач
>Я его не выдвинул, я процитировал весьма известное высказывание (жаль, что вы его не знаете).
Жаль, но логикой тут и не пахнет. То, что вам, как программисту ставят задачи не программирования, опять же жаль, но никак не доказывает ваш тезис.lair
05.08.2016 15:10+6Именно это я и утверждаю.
В таком случае вся индустрия внезапно занимается не тем. Угу.
Естественно, если вы, опять же, не забыли про декларированную мною «единственную сложность»
Рекурсивные определения не ко мне.
Но ок, вот вам программа без названия
Достать руку из кармана
Положить ее на клавиатуру
Включить мозг и перестать троллить, и так слишком много в мире нерешенных интересных задач
Содержащая десяток именованных объектов. Поэтому, внезапно, задача именования все еще актуальна.
То, что вам, как программисту ставят задачи не программирования, опять же жаль, но никак не доказывает ваш тезис.
Никто не ставил мне эту задачи, они возникли как частная проблема внутри конкретной бизнес-задачи (точнее, как частное решение задачи оптимизации производительности, возникшей, в свою очередь, как выполнение "нефункционального" требования к бизнес-сценарию).
arvitaly
05.08.2016 15:18-8> В таком случае вся индустрия внезапно занимается не тем. Угу.
Не знаю чем занимается ваша выдуманная индустрия, моя занимается проблемой повторного использования.
> Рекурсивные определения не ко мне.
Я заметил, что у вас сложности с ведением диалога.
> Содержащая десяток именованных объектов. Поэтому, внезапно, задача именования все еще актуальна.
Именование этих объектов не относится к программированию никаким боком, если вы не заметили для написания этой безымянной программы (кстати, поздравляю, вы ее увидели первый раз в жизни), мне не пришлось выдумывать ни одного нового наименования.
> Никто не ставил мне эту задачи, они возникли как частная проблема внутри конкретной бизнес-задачи
Ура, теперь и вы поняли, что задача инвалидации кэша является задачей бизнеса, а не программирования, и ключевое решение «какой вариант выбрать» принимается на уровне бизнес-планирования. А вот уже обычную задачу «инвалидация кэша при вот таком-то выбранном бизнес-решении» можно решить и программированием.lair
05.08.2016 15:20+5Не знаю чем занимается ваша выдуманная индустрия, моя занимается проблемой повторного использования.
Окей, у нас с вами разные индустрии. В принципе, на этом можно и закончить, сложности вашей индустрии меня не интересуют.
PS
задача инвалидации кэша является задачей бизнеса, а не программирования, и ключевое решение «какой вариант выбрать» принимается на уровне бизнес-планирования.
А вот и нет. Бизнесу на это положить, у бизнеса есть ровно одно требование: 30+ транзакций такого типа в секунду (при такой-то загрузке).
Впрочем, повторюсь, представления вашей индустрии о программировании меня не интересуют.
arvitaly
05.08.2016 15:23-10> 30+ транзакций такого типа в секунду (при такой-то загрузке).
Это и является тем самым бизнес-решением, что разделяет «задачу инвалидации кэша» от «задачи инвалидации кэша при таких-то условиях» и делает ее элементарной. А вот выбрать сколько должно быть этих самых транзакций в секунду — вот это действительно сложно, но к программированию отношения не имеет.
VolCh
05.08.2016 21:26+2Это является постановкой задачи программистам на реализацию бизнес-требования. Бизнес и не должен знать не то что про инвалидацию, а даже про кеши вообще.
lair
05.08.2016 21:48Что-то мне кажется, что вы обоими своими комментариями промахнулись с адресатом. Но я с вами согласен, если что.
arvitaly
06.08.2016 04:24-4Абсолютно не важно, на каком уровне будет принято то самое ключевое решение превращающее абстрактную задачу «инвалидация кэша» в конкретную «инвалидация кэша при условии, что босс не хочет, чтобы сайт тормозил». Повторюсь, это не задача программирования. Если босс не знает, что такое миллисекунды, значит есть кто-то, кто переведет эти условия для программистов, IT-директор, например. В таком случае можно считать это задачей IT отрасли, включающей в себя, например, UX-специалистов. А вот совместными усилиями эта задача наконец дойдет до задачи программирования — описать уже готовый алгоритм, понятным компьютеру.
А то, что некоторые люди (ох, как оказалось их много), занимаясь не только программированием, решили, что теперь все есть оно, наверное, это ЧСВ, ну, или банально ошибка. Хотя, скорее, попытка выжить, ведь уже в огромном спектре задач не нужны переводчики в виде программистов, компьютер и так понимает смысл жестов, естественных языков, звуков и т.д.michael_vostrikov
06.08.2016 06:55+1Эм. То есть вы предлагаете, чтобы всякие индексы БД, алгоритмы кэширования, очереди и грин-треды изучали UX-дизайнеры и директора, а потом выдавали вам на блюдечке готовую инструкцию с описанием архитектуры программы и ссылками на описание конкретного алгоритма?
arvitaly
06.08.2016 07:00-5Я предлагаю называть вещи своими именами и не преувеличивать. И нет, не все задачи в IT-отрасли относятся к программированию! Но и к UX-дизайнерству или управлению тоже не относятся. Если вы не знаете как называется конкретная отрасль — не повод называть ее программированием, правда? И так странно слышать «тыжпрограммист» на ресурсе для программистов, похоже давно нужно было поднять эту тему, вон как все запустилось.
areht
06.08.2016 12:05+1Так и как же называется человек, который формулирует когда кеш инвалидировать?
arvitaly
06.08.2016 12:21-3Человек может называться как угодно, главное, что к программированию это отношения не имеет.
А обычно, это человек принимающий бизнес-решения на уровне IT: менеджер проекта, IT-директор и т.д. В маленьких компаниях, очень часто, человек является и менеджером проекта и программистом, отсюда и непонимание. Но ведь если он поможет один раз собрать новые стулья, к примеру, или в компании принято каждое утро убираться на территории всем, вряд ли эти занятия станут программированием, правда?areht
06.08.2016 12:55+2> менеджер проекта
Этот человек должен диаграммы Ганта инвалидировать, когда уволит программистов, не инвалидировавших кэш.
> IT-директор
А этот, на втором собеседовании, спрашивать «к кому вы пойдёте, если надо инвалидировать кэш?».
Но придумыватькак инвалидировать кэш эти двое точно не должны.
doniys_a
06.08.2016 14:59Да нет, судя по всему, это задача системных администраторов. С чего бы программисту нагружать свой мозг такой «тривиальной» задачей, как кеширование? Это наверное только админам знать и нужно. А программисту (опять же, судя по утверждениям) это абсолютно не нужно. Не нужно знать о проблеме парралельной записи и методов предотвращения испорченных данных, не нужно знать о нетсплитах — это тоже задача админа следить за сервером (хотя, пожалуй, все же программист отвечает за целосность и контроль отдаваемых данных, а не админ — но это мое субъективное мнение). И много еще «не очень интересных» для программиста проблем с кешированием и инвалидацией кеша (которая, как минимум, обеспечивает актуальность данных) связаны с кешированием.
Но тогда вопрос, если кеширование — не задача программиста, то естественно, из утверждений выходит, что ему и знать это не нужно. Так выходит что ли?
А тогда встречный вопрос, если этого не нужно знать и можно даже представления не иметь о принципах, то каким образом это будет поддерживать и кто за это будет отвечать?
И вот еще, главный вопрос, каким образом в таком формате Вы собираетесь строить кеши, которые не разезжаются с базой?
По поводу «линейности» каждой программы. Это Ваше личное мнение? Линейность предполагает выполнение одной команды за другой и этот порядок ничем и никогда не нарушается (1-2 курс университета).
А как быть с определением «нелинейное программирование»? Или, по Вашему, эти программы все равно линейны?
А то, что некоторые люди (ох, как оказалось их много), занимаясь не только программированием, решили, что теперь все есть оно, наверное, это ЧСВ, ну, или банально ошибка
Наверное за столько лет расширился спект задач, существенно прибавилось технологий, существенно выросли запросы, существенно ИТ разрослось, и не только сообщество. Любое развитие предполагает развитие и если, как программист, не понимаешь, как работает на серверах то, что использует программный продукт и с чем взаимодействует, как взаимодействует устройство с кодом, каким образом собираетесь его оптимизировать? Каким образом собираетесь укладываться в наложенные ограничения? Или Вы полагаете, что каждый заказчик желать масштабироваться вертикально? Да нет, чаще экономят.
VolCh
05.08.2016 21:21+2Кэши (в ОЗУ, файлах, базах) управляются программно и реализовать управление, включая инвалидацию, задача именно программирования.
Idot
06.08.2016 15:27+3Все программы линейны.
Извините за личный вопрос, Вы случайно не закончили краткие курсы, вместо учёбы в ВУЗе?
Уж очень в Вашем сообщении сквозит невежество и отсутствие какого-либо знакомства с теорией.
encyclopedist
05.08.2016 20:12+3А нет никакой сложности в написании линейной программы
Про "проблему остановки" вы похоже не слышали.
hlogeon
05.08.2016 21:26+4Да что Вы, у человека вообще сложностей в программировании нет совершенно никаких. Вся индустрия баклуши бьет.
arvitaly
06.08.2016 02:12-3Это не проблема описания алгоритма, это проблема самого алгоритма. Программирование занимается переводом одних языков на компьютерные. И все. Программирование — функция, на вход которой поступает готовый алгоритм, а задача описать его компьютеру. И вся сложность заключается в том, чтобы не описывать одни и те же алгоритмы многократно. Соответственно, нас интересуют только те теории, которые позволяют сократить код (например, теория категорий). Однако, хоть эта задача и относится к единственной сложности в программировании, даже ее решают математики, а программисты лишь используют частично и подгоняют под себя.
Все по одной простой причине, мы всегда пишем программу для конкретного исполнителя (для которого уже задан набор команд), будь то процессор или виртуальная машина.
А когда пишут псевдокод, просто в голове подразумевают ряд существующих языков (написанных для конкретного процессора) и пишут нечто, для чего можно в любой момент написать компилятор.0xd34df00d
06.08.2016 04:38+1Я с теорией категорий немножко знаком, и мне непонятно, как она помогает писать более короткий код.
Более того, мне сходу неочевидно, как она помогает выбирать удачные абстракции вроде каких аппликативных функторов или моноидов в категории эндофункторов.arvitaly
06.08.2016 05:24-1Нет, теория категорий думать и выбирать абстракции не помогает, это правда) Зато дает возможность создавать абстракции более «навсегда», чем ООП, например, что в свою очередь, для описания каждой последующей задачи, сокращает код. Т.е. нужно читать мое предложение про сокращение кода в контексте множества задач, а не вот этой конкретной.
Именно поэтому часто бывает так, что есть основная платформа, написанная на «сложных» языках, где уже определены обобщения на большинство случаев, а над ней есть какой-нибудь скриптовый язык с простейшими возможностями: так было с javascript, так есть с 1С, Siebel, так часто используется Lua и т.д. Потому что до выкристализации обобщения нужно доказать его ценность для последующих задач. В том числе и опытным путем.
hlogeon
06.08.2016 05:18+6И тогда выходит, что те 6 статей, что вы тут писали — пустой звук. Все и так уже поняли, что вы зеленый и собеседников слушать не хотите, в двух соседних комментариях противоречите сами себе. Так активно рассуждаете про теории, а которых, судя по набору технологий о которых вы пишите(а значит в которых разбираетесь), вы понятия не имеете.
Даже спорить не хочется с очередным JavaScript- & PHP-функциональным программистом, с одной стороны находящим проблемы даже в шаблонизации HTML и тут же говорящим об отсутсвии оных в программировании вообще.
В проектах, где количество разработчиков больше одного, задача разделения кода и отображения — одна из первоочередных
Почему нельзя написать идеальную программу
Можно, ведь она основана на логике — абстрактном математическом понятии. При этом мы изначально берем за аксиому верность каких то утверждений:
Если А верно, то и Б верно, но то, что А верно — мы решили сами.
Но программы работают на конкретном оборудовании — станки, ЭВМ, самолеты. А они собраны из неидеальных деталей, а значит неидеальным способом. А значит — они сломаются даже до физического износа. В общем, об этом и так сказано достаточно много.
Поэтому наша программа из одной строчки гарантированно будет работать с ошибками.
По долгу службы приходится иметь очень много дел с JSON, и здесь система типов TypeScript не помогает ничем, даже мешает, ведь компилятор сообщает об отсутствии ошибок, JSON.parse возвращает тип Any. Кроме того, TypeScript не поддерживает рефлексию, ввиду специфики работы, а значит, нет возможности проверить тип, основываясь на уже существующем коде. Также, до последнего времени, средств для мета-программирования не было вовсе.
vintage
06.08.2016 10:24На самом деле TypeScript уже поддерживает рефлексию. Правда пока в весьма куцем виде.
А результат работы JSON.parse вы всегда можете скастовать к более конкретному типу после (или во время, благодаря type guards) валидации.
samaranin
07.12.2016 14:10Большое спасибо!
arvitaly
07.08.2016 07:49-1Спасибо. Я уже понял, что на хабре не знакомы другие понятия, кроме «программирование», которое означает все на свете, даже если вы же используете другие слова. Двоемыслие тоже давно описано, только жаль видеть его среди IT-среды.
Ckpyt
07.08.2016 07:56А не надо путать программиста — человека, дающего код в ответ на требование «дай мне кнопку сделать все» и кодера — человека, реализующего написанный алгоритм :-)
arvitaly
07.08.2016 08:53-3Программист — это человек, который занимается программированием, все остальное — демагогия. Программирование — это процесс создания программ из формальных требований для конкретного выходного интерфейса.
Формированием формальных требований и выбором конечного устройства не занимается программирование, а следовательно и программист, хотя один человек способен заниматься несколькими профессиями (например, быть и программистом, и менеджером части бизнеса и принимать такие решения).
Кодер — жаргонное понятие, обозначающее программиста, не способного создавать собственные абстракции для повторного использования.
Неспособность некоторых «заказчиков» создавать формальные требования, не делает программирование чем-то другим, зато добавляет необходимость заниматься их разработкой ДО программирования и возможно теми же людьми, что и будут программировать.
Согласен, все это путать не надо, да и в википедии (по вашей же ссылке) примерно это и описано https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%81%D1%82#.D0.A1.D0.BB.D0.BE.D0.B2.D0.BE.D1.83.D0.BF.D0.BE.D1.82.D1.80.D0.B5.D0.B1.D0.BB.D0.B5.D0.BD.D0.B8.D0.B5. А на требование «дай мне кнопку сделать все» дают код в ответ не программисты, а несуществующие персонажи из сказки «Программист — бог».
symbix
05.08.2016 15:18Если изменился контракт — очевидно, что требует изменений и код, полагающийся на него. Это справедливо для любой парадигмы.
В Go, кстати, очень элегантная реализация ООП.arvitaly
05.08.2016 15:33>Если изменился контракт — очевидно, что требует изменений и код, полагающийся на него. Это справедливо для любой парадигмы.
Речь о величине куска с контрактом. Чем он меньше, тем меньше изменений в использующем его коде.areht
05.08.2016 19:10+2> Речь о величине куска с контрактом. Чем он меньше, тем меньше изменений в использующем его коде.
И при чём тут ООП?
svekl
05.08.2016 21:59+1Величина контракта не зависит от парадигмы, большие контракты — это проблема исключительно архитектуры
symbix
06.08.2016 01:09Поэтому лучше, когда контракты явно объявлены и придерживаются SRP.
Ровно то же справедливо для любой парадигмы, опять же. Названия меняются, суть остается.arvitaly
06.08.2016 03:20-4Нет никаких проблем в написание однократно используемого кода. Проблемы начинаются, когда для разных модулей/проектов/систем разные представления о SRP для данного объекта и конкретного метода.
В ООП жестко задан тип связи между методами (поведение объекта), в ФП — и эта связь является описываемой функцией. Так вот в одном модуле этот класс объекта полностью поддерживает SRP, а в другом нужна только его половина.
Мало того, каждый модуль можно рассматривать во временном промежутке. Сегодня SRP для него таков, а завтра может быть что-то изменится (тут можно спорить о качестве композиции).
И вновь напомню, что люди не одинаковые. Для одних будет SRP заключаться в этом, для других в другом.
Так вот, ФП позволяет сделать систему гибче, за счет настраиваемости связей, жестко зашитых в ООП (да, да, именно публичные, приватные, защищенные свойства, принципы наследования и т.д.). Но за эту гибкость приходится платить сложностью — мы знаем гораздо меньше заранее об одном и том же объеме кода (чем в ООП). Немногие способны компилировать в голове такой код, в отличии от, например, линейного скрипта. Поэтому детям в школе дают QBasic. Не потому, что на нем нельзя чего-то написать, а потому что на единицу объема доступный для разбора мозгом код. А кто-то способен ночью, проснувшись, прочитать чужой haskell и мгновенно понять.
Так что, выбор парадигмы напрямую зависит от ваших возможностей и потребностей, но именно в связи с совокупными возможностями ваших программистов.
Кстати, советую всем в возрасте от 35 лет (усредненно) принять свой уровень, как данность и не мучаться угрызениями совести от незнания сложных языков.symbix
06.08.2016 07:03+21) в ООП, как и в ФП, и даже как и в процедурке, связей ровно столько, сколько вы напрограммируете.
2) либо вы не понимаете, что такое SRP, либо я не распарсил
3) если у вас с возрастом проблемы, я сочувствую, но не стоит говорить за всех. Робу Пайку скоро 60, а Кену Томпсону вообще уже 73, однако модный у молодежи Go они как-то сделали и продолжают над ним работать.arvitaly
06.08.2016 08:01-4> 1) в ООП, как и в ФП, и даже как и в процедурке, связей ровно столько, сколько вы напрограммируете.
В ООП уже зашиты некоторые из них (я описал выше какие), поэтому нет, там их меньше, в ФП приходится писать их вручную.
> 2) либо вы не понимаете, что такое SRP, либо я не распарсил
А фишка как раз в том и заключается, что нет формулы определения SRP, понятие завязано на семантике, которая не определена формально даже в естественных языках (а именно с помощью слов из естественных языков и даются названия обобщениям). Например, пресловутый стул, как оказалось может быть и сидением и предметов торговли, иметь и три ножки и вообще не иметь ножек и т.д. Создайте класс стул с его SRP и получите непонимание уже даже вашего соседа) Что уж говорить про интернациональные пакеты.
> 3) если у вас с возрастом проблемы, я сочувствую, но не стоит говорить за всех. Робу Пайку скоро 60, а Кену Томпсону вообще уже 73, однако модный у молодежи Go они как-то сделали и продолжают над ним работать.
Не понял, где тут логическая связь вообще. Я говорю всего лишь о том, что после определенного возраста обучение сложным вещам дается намного тяжелее. А то, что Пайк обладает уровнем для создания простого языка, не говорит о его возможности к созданию (или даже изучению) гораздо сложного, правда? В любом случае, ремарку про усреднение я оставил неспроста. Для кого-то это может быть и 60, но Пайк не тот пример.grossws
06.08.2016 17:34+1семантике, которая не определена формально даже в естественных языках
Спасибо, долго ржал. Формальная семантика часто нормально определяется для искусственных языков, но для естественных всё совсем плохо.
symbix
06.08.2016 23:13+1> обладает уровнем для создания простого языка
Вообще, сделать простой язык — намного сложнее, чем сложный.
Остальное оставлю без комментариев, у вас просто каша в голове.
wholeman
08.08.2016 12:55Кстати, советую всем в возрасте от 35 лет (усредненно) принять свой уровень, как данность и не мучаться угрызениями совести от незнания сложных языков.
Утверждение, типичное для возраста до 25 лет или ранее.
Проблема не столько в возрасте, сколько в опыте. Освоив, например, ООП и простой язык C++, для смены их на что-то другое уже нужны достаточно веские причины, поскольку новый язык придётся осваивать заново. Даже если он отличается несильно, тонкие ощущения будут другими, т.к. некоторые конструкции, которыми привык оперировать, могут отсутствовать, а для их замены есть что-то другое.
Также приходит понимание, что языков много, и изучить каждый вновь придуманный, потому что кто-то считает, что он — лучше всех, просто не хватит времени.0xd34df00d
08.08.2016 20:16Так не в этом ли и дело, что в более юном возрасте всякие разные новые языки учатся с большим удовольствием и большим стремлением, что ли?
taujavarob
08.08.2016 20:36Так не в этом ли и дело, что в более юном возрасте всякие разные новые языки учатся с большим удовольствием и большим стремлением, что ли?
Полно людей, севших 30 лет назад на C и так и не слезших с него.vlad72
08.08.2016 20:58В молодости интересно все новое, но потом становится достаточно эффективности.
0xd34df00d
08.08.2016 21:05Ну вот грустно, когда интерес пропадает, и становится достаточно чего-то там.
wholeman
08.08.2016 22:46Интерес не пропадает — он меняет направление и становится более осмысленным. В юности это (было) больше похоже на метания.
taujavarob
09.08.2016 17:32Интерес не пропадает — он меняет направление и становится более осмысленным.
Он просто меняет направление. — Знаю программиста на SQL — с возрастом он стал начальником отдела и начал… писать стихи. — При встрече просил никогда не говорить с ним о базах данных и SQL.
Idot
09.08.2016 03:47А есть ещё давно севшие на COBOL, и недавно была про это статья. Если за язык хорошо платят, а работа нравится и приносит удовольствие, то нет нужды слезать с языка.
taujavarob
09.08.2016 17:32Если за язык хорошо платят, а работа нравится и приносит удовольствие, то нет нужды слезать с языка.
Тут много если. Имхо
Idot
09.08.2016 10:58Ну, в юном возрасте, я изучал на бумаге и CLU и Modula, и кучу других экзотических языков, и даже пытался учить PL/1, но отсутствие доступных компиляторов остановило меня.
taujavarob
09.08.2016 17:34Ну, в юном возрасте, я изучал на бумаге и CLU и Modula, и кучу других экзотических языков, и даже пытался учить PL/1, но отсутствие доступных компиляторов остановило меня.
Потом вы выучили Adabas, Foxpro, Clipper и Дельфи, завершив всё SQL и Java. Наверное.
VolCh
05.08.2016 21:27ООП не ограничивает контракт ни сверху, ни снизу. Ограничивать могут некоторые реализации.
MisterN
05.08.2016 20:54Господи, вы хоть поняли, что поставили выбор парадигмы программирования в зависимость от «умственных способностей программиста»? Если в ооп меняется один метод или публичное свойство, то все зависит от того, насколько меняется и как оно влияет на другие. Так же, как и в функциональном проганьи. Зачем нужно делать функцию? Чтобы её потом везде использовать. А если мы много используем функцию и вдруг её радикально изменили, обновив в том числе и параметры? Ну а чем в этом смысле отличаются методы объекта?
pda0
05.08.2016 17:00+3Касается. Так же, как и процедурного программирования. Собственно, «повторное использование» это не только про создание библиотек кода для другой программы. Это в первую очередь снижение дублирования кода внутри самой программы. Код разбивается на функции не только для улучшения читаемости, но и чтобы один и тот же код можно было повторно использовать из разных мест одной и той же программы. Это сокращает объём кода, позволяет сократить количество копапаст-ошибок и ошибок типа «там исправил, а тут забыл».
Но раз автор этого не знает, то он далёк от понимания программирования вообще, так что всерьёз воспринимать его критику…
0xd34df00d
06.08.2016 04:31В упомянутом примере с добавлением элементов в неявный публичный контракт включено гораздо больше соглашений, чем в типичном чистом ФП. Ну, я не могу сходу придумать аналогичную проблему на хаскеле, например.
ilyaplot
05.08.2016 13:43+9Мне обещали, что я больше никогда не стукну молотком по пальцу. Мне больше не нужны будут гвозди, есть же множество саморезов различной величины и формы! Я смогу просто взять шуруповерт и крепко прикрутить доску к другой доске!
Но когда я пытаюсь вкрутить саморез в палец, он действительно вкручивается. Когда я бью шуруповертом по гвоздю, он забивается медленнее, а шуруповерт ломается.
Прощай, шурик! Теперь я буду пользоваться молотком. (Потом камнем)
Misiam
05.08.2016 13:55+12Каждый раз вспоминается шутка с одного it форума (ссылка на оригинал затерялась):
«Почему они выбирают между ООП и процедурами? Надо выбирать между ООП и HTTP.» © Popoff
forketyfork
05.08.2016 13:59+22Эта треш-статья не стоила перевода.
Особенно забавный пример с «хрупким классом». Автор добавляет в подклассе состояние (поле count), которое по его задумке должно быть консистентно с внутренним состоянием суперкласса (число элементов в нижележащем ArrayList), и при этом почему-то полагает, что ему не нужно вникать в детали устройства суперкласса.areht
05.08.2016 19:25+1Ну, это не проблема с состоянием, или ООП. Это проблема языка.
Если в C# ты пометил add() в базовам классе как virtual, но начинаешь его вызывать(т.е. меняется семантика) — это неявный breaking change, и свинство.
Если virtual add(int n){internalAdd(n);} — и проблемы нет.
в Java как-то не очевиднее.0xd34df00d
06.08.2016 04:45+1Потому что там на самом деле три метода: публичные add и addMuti и приватный/протектед addImpl, при этом первые два никогда не вызывают друг друга, а делегируют непосредственную вставку третьему.
Хочешь перехватывать общение с пользователем класса — переопределяешь первые два. Хочешь перехватывать реализацию — переопределяешь третий.
Non-virtual interface из плюсов тут, кстати, тоже где-то рядом.
TyVik
05.08.2016 14:00+14Как же надоели такие статьи! Человек работал 10 лет с ООП, но так и не научился его готовить? И вместо того чтобы понять когда что использовать он предлагает всё переписать на ФП. Набьёт шишек там и напишет точно такую же обличительную статью про ФП. Прям круговорот какой-то…
UA3MQJ
05.08.2016 14:29+9При всей моей любви к ФП в лице Erlang, но я такое уже видел.
Сначала:
Написал свой первый простенький скрипт на Erlang
И потом:
Впечатления от Erlang после года работы с ним
Никогда-никогда не используйте в своих проектах Си!
Почему Erlang — язык для очень специфичных задач
Так что без работы не останемся :)
Landgraph
05.08.2016 14:00+3Только недавно перечитывал Страуструпа и там один из вариантов решения «проблемы ромба» на С++:
class Copier: public Scanner, public Printer { public: using Printer::start; }
Сама статья больше похожа на проявление архитектурной проблемы. Неоправданное применение ООП там, где можно было обойтись ФП не означает того, что ООП — зло. И то, и другое — лишь инструменты. Молотком можно и дом построить, и голову проломить — от этого молоток хуже не становится.develop7
05.08.2016 14:22решать проблему ромба множественным наследованием — это, пожалуй, качественно новый уровень зла
Landgraph
05.08.2016 14:37+3Вы это о чём? Я просто проиллюстрировал мысль о том, что проблема надумана и является чисто архитектурной.
dolk13
05.08.2016 20:55+3Прошу прощения, но, вроде бы, проблема ромба и возникает только при множественном наследовании? Если я ошибаюсь, то хотелось бы увидеть пример ромбовидного наследования без множественного.
BlessMaster
06.08.2016 00:01+2В данном случае пример — это сама задача. Копир должен наследовать и принтер, и сканер. А это чуточку разные вещи, выполняющие разные задачи. «Старт» принтера и «старт» сканера — это не одно и то же, нельзя выбрать что-то одно из этого, нельзя «стартануть» их просто «по-очереди». Нужно, чтобы отработал сканер, забрать его результат и отдать обработать принтеру. Что в принципе независимый третий метод «старт».
То есть архитектура уже должна приводить к компоновке, а не наследованию и разведению «состояний» «в разные углы». Копир состоит из принтера и сканера, связывая их дополнительными функциями, но не наследует их свойства, как может показаться на первый взгляд.
«Проблема ромба» может возникать и без множественного наследования — это проблема некоторого общего ресурса, имеющего состояние, и нескольких владельцев, желающих его использовать. Открытый файл, общая память, синхронный протокол связи, сеанс на веб-сервере. То есть, столкнуться с нею в ФП-языке так же возможно, как и в ООП-языке, не реализующем множественное наследование. Множественное наследование — лишь частный случай.vintage
06.08.2016 00:19+1Наследование — это ничто иное как агрегация, приправленная делегированием. И копир, вполне может быть наследником их обоих.
class Copier : Printer , Scanner { /// запускает основную функцию девайса (большая зелёная кнопка) start(){ return super->Printer::start( super->Scanner::start() ) } /// конкретно у копира есть дополнительная кнопка запуска отдельно принтера print() { return super->Printer::start() } /// конкретно у копира есть дополнительная кнопка запуска отдельно сканнера scan() { return super->Scanner::start() } }
symbix
06.08.2016 00:28+2Для начала нужны PrinterInterface и ScannerInterface.
Тогда все становится понятно: Copier implements PrinterInterface, ScannerInterface.
Если в обоих интерфейсах методы называются start, а не scan и print, то будет плохо, но это надо было сразу думать над неймингом.
Если же методы называются print() и scan(), как у нормальных людей, то и проблем нет.
А уж наследоваться или нет — вопрос реализации. Я предпочитаю наследоваться только от абстрактных классов, а во всех остальных случаях делегировать — так явнее и безопаснее.vintage
06.08.2016 00:49+1Для начала нужны PrinterInterface и ScannerInterface.
Не нужны. Нужна возможность наследовать интерфейсы без реализации:
class Printer { ... } class Scanner { ... } class Copier implements Printer, Scanner { ... }
Если же методы называются print() и scan(), как у нормальных людей, то и проблем нет.
Если знать, где упадёшь, то можно и соломки подстелить: scanWithLaser(), printToBottomTray().
во всех остальных случаях делегировать — так явнее и безопаснее.
И копипастить проксирование каждого из 100500 методов? А толку? Указывая "extends" вы фактически явно указываете проксировать всё, что не перегружено, до предка.
symbix
06.08.2016 01:20Интерфейсы нужны, потому что ISP.
scanWithLaser — как раз-таки нет, это уже детали реализации. Если кто-то наплодил интерфейсов с методами performAction или go, это уж ССЗБ. Имя метода должен отражать суть действия.
По поводу наследования все написано классиками в GoF — Favor 'object composition' over 'class inheritance'. Не вижу смысла пересказывать своими словами, все равно лучше не получится. Что касается «100500» — а зачем, а откуда? Всего два метода, это не больно. А если и правда 100500 — так боль уже в том, что их 100500, где-то забыли про SRP, типичный God Object. (И, кстати, копипастить надо не во всех языках).vintage
06.08.2016 01:32Интерфейсы нужны, потому что ISP.
Объявление класса уже является объявлением интерфейса. В большинстве случаев нет логической необходимости его дублировать отдельно.
scanWithLaser — как раз-таки нет, это уже детали реализации.
Это конкретизация интерфейса. Такая же, как и print|scan или startPrinting|startScanning вместо start.
По поводу наследования все написано классиками в GoF — Favor 'object composition' over 'class inheritance'.
Ни чем не обоснованный догматизм.
Всего два метода, это не больно.
Всего два метода демонстрируют проблему ромба, а всего методов, разумеется куда больше. И, самое главное, нам, при реализации копира, совершенно не важно сколько и каких методов реализуют принтеры и сканнеры, пока их их интерфейсы не пересекаются. А вот места пересечения требуют от нас уточнения, что именно требуется.
А если и правда 100500 — так боль уже в том, что их 100500, где-то забыли про SRP, типичный God Object.
Давно вы практикуете гадание по числу методов? :-)
symbix
06.08.2016 02:09Это не догматизм, это практика. Если мне на слово не верите, почитайте литературу. У того же Фаулера примеры из его практики.
Все, что мы знаем — что Copier должен уметь scan() и print(). Конкретные реализации Scanner и Printer могут не подходить. Может, у них у каждого независимая очередь, а нам тут нужна одна. Может, еще что. А может, сегодня подходят, а завтра нет. С делегированием такие проблемы решаются проще и безболезненнее.
По числу методов гадать нечего, это такая же объективная метрика, как и цикломатическая сложность.vintage
06.08.2016 09:56Это не догматизм, это практика. У того же Фаулера примеры из его практики.
В ваших устах это именно догматизм. А у того же Фаулера используется довольно нейтральное "предпочитайте". Я бы сформулировал проще: "не используйте наследование, если вам не нужно наследование — для повторного использования кода есть и другие техники".
Конкретные реализации Scanner и Printer могут не подходить.
Это не конкретные реализации, а базовые классы, реализующие общую для всех принтеров/сканеров функциональность.
Может, у них у каждого независимая очередь, а нам тут нужна одна.
Скорее всего там будет именно одна, унаследованная от общего предка, очередь. Но если окажется, что там две независимые, то их объединение реализуется в точности так же как и в случае с композицией. Как я уже говорил, наследование — это частный случай композиции и довольно глупо их пытаться противопоставлять.
По числу методов гадать нечего, это такая же объективная метрика, как и цикломатическая сложность.
Только имеет ещё меньше смысла. Вот у меня есть приложение ToDoMVC, полный интерфейс которого насчитывает 70 методов. Это много или мало? Если не использовать автоматическую делегацию агрегату (наследование), то конечно много — это ж чтобы добавить кнопочку в футер, нужно будет вручную повторить весь этот интерфейс.
symbix
06.08.2016 23:20Я тоже именно предпочитаю. Но предпочитаю почти всегда, если сомневаюсь — делаю композицию, и еще никогда не пожалел. Вот о том, что сделал наследование там где не стоило бы — жалеть приходилось.
В рассматриваемом случае наследование плохо подходит, потому что Copier — это совершенно необязательно Scanner + Printer, он может быть устроен совершенно иначе. Завязаться на реализацию можно в качестве «быстрого хака». И потом огрести. :)
Разумеется, есть случаи, когда наследование абсолютно уместно. Например, что-то вроде Postgresql94QueryBuilder extends Postgresql90QueryBuilder extends PostgresqlGenericQueryBuilder extends AnsiSqlQueryBuilder.vintage
07.08.2016 01:19Если "он устроен как-то иначе", то наследование тут мимо кассы и обсуждать, собственно, нечего.
symbix
07.08.2016 04:37Так смысл ООП во многом как раз в том, чтобы убрать зависимость от реализации для внешних пользователей интерфейсов.
Пусть наследуется, пусть что угодно — снаружи знать про это не надо, снаружи интерфейс.vintage
07.08.2016 11:35Интерфейс МФУ = Принтер+Сканнер+Копир. Этот интерфейс предполагает, независимую реализацию принтера и сканнера. Если вам нужен только копир, а принтер и сканнер не нужен, то довольно странно наследовать его от принтера и сканнера.
areht
06.08.2016 00:39> И копир, вполне может быть наследником их обоих.
«Не для науки ради, а так, чисто позырить...»
Вам тут наследование что дало?vintage
06.08.2016 00:53Реализацию обоих интерфейсов в одном объекте, очевидно.
areht
06.08.2016 01:00Очевидно, да. А дальше что?
Если у вас реализации интерфейсов не пересекаются, что дальше то? Кастим к одному из интерфейсов и получем обратно принтер, от которого наследовались?vintage
06.08.2016 01:06А что вы дальше ожидаете от МФУ?
areht
06.08.2016 01:13Если вы 2 интерфейса засунули в один — это как принтер к сканеру днищами склеить. Перевернул — получил сканер, а не принтер. Это не то, что я ожидаю от МФУ.
vintage
06.08.2016 01:37Взяли 2 интерфейса, добавили своих методов и получили третий интерфейс (один девайс, выполняющий 3 функции).
areht
06.08.2016 01:42> один девайс, выполняющий 3 функции
Т.е. профит в нарушении SRP?vintage
06.08.2016 02:05Профит в появлении третьей функции, как результат объединение первых двух. Аналогичный случай:
InputRange + OutputRange = Channel.areht
06.08.2016 02:13появлении третьей функции, как результат аггрегации первых двух — это замечательно. Но я спрашивал про наследование.
> Аналогичный случай: InputRange + OutputRange = Channel.
А это с наследованием, что бы Channel к RangeBase было интереснее кастить? )vintage
06.08.2016 10:07А SRP тут, кстати, не нарушается. Копир не содержит реализацию ни принтера, ни сканера — он их берёт у предка. Когда нужно будет изменить разрешение сканирования по умолчанию — будет меняться класс Scanner, а не Copier.
А зачем вам кастить Channel к BaseRange?
areht
06.08.2016 11:39То есть Copier можно отнаследовать ещё и от листа бумаги(холодильника и Array), и пусть печатает сам себя — это не нарушение SRP?
То есть профит от наследования — в возможности создать god object «не нарушая SRP»?vintage
06.08.2016 11:45Можно, если вам это почему-то нужно. Хотя, я затрудняюсь сказать зачем такое может понадобиться. Но нарушением SRP это опять же не будет.
Божественный объект — это объект, который содержит не связанную между собой реализацию, а не поддерживает несколько не связанных между собой интерфейсов.
areht
06.08.2016 11:54> Можно, если вам это почему-то нужно.
Наследовать не «нужно». Никогда.
Я вот от вас не могу добиться зачем вы отнаследовали 2 несвязанных класса.
> Божественный объект — это объект, который содержит не связанную между собой реализацию
И это именно то, что вы делаете.vintage
06.08.2016 12:16Постановка задачи такая — нужен МФУ.
В случае наследования, реализация находится в родителе. В этом, собственно, и суть наследования — хранение общего поведения в отдельном классе (классах).
areht
06.08.2016 12:48> Постановка задачи такая — нужен МФУ.
Мы ещё ваш код копира обсуждаем? Это не МФУ. На копире «scan» мне не нужен.
Если нам нужен МФУ — у вас будет, например, 3 свойства *Settings: от принтера, сканера и МФУ. Это монстр Франкенштейна, а не МФУ.
> В случае наследования, реализация находится в родителе.
Если вы про god object — не имеет значения. God object — это проблема не реализации, а использования. Для внешнего кода не важно как именно класс агрегирует функционал всего.
> В этом, собственно, и суть наследования — хранение общего поведения в отдельном классе (классах).
Да ну? Для «хранения общего поведения в отдельном классе» наследование не нужно от слова «совсем»vintage
06.08.2016 15:55Нет смысла делать чисто копир, если можно сделать мфу. Но если нужен только копир, то наследование тут не нужно. Как, впрочем, и композиция сканера и принтера в общем случае.
Корневой неймспейс — вполне себе божественный объект в вашей интерпретации.
А никто не говорит, что оно нужно. Оно удобно.
areht
06.08.2016 16:36+1> Нет смысла делать чисто копир, если можно сделать мфу.
Нет смысла не сделать god object, понимаю.
> Корневой неймспейс — вполне себе божественный объект в вашей интерпретации.
В моей интерпретации неймспейс — не объект. Вы ещё и объект от неймспейса не отделяете?
> А никто не говорит, что оно нужно. Оно удобно.
> Можно, если вам это почему-то нужно.
Ох…
Ладно, я понял, что не дождусь объяснения где же скрывается удобство от совокупления сканера с принтером методом ромба.vintage
11.08.2016 00:28У вас богообъектофобия? Вы видите их там, где их нет.
Не отделяю. Я ещё и классы от объектов не отделяю. Вообще пропащий человек.
А в чём удобство от повторения делегирования каждого метода?
areht
11.08.2016 00:58> А в чём удобство от повторения делегирования каждого метода?
— Доктор, когда я вот вот так вот делаю у меня болит..((
— А вы вот вот так вот не делайте
babylon
06.08.2016 19:00-1Суть наследования в расширении базового класса. Наследуем то, что есть и добавляем то, чего нет. Всё прекрасно делается без классов. Есть нода-контейнер. Есть ноды с контентом. В контейнере можем оверрайдить контенты хоть до опупения. Причём, заметьте никаких жестких связей. ООП построенное на классах в топку! Это по любасу приводит к росту статического ядра.
Idot
06.08.2016 21:50babylon может статью с подробными пояснениями про нода-контейнеры напишете? *стало любопытно*
babylon
07.08.2016 00:44-1Для статьи слишком мало материала. Нода-контейнер любая нода, лежащая в массиве неймспейсов to
Idot
07.08.2016 08:35Если начать с введения, что такое Нода итп, то на статью вполне потянет.
babylon
07.08.2016 16:48-1Idot Нода — наименьшая структурная единица. Родительские ноды лежат в массиве неймспейсов parent. Агрегирование (сборка) структуры происходит снизу вверх по индексам ключей. Работа сверху вниз от корня или от контекста, который заранее установлен. Контейнеры лежат в массиве неймспейсов from. Контенты — в to. Сигналы в массиве неймспейсов signals. Меньше параграфа.
wholeman
08.08.2016 11:45Вам тут наследование что дало?
Например, просто добавить копир на ресепшене в список принтеров компании, доступных отделу продаж для печати накладных.lair
08.08.2016 11:50… а для этого не надо наследования. Для этого надо выставить публичный интерфейс "принтер".
wholeman
08.08.2016 12:34У интерфейсов есть недостаток — их нужно реализовывать, хотя бы на уровне делегирования.
Но на ресепшене не просто копир — там есть ещё факс, поэтому придётся делать отдельный класс с реализацией всех интерфейсов, в том числе копира, т.к. копировать он тоже может.lair
08.08.2016 12:48У интерфейсов есть недостаток — их нужно реализовывать, хотя бы на уровне делегирования.
Это зависит исключительно от языка/фреймворка, которым вы пользуетесь. Но что самое важное, с точки зрения потребления этого класса (а вы написали именно о потреблении), эта проблема несущественна.
wholeman
08.08.2016 13:05То, что я не написал о реализации, не значит, что я о ней не думал. Слово «просто» из предыдуцего комментария относилось и к ней тоже.
Я понял интерфейс, как в Java. Там он не имеет никакой реализации по определению. Конечно, в других языках может быть возможность определить для объекта интерфейс с реализацией, способом отличным от наследования, но зачем?lair
08.08.2016 13:07Конечно, в других языках может быть возможность определить для объекта интерфейс с реализацией, способом отличным от наследования, но зачем?
Чтобы избавиться от проблемы множественного наследования (и получить легкую/дешевую композицию).
areht
08.08.2016 13:04О, а раскройте схему наследования факса. Ну, с учётом того, что там сканер протяжной, а принтер печатает на отрезных кусках рулона (т.е. реализация у них несколько иная, чем у МФУ).
wholeman
08.08.2016 13:14Факс в составе МФУ может сводиться к розетке RJ11 с интерфейсной платой. В рамках данной дискуссии это несущественно.
areht
08.08.2016 13:24Ну, наверное, может. Хотя, я всегда факс видел на столе, а МФУ — отдельная тумбочка. Вот мне и было интересно каким наследованием это должно делаться.
Хотя, без наследования — тоже вариант.
areht
08.08.2016 12:59Т.е. вместо
Add(Copier.GetPrinter())
можно написать
Add(Copier)
Ну, принимается. Хотя ценность так себе.lair
08.08.2016 13:09… для этого, впрочем, наследование не нужно, нужно
Add(IPrinter)
иCopier: IPrinter
(илиimplicit operator IPrinter(Copier copier)
)areht
08.08.2016 13:58Add(IPrinter) может от меня не зависеть, а implicit operator — это всё же костыль, которым лучше не злоупотреблять.
Я вообще не уверен, что хочу иметь на копире интерфейс принтера. I feel disturbance in Force.
Скорее MyUberDevice: IPrinter, ICopierlair
08.08.2016 14:06Ну так если
Add
неIPrinter
, то мы и так в печали уже. А если оно такиIPrinter
, то откуда мы это выставили — уже не важно, важно, что нас дальше не волнует, наследовались мы от какого-то принтера, или нет.
BlessMaster
06.08.2016 03:46Вопрос был в общем про ромб (diamond problem).
Компоновку — хорошо — можно предоставить и как наследование при соответствующих возможностях языка, однако как бы мы компоненты ни заворачивали — они должны быть изолированы друг от друга, иначе они друг друга «покрошат», поскольку у них есть похожие части, которые выглядят одинаково, но отвечают за разные состояния и переопределённые в наследнике эти части могут удивить родителя. В примере выше — кроме отдельной кнопки — есть ещё отдельные внутренности, например, сервопривод. Если же всё это изолировано и работает независимо — смысл склеивать всё в один класс, чтобы потом всё время писать «такая-то часть класса» — уже дискуссионен. Но «осуждать» не зная особенностей конкретного языка — воздержусь.
babylon
07.08.2016 18:02Конечно это агрегация. Проблема в том, что это ранняя агрегация. Ну или не проблема. Зависит от точки зрения :)
wholeman
08.08.2016 11:24В примере из статьи, скорее, просто проблема именования: название метода должно отражать процесс, то есть start — это подготовка устройства, которая вполне может происходить и параллельно для составных частей копира, а для печати/сканирования/копирования метод должен называться соответственно print/scan/copy, возможно с префиксом start_, если мы хотим не ждать окончания процесса, а получить отдельное сообщение о результате.
Копир должен именно наследовать принтер и сканер, поскольку может использоваться как эти отдельные устройства, а не только их комбинация.
f1inx
05.08.2016 16:09+1Сам по себе разумный подход к ООП не опасен. Опасность в инструментах, которые слишком многое скрывают от программиста, а их использование обычно означает религию определенного способа мышления и подхода к решению задач.
Лично у меня большие проекты, где основными элементами дизайна (архитектуры) являются сомны сложных объектов вызывающие методы друг друга, а не подсистемы с ограниченным функционалом и конкретным явным уровнем абстракции данных, связанные протоколами различных уровней вызывают головную боль и рвотные позывы.
Особенно когда видишь a++ на верхнем уровне и понимаешь, что сейчас придется пройти через сотню унаследованных классов и операторов, чтобы понять, что здесь происходит :(.vlad72
06.08.2016 13:55Проблема не в ООП как в таковом, а в том, что в ОО-ЯП внедрены все возможные инструменты, которые позволяют стрелять себе в ноги, только ради «полноты возможностей ООП». Не всегда всё, что красиво в теории, надо воплощать на практике. Нужен обрезанный ЯП (или его стиль), который не позволяет неокрепшим девелоперам лажу гнать, а не переход к другой парадигме, где будут присутствовать все те-же проблемы.
Idot
06.08.2016 14:19Что-то вроде Паскаля (или даже Дельфи), но для ООП?
vlad72
06.08.2016 15:05Да, ведь Java и возникла, чтобы сделать сложное (С++) простым. Но не было опыта, поэтому сделали по максимуму. Теперь надо ее минимизировать, чтобы «хоп-хоп и в продакшен» еще легче делать было. И это не только Java касается…
Конечно, сам подход, что программисту надо о чем-то думать заранее, уже несовершенен. Но даже в ФП он присутствует…
areht
05.08.2016 19:19+1Проблема ромба не в том, что бы выбрать какую функцию start дергать. Проблема в том, что копир по кнопке «старт» не копирует (Карл!).
oledje
05.08.2016 21:45+4Проблема в том что автор не понимает до конца что ему нужно. Копир должен скрнировать, а после печатать и выглядит этот так:
Class Copier { Scanner scanner Printer printer function start() { page = scanner.start(); printer.start(page); } }
Да и остальные примеры высосаны из пальца. Как уже неоднократно говорилось в коментариях к подобным статьям: плохая архитектура не проблема парадигмы.
BalinTomsk
05.08.2016 22:45А зачем дополнительное инстанциирование?
function start() { Scanner::start(); Printer::start(); }
oledje
05.08.2016 23:15За тем, что мы избавились от множественного наследования в пользу делегирования, как и завещала великая банда четырех. "Используйте делегирование вместо наследования". Наверно это можно повторять неустанно.
Множественное наследование это мощный инструмент, но, как и любой другой мощный инструмент, должен быть использован с умом и осторожностью. Автор об этом не знает.
Landgraph
06.08.2016 11:34Конкретно в тексте у автора проблема именно в том, чтобы выбрать какую из функций start дёрнуть.
Обратите внимание, что классы Scanner и Printer оба реализуют функцию start. Тогда какую функцию start унаследует класс Copier? Та, что реализована классом Scanner? Или классом Printer? Не могут же они обе унаследоваться.
Ни у принтера, ни у сканера нет функционала копира, поэтому наследованием проблему копирования не решить. Поэтому у автора и проблема с архитектурой, а не с ограничениями ООП.
Я бы поставленную задачу решал как-то так:
class Copier: public Scanner, public Printer { public: void start() { Printer::start(); Scanner::start(); } using Printer::print; using Scanner::scan; void copy() { Page *page = this->scan(); this->print(page); } }
Cheater
05.08.2016 14:05+8> Software Engineer and Architect, Teacher, Writer, Filmmaker, Photographer, Artist
Какой разносторонний товарищ. Мне кажется, ФП ему нужно чисто в коллекцию титулов…
NeoCode
05.08.2016 14:09+5А вообще интересно такие статьи читать.
Во-первых, хоть что-то про программирование
Во-вторых, когда показываются недостатки, или проблемы, возникающие при попытке использования тех или иных средств — сразу задумываешься, а почему так и можно ли улучшить что-то в самом ООП чтобы эти особенности обойти?
Saffron
05.08.2016 14:19+3Господи, какое убожество. Он изобрёл мультинаследование и говорит при том, что наследование — это плохо. Проблема ромба решается тривиально во многих языках. Где не решается — это вопрос реализации (языка), а не концепции ООП. Вообще концепция ООП не тождественна C++, есть например ещё smalltalk, где всё совсем по-другому.
Полиморфизм — не есть уникальное свойство ООП, каждая парадигма предлагает полиморфизм, просто реализации разнятся. ООП имеет естественный полимформизм, но может пользоваться и функциональным полиморфизмом, просто не бесплатно.
Tiendil
05.08.2016 14:23+12Читал в оригинале, увидел на хабре, думаю, кто ж решился этот трешак переводить… Смотрю: Mail.ru и даже не удивился — как всегда уверенно пробиваем очередное дно.
MadridianFox
05.08.2016 14:38+3Думаю много проблем в области ООП растут из того, что «умные делают а глупые учат». 99% всех объяснений ооп содержат в себе те самые 3 столпа, когда на самом деле их не три. Это лишь попытка систематизировать свойства ООП. И эта попытка стала очень популярной и отсюда куча однотипных и неверных примеров наследования. Да, вначале это кажется чем-то божественным, но потом, оказывается что надо отринуть привычное и делать композицию. Это кажется противоестественным потому что везде учат наследовать.
Antervis
05.08.2016 14:47все описанные проблемы решаются интерфейсами. Бтв, множественное наследование практически везде лучше/проще заменять на композицию
lega
05.08.2016 14:47+2Ты слышал про парня, который попрощался с OOП?
О нет. Ещё один? Что же он сказал?
Совсем недавно писали по этому поводу.
wholeman
05.08.2016 15:01+6Документы и компании для обрушения «столпа наследования» — классический приём демагогии: привёл два понятия, не связанных отношением «общее-частное» и на основе этого объявляем само отношение ненужным. Есть же случаи, когда это отношение работает, например, документы и отчёты, письма, заявления и т.д.
Я открыл статью, задавшись вопросом: «А что взамен?», но так и не понял, чем так замечательно ФП и почему автор выбрал именно его.
heleo
05.08.2016 15:04Человек решает проблему иерархии переводя её в плоскость группировки и классификации… ЧуднО однако
chibitko
05.08.2016 15:04+1Нужно сочетать OOP и COP, не хочешь вникать в устройство класса и наследоваться, считай его компонентом.
Иногда лень писать шаблонные методы, пользуюсь копипастом, но я с этим борюсь, в случае с ФП копипаста было бы намного больше.
hlogeon
05.08.2016 15:07-5Божечки-матрешечки! Сколько уже можно? Предположу, что автор статьи через N-лет напишет «Почему ФП мертво», так и поняв, что мертво не ФП или ООП, а он, как инженер. Проблемы начинаются с самого начала, потому что автор подумал, что ООП — это способ переноса объектов реального мира в код, что само по себе, на мой взгляд, не очень правильно. Начало понимания ООП, на мой взгляд, как раз начинается с понимания того, что этого делать чаще всего нельзя. Попытка оправдать свою кошмарную архитектуру недостатками парадигмы(которы, конечно, безусловно есть) обречена на провал. Хороший инженер отличается от плохого тем, что может используя доступные ему инструменты делать качественные продукты. Автор же похож на типичную ТП, купившую зеркалку и возмонившую себя крутым фотографом, которой не вдомек, что качество твоей работы от фотоаппарата зависит далеко не в первую очередь. Что бы было понятней — вот фото конца 19 века, когда фотографам современные технологии даже в самых смелых мечтах и приснится не могли.
nikabc555
05.08.2016 16:24+10Это не фото 19 века, а картина Arch of Constantine (Rome), нарисованная Бернардо Беллотто в 18 веке
hlogeon
05.08.2016 21:18-1Ахах! Глупо вышло, не знаток искусства и фотографии(я же все-таки программист, а не фотограф), но я думаю, вы и сами без труда сможете найти старые фотографии, или очень красивые фотки снятые на обычные мыльницы или телефоны. Спасибо, что сказали, теперь запомню надолго, что это именно Бернардо Беллотто.
Потерянное поколение XDShamov
06.08.2016 14:49+1Можно было выкрутиться, сказав, что это фотография картины. Прокси-объект… если говорить в терминах ООП.
semenyakinVS
05.08.2016 15:13+1«Демократия имеет много недостатков, но лучшего человечество не придумало» (с) У.Черчилль
А если по существу — всё уже сказано выше. Автор статьи строит кривую архитектуру, после чего начинает ругает ООП за то, что архитектура получилась кривой. Если б я со своим почти нулевым опытом ФП бросился писать статью о ФП — от ФП вообще камня на камне не осталось бы. Кстати, о самом ФП автор пишет только, что ФП рулит… И всё (впрочем, это тоже уже заметили комментирующие выше).
П.С.: Печально что такая серьёзная компания, как mail.ru допускает в своём блоге публикацию столь маргинальных статей (ну, или переводов статей — без разницы).AcidBat
08.08.2016 12:04+1Цитата Черчилля звучит немного по-другому:
«Демократия — наихудшая форма правления, если не считать всех остальных.»
potan
05.08.2016 15:18+1Основная проблема ООП — что оно кажется простым, а на самом деле очень сложное. (Функциональщина наоборот, кажется сложной, но на деле проста.)
Если бы программисты понимали принцип подстановки Лискофф и знали, что такое коммутативные диаграммы в теории категорий, то проблем бы не было.
И, кстати (в поддержку ФП), с иммутабельными объектами наследование работает лучше, с мудательными принцип подстановки соблюдать сложнее.
HaruAtari
05.08.2016 15:19+1Я конечно могу ошибаться, но разве инкапсуляция, полиморфизм и наследование имеют отношение к ООП? Да, это хорошие приемы, но это не обязательная часть ООП. Все эти принципы можно реализовать на не ОО языке. И ОО язык не обязательно будет обладать встроенными механизмами для их реализации.
ООП — это это не классы, это когда объекты обмениваются сообщениями.symbix
05.08.2016 15:23+1Я бы только добавил, что обмениваются сообщениями в соответствии с контрактами (это важно). А так целиком согласен.
chaetal
07.08.2016 21:26О каких контрактах речь? Почему это важно? Вы же не хотите сказать, что ООП не может быть без контрактов?
symbix
08.08.2016 01:19Потому что обмениваться произвольными сообщениями с кем попало довольно бессмысленно.
Saffron
08.08.2016 04:08+1Ну вот erlang OTP так делает и не особо страдает. Да и вообще больше половины языков не имеют статической типизации, и ничего — живы.
chaetal
08.08.2016 09:39…И Smalltalk так же делает. И даже имеет DoesNotUnderstand по этому поводу — мощнейшая вещь, показывающая что очень даже не бессмысленно.
symbix
08.08.2016 10:56+1А я не про типизацию. И erlang/smalltalk/ruby/etc — это не важно.
Бессмысленно просить кофеварку помыть посуду, а пылесос сварить кофе.
Контракт всегда есть. Он выражен в виде слова interface в коде, в юнит-тестах, в документации или в голове у разработчика — но он есть. Видишь суслика?chaetal
08.08.2016 11:31Я потому сразу и спросил: «о каких контрактах речь?» Контракты (метаинформация) есть — но не на уровне концепции ООП, а ниже. ООП — про объекты и сообщения. А как они договорятся между собой — разговор отдельный.
symbix
08.08.2016 11:37Не согласен с вами. ООП это не про язык программирования (объектно-ориентированную программу можно написать, например, на старом добром C), а про подход к проектированию программы. А когда мы думаем про объекты и сообщения, то есть проектируем программу в терминах объектов, мы не можем не думать о контрактах. Обязаны думать.
chaetal
08.08.2016 11:45Да не надо со мной соглашаться. Тем более, что я про язык ни слова не говорил.
Но у термина «ООП» есть автор. Он и словами, и делами объяснил этот термин: что именно под ним подразумевается и в каком виде.
А по поводу ООП на C можно ответить так: прежде, чем вы сможете на C писать объектные программы, вам придется сначала создать соответствующую «инфраструктуру» (те же объекты + механизм посылки сообщений и связывания их с кодом) — другими словами, написать объектный «DSL». Так что без языка — увы — все равно ничего не выйдет.symbix
08.08.2016 12:09Кажется, вы слишком буквально воспринимаете «отправку сообщений». Это не о реализации, а о принципе. О способе думать.
Вот в Objective C технически отправка сообщений, а в Swift-е — технически вызов метода. И ничего, ООП-программы писать можно и на том, и на другом.
На С можно обойтись структурой с указателями на функции и договориться первым аргументом передавать this (привет, python).chaetal
08.08.2016 12:20-1Кажется, вы слишком буквально воспринимаете «отправку сообщений».
Вы хотите рассказать мне о том, как я это понимаю? :) Ну, ок, давайте…
Вот в Objective C технически отправка сообщений, а в Swift-е — технически вызов метода.
Что такое «технически отправка сообщений»? И что такое «технически вызов метода»? Я-то по простоте душевной думал, что независимо ни от чего, получивший сообщение объект должен как-то связать этот факт с кодом, который это сообщение должен обработать. Оказывается — нет. Очень-очень интересно, продолжайте.
На С можно обойтись структурой с указателями на функции и договориться первым аргументом передавать this (привет, python).
И что же это будет, если не язык?symbix
08.08.2016 12:25> Что такое «технически отправка сообщений»? И что такое «технически вызов метода»?
Принципиальная разница, в том, в какой момент связываем сообщение с кодом-обработчиком — compile time или run time.
Хотя на самом деле все сложнее. Например, компиляторы Objective C могут оптимизировать «сообщение» в «вызов метода», если во время компиляции обрабатывающий код очевиден. Когда (и если) в PHP сделают JIT, там наверняка будет то же самое.
Опять же, это несущественные детали. Парадигма — она о способе думать, а не о технической реализации.
> И что же это будет, если не язык?
На уровне языка — структура и указатели. А объект и сообщение — у нас в голове.chaetal
08.08.2016 12:32Принципиальная разница, в том, в какой момент связываем сообщение с кодом-обработчиком — compile time или run time.
Ох, я начинаю жалеть, что вступил в беседу. Давайте вы сначала разберетесь хотя с тем, что передача сообщения не может быть реализована в «compile time», хорошо?
…А потом, надеюсь, и с другими понятиями. Удачи!symbix
08.08.2016 13:25Да, я тоже зря вступил с вами беседу. Давайте вы сначала будете читать комментарии целиком и разберетесь, чем парадигма отличается от технической реализации, а потом, надеюсь, и с другими понятиями. Удачи!
babylon
05.08.2016 15:41Классы это те же объекты. И функции. Правда в том, что для программирования объект «Класс» не нужен. Подпишусь под необходимостью агрегировать и делегировать. Вспоминая Smalltalk…
chibitko
05.08.2016 15:53«Вы не любите кошек? Да вы просто не умеете их готовить!»
Заметил такую особенность в своём коде — почти все поля классов readonly, за исключением классов для данных.
Как-то само собой стало так получатся, может поэтому нет проблем про которые пишет автор?
Если нужно организовать in-memory DB использую разделяемые списки, как я понимаю чистое ФП этого не может, а вот мне надо.
Так что ООП никуда не денется.
mtivkov
05.08.2016 16:15+4Процитирую статью с Хабра:
Алан Кэй, создатель ООП, про разработку, Лисп и ООП
ООП для меня это сообщения, локальное удержание и защита, скрытие состояния и позднее связывание всего. Это можно сделать в Smalltalk и в LISP.
Мне жаль, что давным давно я использовал термин «объект» для этой темы, потому что из-за этого многие люди фокусируются на меньшей из идей.
Большая идея это «сообщения».
nikabc555
05.08.2016 16:28+1ООП очень удобен, но это не значит, что он подходит всегда и везде. Поэтому и недостатки ООП нужно свести к тому, чтобы применить другой подход к конкретной ситуации, но полностью отказываться от ООП из-за этого — это уж слишком
Может кто-нибудь объяснить мне подробнее «Проблему ссылки (The Reference Problem)», а то автор статьи как-то не очень понятно ее расписал? а в поисковиках что-то нашел, но не тоtaujavarob
05.08.2016 22:23Может кто-нибудь объяснить мне подробнее «Проблему ссылки (The Reference Problem)»
Если вы при создании объекта А передаёте ему ссылку на другой объект B, то у вас нет гарантии, что дальше вы можете спокойно продолжать использовать данные объекта B, так как объект А вполне может их поменять как ему вздумается.
Вам надо создать копию объекта B (B') и передать ссылку при создании объекта A на эту копию (на B'), а самим спокойно использовать данные объекта B.
Но эту копию (объект B') не всегда можно создать.nikabc555
07.08.2016 17:47+1На мой взгляд, эта проблема, также, как и многие другие, указанные в посте, вытекает из-за непродуманного проектирования, а не из ООП
Например, на С++:
Ничего не мешает передавать в А константную ссылку на В, если нужны 100% гарантии*, что данные в В не изменятся. В данном случае, если конструктор А требует неконстантную ссылку, то проект не скомпиляется — будет возможность продумать дальнейшие шаги по устранению до запуска программы
Если объект А все-таки принимает неконстаную ссылку, значит он меняет что-то в В, значит создание А должно сопровождаться ожиданием, что в В что-то может измениться
Объект А для своих внутренних нужд может изменять только свои приватные данные, а не внешние, т.е. В в данном случае
То есть, при самом обычном (даже не самом хорошем) проектировании указанной проблемы с ссылкой не должно возникнуть, в принципе
*100% гарантии все равно не будет, т.к. существует const_cast, но это крайний случай, который опять-таки вытекает из проектирования. Константные ссылки, константные методы и т.д. придумали не просто так
fogone
05.08.2016 16:31+2наследование является ключом к повторному использованию
Нет
Но переданный объект небезопасен!
Нет
Так что без лишних разговоров прощаемся с ОО-полиморфизмом, и приветствуем полиморфизм на основе интерфейсов.
Простите, а ОО-пролиморфизм он не на основе интерфейсов?playermet
06.08.2016 00:00Простите, а ОО-пролиморфизм он не на основе интерфейсов?
Возможно ключ в том, что автор писал только на C++, где ключевых слов для интерфейсов нет.fogone
06.08.2016 11:48Да интерфейсы как сущности языка тут вообще нипричем. Просто суть полиморфизма в том, чтобы взаимодействовать с объектом через его интерфейс. В утиной типизации, например, обычно классам вообще не нужно имплементировать интрерфейсов, чтобы воспользоваться балагами полиморфизма.
babylon
05.08.2016 16:49Проблема ООП в том, что класс это часть жёсткой структуры. Если же на программу смотреть как на связный список ссылок из несвязанных объектов и функций такой коллизии не возникает. Что-то можно убрать, что-то добавить.
Idot
05.08.2016 16:52Привет, функциональное программирование.
… и где вторая половина статьи?
Где вторая половина статьи, где объясняется, почему именно ФП, а не скажем ProLog или ещё что-то иное?
customtema
05.08.2016 18:28Как-то все слишком косно.
Ну да. ООП без паттернов проектирования само по себе не суперполезно. Я получил большой профит, когда стал выделять фрагменты декларативного программирования, освоил и стал активно понимать и использовать паттерны.
Все это в сумме очень ускоряет разработку. По отдельности — ломает мозг.
olegchir
05.08.2016 19:18+9Небольшой обзор статьи в новый тэг #кунсткамера
> Эта проблема — ещё одна трещина в Столпе Наследования.
Это трещина не в наследовании, а в башке автора
1) ООП не предназначено для прямого уменьшения повторного использования, оно предназначено для корректного моделирования системы. Повторное использование если вообще и бывает, является побочным эффектом.
Дублирвание кода (текста) делается макросами или еще какой копипастой. Дублирование высокоуровнего смысла — это совсем другая область, другой логический уровень.
Соответственно, сама система должна быть спроектирована для увеличения повторного использования. А не говнокод которым ты это всё замоделил. Чтобы система была правильно спроектирована нужен хороший разработчик систем, например выделенный системный аналитик или архитектор.
Аналитика у человека не было, сам он не то чтобы гений проектирования, поэтому подпер убогую идею костылями на С++ и Java, и плачет теперь что говно почему-то С++, а не его подход к решению задачи.
Т.е. в проблеме поиска сотрудников (аналитиков, архитекторов) обвинили ООП. Хм, отличная отмаза. На менее подготовленного читателя даже проканало бы, но у нас в Инквизиции за такое сразу же сжигают на рее.
2) > Проблема с ОО-языками заключается в том, что они тянут за собой всё своё окружение. Вы хотели всего лишь банан, но в результате получаете гориллу, держащую этот банан, и все джунгли впридачу.
> Джо Армстронг
В 1986 году Джо Армстронг сделал Эрланг. Оперативная память стоила очень дорого, и Джо пришлось капитально попотеть, чтобы сэкономить хоть немного. И процессоры были очень медленные, анализ контекста в компиляторе занимал очень много вычислительных ресурсов.
А потом прошло 30 лет.
3)
> Я в течение десятилетий программировал на объектно-ориентированных языках.
> private ArrayList[Object] a = new ArrayList[Object]();
Вместо private List используется private ArrayList. Абстрагирование, полиморфизм подтипов — не, не слышали. Загримированный под матерого ООП кодера, Штирлиц никогда не был так близок к провалу.
4)
> Возьмём следующий базовый класс
> public class Array { private ArrayList[Object] a…
Знаете, чем хороша Java? Тем что в ней, к счастью, нет такого базового класса.
Автор написал поверх класса List прокси с названием Array (sic!), и запроксировал в нем метод add(element). Потом запроксировал addAll, в котором полностью проигнорировал тот факт, что addAll и add это не какие-то произвольные операции, а жестко связанные. В addAll уже нельзя использовать оригинальный add, нужно использовать свой прокси. После столь свинского с собой обращения, всё похерилось. Конец был немного предсказуем, верно?
5)
> Мы должны работать только с интерфейсом. Это раздражающая тенденция...
… раздражающе мешающая писать говнокод, ясно.
6)
> Идея иерархии “общее/частное”…
> Каждый раз, создавая новую компанию, мне приходится решать проблему, связанную с выбором места для документации компании. Нужно ли мне сделать папку Документы, а внутри неё создать папку Компания? Или мне нужно сделать папку Компания, а внутри неё — Документы? Оба варианта рабочие, но какой из них правильный? Какой лучше?
Вы действительно хотите сделать лучше? Тогда вот мой совет как вашего адвоката:
Лучше всего курить дудку немного реже. Мужик, поверь старому норкоманту, это не доводит до добра. Папка документы — это частный случай сущности Компания, серьезно?
7)
> иерархия “общее/частное” не работает. Так для чего тогда хороши иерархии?
> Для включения (Containment).
Компания включает в себя папку. Если спиздить папку и утащить за пределы офиса Компании, мир рухнет, а автор статьи отвалится с сегфолтом. Ясно. К папкам нужно относиться весьма серьезно, это вопрос национальной безопасности.
С 1 сентября 1960 года (или когда там в продакшене появится ООП), писать на нем разрешается только отряду Мстители под руководством отца Тони Старка. (Marvel не похоже на людей, свято блюдущих хронологию)
8)
> Инкапсуляция, второй обрушившийся столп
Учитывая объем текста, описывающий проблему, к этому моменту автор уже понял, что что-то пошло не так. Проклятое ООП не сдается даже на его территории — посте на Хабре.
9)
> The Reference Problem
Автор сам придумал этот термин?
Я вижу только https://en.wikipedia.org/wiki/Reference_class_problem
и это вообще из статистики, а не программирования
10)
> Если ссылка на объект передаётся конструктору объектов, то он кладёт её в приватную переменную, защищённую инкапсулированием. Но переданный объект небезопасен! Почему? Потому в какой-то части нашего кода содержится указатель на объект, то есть код, вызывающий конструктор. Но он ДОЛЖЕН иметь ссылку на объект, в противном случае он не сможет передать её конструктору.
Автор скорей всего как бы хотел сказать о паттернах fluent interface + builder, примененных в concurrent случае? Что кроме кроме религии запрещает ему сделать все доступы безопасными?
11)
> Конструктору придётся клонировать переданные объекты.
А, автор решил нам продать defensive copying! Об этом рассказывают на первом курсе универа на предмете «программирование на языке высокого уровня» (ПЯВУ). Так что, кто не пробовал поступить даже на первый курс, или никогда не читал классические тексты по Java, или википедию, или вообще ничего, эта информация безусловно будет вам очень полезной.
> в лучшем случае клонировать их бесполезно, а в худшем — невозможно. В каждом из мейнстримовых ОО-языков существует такая проблема. Прощай, инкапсуляция.
Ну а невозможно это потому что? Да черт его знает. Я уже задолбался писать этот комментарий, если честно :-)
12)
> Полиморфизм, третий обрушившийся Столп
> Куда бы они не отправились, он был с ними, но всегда на вспомогательных ролях.
Барбара Лисков и Джанет Винг, Андрей Александреску и Герб Саттер, Роберт «Uncle Bob» Мартин, Девид Хейден и Бертранд Мейер, и другие, менее популярные товарищи, плачут кровавыми слезами и крутятся в постелях как вентиляторы.
13)
> Блог компании Mail.Ru Group
Мэйлру капец. Ясно.
В Новосибирске уже ночь, надо с этим заявязывать.
Спят усталые игрушки, книжки спят,
Одеяла и подушки ждут ребят.
Даже сказка спать ложится,
Чтобы ночью нам приснился
Мир, в котором ООП больше не работаетsumanai
05.08.2016 21:04+1Проклятое ООП не сдается даже на его территории — посте на Хабре
Это перевод, и изначальная статья была на каком-то там medium.com.
Мэйлру капец. Ясно.
Ну бывает, неудачно выбрали статью для перевода.
С остальным согласен.
Alex_ME
05.08.2016 20:05+1Опять! Недели не проходит, чтобы кто-нибудь не стал наезжать на ООП.
Появился новый проект, я не забывал о своей идее с классом и испытывал большой энтузиазм. Без проблем. Повторное использование спешит на помощь. Нужно просто взять класс из другого проекта и применить его. Ну… вообще-то… не просто класс. Понадобится родительский класс. Но… Но это всё. Гхм… погодите… кажется, нужен будет ещё родитель родителя… А потом… в общем, нужны ВСЕ родители. Хорошо… хорошо… Я с этим разберусь. Без проблем.
Мне просто нужна была функция, но она внутри вызывала композицию из нескольких функций, а они в свою очередь еще несколько функций...
Azoh
05.08.2016 20:55Начнем с переиспользования. Которое никак с наследованием не связано. И ФП тут тоже не серебрянная пуля. Функции точно также зависят от других функций, могут требовать странных типов, а джунгли волшебным образом не исчезают. Просто обычно есть средства для создания модулей, в которых эти джунгли изолированы. Проблема автора скорее напоминает проблему отсутствия модульности в C++. Очевидно, проблемы конкретного языка называть пролемами парадигмы нельзя.
Пример со сканером, принтером и копиром — это классика плохого дизайна и неудачных абстракций. Зададимся вопросом, является ли поведение сканера и принтера частями поведения копира. Ответ довольно очевиден: нет. Копир может и не уметь сканировать или печатать. Некоторая реализация копира может использовать сканер и принтер, но это детали реализации, которые скрыты инкапсуляцией. И да, инкапсуляция про отделение деталей реализации от интерфейса, а не про защиту от доступа. Что ж, неудачный дизайн — не проблема ООП, а проблема тех, кто его использует.
В общем, ждем статью «Прощай, функциональное программирование».
termos_38
05.08.2016 22:23Бытует мнение, что если бы автор придерживался солид принципов, то вероятно смог бы избежать доброй части крашей
vlad72
06.08.2016 01:23Идеала не существует по определению. Можно только идеально применять отдельные инструменты для конкретных задач.
vlad72
06.08.2016 02:32Вообще-то статья о том, что ООП тоже должно развиваться с учетом практического применения (например в сторону уменьшения стрельбы в ногу). Вот вывод непонятен — при чем здесь ФП, если ООП еще не исчерпало себя? Может просто надо выдвинуть определенные требования к ОО-ЯП быстрее меняться?
Ну а ФП — это вовсе не следующий, последовательный шаг в развитии, это параллельная ветка. Которая возможно когда нибудь и объединится с ООП,
ShapitoS999
06.08.2016 01:33+1Эта статья — детский сад, парадигма не может быть виновата в своих недостатках, она может быть выбрана на ту или иную задачу, или совокупность задач. Надо заранее предполагать, какая более удобная в каждом случае должна быть выбрана парадигма. К примеру, Web же не кодят на bash. Думаю автор непрофессионален.
softaria
06.08.2016 11:02+1Основная ошибка автора — ни один язык сам по себе ничего не гарантирует. Парадигме надо следовать.
Проблема банана и гориллы решается использованием Dependency Injection — все зависимости должны внедряться в конструктор, причем, в идеале, как интерфейсы.
Проблема хрупкого класса не являтся проблемой ООП. Это просто — антипаттерн. Такой же, как, например, GOD-object. Не надо так писать. А если почему-то надо, то стоит запретить наследование таких классов.
Вот тут , например, автор хорошо показывает, что стрелять себе в ногу можно и на функциональном языке. Было бы желание.
Shamov
06.08.2016 11:19+1У меня плохие новости. Дело в том, что выявление общих свойств у нескольких объектов требует IQ. Тесты на IQ составлены таким образом, что проверяют именно умение выявлять общие закономерности в приведённых частных проявлениях. Чем выше IQ, тем более сложные и запутанные кейсы человек может распутать. Так что если кому-то кажется, что иерархия «общее/частное» не работает, то скорее всего проблема не в иерархии, а в чём-то другом.
i360u
06.08.2016 12:30+5Т. е. статья о том, что если вы пытаетесь с помощью ООП сделать какую-то херню, получается херня? Ок.
Toshiro
06.08.2016 17:21-1Градус неадеквата статьи и отдельных веток комментариев просто зашкаливает.
https://www.youtube.com/watch?v=HTLu2DFOdTg — я просто оставлю это здесь.
chaetal
07.08.2016 21:09Я все комменты не осилил, возможно пропустил… Но таки никого не коробит от этих «трех столпов ООП»? Абсолютно все на самом деле полагают, что ООП — это наследование, инкапсуляция и полиморфизм?!
mx2000
08.08.2016 02:58Пикантность ситуации в том, что каноническое определение ООП на классах звучит так: ООП — это парадигма программирования с использованием объектов, которые являются экземплярами классов, которые в свою очередь образуют иерархию (т.е. наследуются друг от друга).
Отсюда автоматически вытекает необходимость наследования и полиморфизма, а инкапсуляция идет как маленький бонус — было бы странно носить данные объекта (ака его внутреннее состояние) отдельной структурой данных.
Ну и касательно повторного использования — если вспомнить времена Turbo Pascal 5.5 и становления Java — основная киллер-фича ООП, которая хорошо покупалась бизнесом, звучала именно как «возможность повторного использования кода». Это исторический факт.
Другое дело, что практика показала, что профит от повторного использования ООП-кода чуть менее чем ноль.
Такие дела.symbix
08.08.2016 03:33Наследование != повторное использование.
Профита от повторного использования ООП-кода ровно столько же, сколько от повторного использования не ООП-кода. Фреймворки, библиотеки, вот это все.
chaetal
08.08.2016 09:46каноническое определение ООП на классах звучит так: ООП — это парадигма программирования с использованием объектов, которые являются экземплярами классов, которые в свою очередь образуют иерархию (т.е. наследуются друг от друга).
Пикантность как раз в том, что это далеко не каноническое определение ООП. Но все почему-то хватаются за него, а потом плюются. Исходная статья — показательный пример. Комментарии к ней — тоже. Обсуждаются симптомы, а причина — неверное понимание принципов ООП — как-то остается незамеченной. В результате ООП в очередной раз мертв и все бросаются уродовать «новую» (старую) идею с тем чтобы лет через *цать от нее с негодованием отказаться.
WarKnife
08.08.2016 11:51+2Для ООП никогда не существовало канонического определения, так как само понятие ООП распределено во множестве языков, его реализующих. И каждый, кто начинает говорить о каноническом определнии, ошибочно полагает, что он знает все его проявления, что, чаще всего, является ошибкой.
Я не знаю кто и в каком языке впервые ввел ООП, но одним из самых известных пионеров, кто начал его популяризаю, думаю, был Алан Кэй. Он утверждал, что основной задачей данной парадигмы, является уход от статической модульности, в виде поключаемых файлов, к динамическим модулям в виде объектов, реализующих как обобщенную, так и конкретную функциональность. Он никогда не говорил, что основной сутью ООП являются объекты как таковые, но именно инкапсуляция в объектах независимых частей общей системы (откуда и растут ноги многократно используемого кода) и взаимодействие данных частей. Главной задачей была именно динамичность системы: горячие обновления путем замены объектов в runtime, как, например, обновление в erlang, взаимодействие модулей, основанное прежде всего на интерфейсах, обеспечивающих гибкость и легкость изменения объектов без нарушения работоспособности системы в целом, общение объектов между собой посредством сообщений, что давало возможность общим объектам делегировать выполнение задачи более специализированным объектам и многое другое.
В целом, можно сделать вывод, что большинство сторонников ООП вообще не понимают первоначальную идею данной парадигмы, считая её неким выражением философской когнитивной модели объект-субъект (причем с отсутствием последнего). Так что ждать от современных ООП-языков соответствия первоначальным принципам не приходится, а попытки переосмыслить все «по-новому» окончательно выродились во что-то неосмысленное и привели к бесконечному потоку подобных статей.chaetal
08.08.2016 12:09Для ООП никогда не существовало канонического определения, так как само понятие ООП распределено во множестве языков, его реализующих
Вот это новое слово в истории IT! Оказывается, нам всем врали, что этот термин придумал какой-то там Alan Kay. Слова, оказывается, народные :)WarKnife
08.08.2016 12:29+1Kay is one of the fathers of the idea of object-oriented programming, which he named, along with some colleagues at PARC and predecessors at the Norwegian Computing Center.
Думаю, при чтении, следует вдумываться в текст. Также вам следует изучить термин «канонический» и отличия терминов «каноническое определение» и «идея»chaetal
08.08.2016 12:38Думаю, при чтении, следует вдумываться в текст.
Вот именно: «which he named». Да и другими источниками иногда пользоваться не мешает. Он сам неоднократно называл себя изобретателем термина, описывал при каких обстоятельствах это произошло. А также объяснял, что именно под этим термином понимается. И, например, такие слова как «инкапсуляция», «полиморфизм» я в его высказываниях не припомню.
Другие люди тоже с удовольствием много раз говорили о нем как об авторе термина. И я не видел никого, кто бы пытался уличить его во вранье по этому поводу. Если вам что-то известно об этом — будет интересно узнать, что именно.
В целом, можно сделать вывод, что большинство сторонников ООП вообще не понимают первоначальную идею данной парадигмы
А вот с этим я абсолютно согласен.
Wuzaza
08.08.2016 11:56+1Открываем любую книжку для посвященную ООП для начинающих в Java или C++, например Эккеля или Лафоре и в первых же главах, посвященных наследованию и полиморфизму мы видим указание на проблемы множественного наследования, и на проблемы последовательной инициализации классов при создании конструкторов потомков. И самое главное, о чудо! Мы видим решение этих проблем…
Но зачем нам читать такие книжки, они же для новичков, а мы же «в течении десятилетий программировали на ООП». Лучше я напишу свою статью, где выведу на чистую воду языки, ставшие промышленным стандартом программирования…
Crazy_as
08.08.2016 12:00Маразм крепчал…
А если по статье: слабая аргументация, практически все можно опровергнуть. Про полиморфизм дак вообще ничего внятного я не увидел.
AdVv
08.08.2016 12:03Статьи подобного направления выходят с завидной регулярностью, и народ накидывается яростно спорить в комментариях. Как в анекдоте про негров и баскетбольный мяч. Люди, не ведитесь…
AntonL
08.08.2016 12:03Пример с Array и ArrayCount возможно может встретиться, но в реальном проекте так писать не надо. Во всех технологиях есть подводные камни о которых стоит подумать прежде чем что то делать.
Как уже писали
Либо несколько изменить ArrayCount, либо оба Array и ArrayCount(если есть возможность).
Что то мне подсказывает, что в текущей реализации будут и другие проблемы с использованием и ожидаемым результатом.
uploadfor
08.08.2016 12:03Не знаю, что все так взъелись на автора. По мне так вполне годный пятничный вброс (количество комментариев говорит само за себя). Конечно, излишне истеричный и эмоциональный для человека, который всего лишь разочаровался в очередном несовершенном, но это ерунда. Просто место его работы, видимо, изначально предопределило тон статьи. Судя по истории сообщений, там это или норма, или требование.
Надеюсь только, что когда автор разочаруется и в функциональном программировании (а это с его ожиданиями от жизни — вопрос времени), он не поленится на такой же расширенный вброс, с примерами, блок-схемами и картинками. И желательно в какую-нибудь пятницу. Ну и с вектором своих дальнейших действий. Чтобы, так сказать, сохранить интригу объективности.LifeKILLED
14.08.2016 17:14+1Так и представил: сидят менеджеры-маркетологи-директора-соучредители, думают, как бы им ещё попиариться, и тут уборщица заходит мыть полы и говорит: «А давайте напишем на хабре, что плюсы и ява — х-ня». Они ей: «Напишешь?». Она: «Фигня-вопрос» (а она пока полы мыла 20 лет в офисе, наслушалась жалоб программистов, типа «Как достал этот ооооп, завтра — точно уволюсь, и на фп перейду… только сначала кредит отдам и ипотеку»). Извините, если не смешно :)
psyriccio
08.08.2016 12:04Мне очень нравится функциональный подход, кайфую когда пишу код. Но с автором не согласен. Это же банальные вещи, у каждой парадигмы свои плюсы, свои минусы, сотню раз обсуждалось. Для каждой задачи свой инструмент. А в «чистом» виде, зачастую ни одна парадигма не жизнеспособна, за исключением редких «экстремальных» случаев.
Tiulkin
10.08.2016 21:19Прощайте стереотипы, здравствуй здравый смысл. Завтра вы поймёте, что и ФП не является рецептом от всех болезней. Послезавтра засомневаетесь в парадигмах. Послепослезавтра научитесь верить внутреннему голосу. А вот после этого уже поймёте, что не так всё плохо и с ООП, и с ФП, а для собственника оплачивающего ваши плюшки бизнеса (о, ужас!) абсолютно фиолетово, каким именно образом убирается его головная боль и автоматизируется та или иная активность, т.к. IT – это всего лишь инструмент (да, мощный, но один из многих). А если собственником бизнеса вдруг окажетесь вы, так вообще количество откровений превысит все мыслимые и немыслимые ожидания.
taujavarob
11.08.2016 16:00+1Но как-то не хочется возвращаться в то время, когда мосты строили на авось, а из проходящего по ним состава выпрыгивал машинист, отправивший немного раньше своего помощника пешком через мост, чтобы он на том берегу запрыгнул в пустую кабину паровоза.
vbif
13.08.2016 01:50+2Представляется человек, который всю жизнь только и делал, что забивал гвозди. И знаком со всеми недостатками данного соединения: что молотком можно случайно ударить по пальцам, что забиваемый гвоздь может согнуться, что в бетон они не так уж легко забиваются, а керамическая плитка и стекло после попытки забить гвоздь приходят в негодность. И однажды он узнал об анкерных болтах, и теперь рассказывает, как удобно на них вешать картины, и даже хвастается собранной на анкерных болтах мебелью.
chaetal
13.08.2016 10:00Точно подмечено! :)
…Причем нельзя сказать, что за эти годы он стал мастером забивания гвоздей.taujavarob
15.08.2016 14:18А потом изобрели… клей!
chaetal
15.08.2016 17:37-1Да, вот, проблема-то как раз в том, что уже давно никто ничего не изобретал.
DexterHD
Как обычно, куча помоев вылитая на одну парадигму и уже заезженное "
ПикачуФП я выбираю тебя". Без агруменов, примеров, и какого либо описания того, как же это круто и почему это круто.napa3um
Невротики безустанно пытаются оправдать свой выбор марки автомобиля, сигарет, смартфона или парадигмы программирования перед окружающими.
pda0
Ну, это мне сейчас веганов напомнило.
Я — функциональщик. Прекрасно себя чувствую. Это вы от объектов такой раздражительный.
taulatin_one
А что функциональщики у нас не используют объекты и прочие чуждые для них элементы?
Idot
«Новообращённые святее Папы Римского» ©
Помнится недавно в обсуждении ФП, один из его адептов отказывался признавать LISP в качестве ФП (язык с которого ФП и началось), за то что в новых его версиях была добавлена поддержка ООП.
sumanai
Интересно, насколько новых? Даже при чтении «Структура и интерпретация компьютерных программ» начиная с главы 3 идёт ООП. А книжка то старая.
Idot
LISP появился раньше, чем C++ и последующая мода на ООП. LISP появился в 1954 году, C++ в 1983 году, а первая версия Object LISP в 1985 году. Однако, мода на ООП всех захлестнула после Windows 3.0, так как под DOS и UNIX без проблем обходились и без ООП, от чего был былинный срач о ненужности/нужности ООП, а вот Windows уже требовал именно ООП чтобы писать что-то неконсольное.
PS поскольку речь зашла о связи Windows и ООП, то интересно как работают без ООП интерфейсы у ФП программ написанных без ООП?
Idot
0xd34df00d
FRP.
Idot
Спасибо!
IIvana
Чего? Я ни разу не умею это ваше (С) ООП, пишу гуевые программки и игрушки под Винду на чистых сях/хаскеле/чем угодно — что я делаю не так? ВинАПИ сишное на все сто, куда и зачем мне объекты пихать?
Antervis
COM-объекты ж еще есть
Per_Ardua
В таком случае он, наверное, и JS не признает в качестве ФП. По мне так, если функциональный подход реализуем средствами языка, то такой язык можно считать ФП (даже, если он позиционируется как ОО-язык)
vintage
В JS невозможно реализовать сколь-нибудь полезную чистую функцию. О каком ФП тут можно говорить?
ivan386
Поясните подробней пожалуйста с чем это связано?
vintage
Как чистые функции связаны с фп?
ivan386
Невозможность реализации полезной чистой функции.
MaximChistov
подозреваю что он о том, что сторонний код может что-то используемое в функции ВНЕЗАПНО подменить(например undefined)
igrishaev
Хех, так Лисп — это не функциональный язык. Лисп поддерживает ФП-парадигму, но не заставляет ей следовать. Вы можете писать на Лиспе в стиле раннего Си с глобальными переменными.
0xd34df00d
Что за объекты в хаскеле?
lookid
Это статья детектор. Для выявления тех, кто не знает ООП или очень мало (лабки в вузе) писал на нем. Я полностью согласен с автором. Поддерживать ООП-код, когда у тебя 100500 классов и чтобы добавить хоть какую-то доп-фичу нужно перелопачивать 100500 классов и интерфейсов, это ад. Вы видели Java Framework'и? Как вам? Ужас? А что творится с кешем, а виртуальное наследование? Это сущий кошмар.
> Инженер по автоматизации технологических процессов и производств по образованиюИзвините, я думал вы программист с продакшена, а не сферический в вакууме. Можете не читать.
lair
Как уже неоднократно сказано в комментариях, это проблема не ООП, а плохой архитектуры — которую можно на чем угодно нафигачить.
lookid
ООП требует очень много лишней работы для очень простых вещей. И когда архитектура разрастается, то поддерживать и расширять её становится настолько сложно, что она превращается в «плохую архитектуру». Давайте вы сами (да и отписавшиеся здесь) не будете себя обманывать. Вы никогда не писали архитектуру.
lair
Например?
Так, а альтернативные предложения?
Оу да, конечно, вам виднее, что я (и остальные отписавшиеся) писал.
lookid
Если вы писали полиморфизм и парочку extends-implements, то вы не писали архитектуру. Давайте не будем забывать, что 99% здесь отписавшихся никогда не испытывали проблем с кешем и производительностью из-за использования ООП. И 99% здесь пишут ООП ради ООП.
lair
Я же говорю, вам явно виднее, кто что писал. Зато как конкретные вопросы — так никаких ответов. Мимими.
lookid
Я рад, что вам весело. Надеюсь, что ваши проблемы не будут выходить дальше готовых решений, документации и разворачивания образов виртуальных машин и бд.
lair
Ну и да, признаюсь, я правда никогда не испытывал проблем с кэшом и прозводительностью из-за использования ООП. Разное было, и хреновый I/O (пятикратная просадка), и идиотская ошибка в распаковке base64 (шестидесятикратная просадка), и всякие другие вещи — а вот ООП как-то не мешало пока еще.
areht
> никогда не испытывали проблем с кешем и производительностью из-за использования ООП
мм… А вы вообще в курсе, что ООП-программы компилируются в тот же ассемблер, что и всё остальное?
Toshiro
У него свой ассемблер с блекджеком и ш@#$%^и)) И в его ассемблере ООП при компиляции вызывает проблемы с кешем и производительностью)) Ну чего вы набросились на человека?)))
Mendel
Ну справедливости ради — проблемы с производительностью у ООП вполне себе реальная тема. типичный сценарий — быстро прототипируешь логику на простых SOLIDных классах.
Десяток уровней абстракции, сотня классов в предметной области где высокая связность всего и вся. И получаем дикую потерю в скорости. Но!
1 — без ООП мы бы еще схемы на доске рисовали, а не проблемы щупали.
2 — Задача обычно решается так или иначе кешированием, или некоторым нарушением изящной структуры.
3 — даже если задача действительно плохо пригодна для эффективной работы в ООП-парадигме (например в конечном итоге у нас оказывается 80% пятиэтажных join «в узких местах», которые занимают 80%, то всё равно гораздо проще и эффективнее писать это всё поверх уже работающей пусть и неэффективной структуры, чем «в вакууме».
4 — реально задач таких почти и нет. В голову сейчас приходит только один пример, да и то не у меня был — там предметная область была — научные исследования в области генетики. «Божественный код» это классическая лапша, поскольку о структуре там никто не думал, так что слабо детерминируется, и анализ его аналогично вызывает вопросы. Тут нельзя отрефакторить предметную область :)
Резюмирую — ООП действительно имеет проблему с производительностью, но это цена за более быструю и понятную разработку. К тому-же как правило в прямых руках цена незначительна.
Toshiro
Десятки сотен уровней абстракции… Сотни тысяч классов… Пятиэтажные json… Вы упретесь в невозможность поддержки такой системы намного раньше проблем с производительностью, независимо от того что у вас под капотом — ФП, ООП или гибрид!!!
Ну включите уже наконец моск! Люди неспроста придумали микросервисы, сервисные шины и оркестровку!!! Все кто солидарен со статьей здесь говорят ООП подразумевая, что это определяет архитектуру системы… да как так?! Вы сколько лет в этом бизнесе народ?!
lair
Микросервисы, сервисные шины и оркестровка — это все как раз быстренько убивает производительность в ее первичном смысле, потому что это все — распределенные системы. Другое дело, что после этого мы можем заниматься масштабированием и всем таким, но это уже принципиально другой уровень сложности.
Toshiro
Голословное утверждение, т.к. повышение или снижение производительности напрямую зависит от задачи. Есть задачи в которых выделение микросервисов убьет производительность, а есть задачи в которых наоборот отсутствие микросервисов — самоубийство.
И мы опять возвращаемся к тому же самому. Что ООП != архитектура. Вы повторяете ту же ошибку, что и lookid и JustMoose выше. Делаете однозначные заявление о технологии в принципе, не учитывая детали ее применения.
Класс который связан с тяжелыми вычислениями, если один-единственный на микросервис или же вплетен в иерархию классов монолитного приложения. Как оценить его производительность абстрагируясь от архитектуры, в которой он живет? От методов, в которых он используется?
Mendel
Ничего что это вы именно тот человек который предлагает технологию способную отстрелить ногу не ориентируясь на контекст?) Сложность системы не является показанием для микросервисов и прочих решений. Зависит от множества факторов.
Toshiro
А что тогда является показанием для архитектурных решений Карл?! о_О
Toshiro
Да, поскольку часто выделение микросервов сокращаит оверхед. Например если части запроса асинхронные, не?
И вообще, мы как то ушли от основной темы. В общем я к чему — в целом вообще неважно ООП у тебя или ФП. Накосячить можно и так, и так.
lair
Нет, выделение микросервисов не сокращает оверхед. Выделение микросервисов добавляет оверхед, который потом может компенсироваться тем или иным выигрышем (например, за счет асинхронности/параллелизма).
Можно подумать, с этим кто-то спорил.
lair
Что именно голословное утверждение? Что распределенная система добавляет оверхед?
Не от задачи, а от архитектуры и контекста.
Mendel
Нее. Там бы всё и утонуло под весом «микро»сервисов)
Вы тут совсем не в тему сказали.
Piter_Y
Если точнее, то в команды процессора или байт-код, для java например.
struvv
Проблемы, которые приписывают ООП это всё различные ипостаси проблемы протекающих абстракций. И эта проблема не имеет отношение к ООП, она касается любых инженерных продуктов. Например необходимость прогреть двигатель автомобиля зимой — протекающая абстракция. Пользователю хорошего продукта должно быть глубоко не интересно как устроен двигатель и какие особенности его эксплуатации в холодные дни. Он должен работать хорошо сам без необходимости дополнительных манипуляций со стороны пользователя.
Ни ООП, ни ФП не решают эту проблему, т.к. суть этой проблемы лежит вне парадигм. В ФП можно написать код, который использует десятки ГБ памяти на ровном месте. Если бы разработчики чистых ФП языков эту проблемы таки решили бы, то не нужно было думать о том, как устроены все потроха, но сейчас ФП имеет на мой взгляд наиболее неприятно текущие абстрации — они протекают чуть чуть, но очень ядовитой жидкостью, а не водой.
Если в ООП дырки лишь раздражают, то в ФП они часто исчерпывают все доступные ресурсы, что гораздо хуже при реальной эксплуатации. И это ни разу не моя проблема, ибо при использовании хороших абстракций я не должен знать что у них внутри иначе рано или поздно ресурсы нервной системы не смогут вместить то, как работает проект даже при нахождении в состоянии потока. Про рефакторинг я вообще молчу, а там есть что сказать.
Подавляющая часть статьи это набор рассказов о плохой архитектуре, но не о проблемах ООП. Да и нет жёсткой границы в реальных ЯП — строго ООП и ни шагу за границу. ООП это один из многих инструментов и его можно использовать вместе с ФП, а не ФП вместо ООП. Может быть пора заканчивать войну и начать мирные переговоры?
vlad72
Не хотите написать статью про дырки в ФП?
symbix
Вопрос не ко мне, но думаю, что это к вопросу ленивых вычислений.
Скажем, банальная хвостовая рекурсия в Haskell спокойно выкушает всю память.
Но я не считаю, что это проблема — просто надо понимать, что пишешь.
0xd34df00d
Банальная хвостовая рекурсия память там не выкушает. Выкушает большая невычисленная цепочка thunk'ов, но это совсем другой разговор.
C4ET4uK
>Это статья детектор. Для выявления тех, кто не знает ООП или очень мало (лабки в вузе) писал на нем.
Тут согласен. А дальше ересь пошла.
fedor1113
Кхм… Этот комментарий детектор. Для выявления тех, кто не знает ООП или очень мало (лабки в вузе) писал на нем (или тех, кого на нём писать [толком] не научили). Простите, но не смог удержаться… (ибо чистая правда)
KroTozeR
Наш проект собирается пол часа. Он достался нам по наследству от коллег, которые раньше писали с применением ФП, потом сели на C++ Builder и поехали всякие TХреньКотораяДелает() --> TХреньКотораяДелаетБольше() --> TХреньКотораяДелаетМного(). Так же пошли структуры с указателями на классы для передачи данных между ними… и т.д. и т.п.
Пытаемся по немногу рефакторить сего монстра. Встаивание функционала по ТЗ (т.н. «улучшения») в код, построенный на ФП происходит раза в четыре быстрее, чем в унаследованный код. Львиную долю времени приходится отслеживать связи, чтобы понять причину падения при успешной сборке.
Да-да, архитектура, конечно, но она – наипрямейшее следствие злоупотребления ООП! И адепты ООП могут сколь угодно с пеной у рта рассказывать про «руки из жопы», но мы на этом теряем время, а потому активно выпиливаем из проекта этот зООПарк, ибо он вредит процессу.
lair
Вы правда думаете, что ФП нельзя злоупотребить в такой же мере?
wholeman
Люди сменили парадигму программирования. Естественно, что проект, на котором они осваивали новую, имеет, мягко говоря, некоторые недостатки. Чувство любого инструмента приходит после периода проб и ошибок.
neit_kas
Просто проблема автора в том, что он не использует парадигму, а поклоняется ей. Когда-то поклонялся ООП, потом нашёл в нём проблемы, и его мир был буквально разрушен. Теперь (с горя наверно) также стал поклоняться ФП. Ждём статью годика через два с заголовком: «Прощай, функциональное программирование».
unixwz
«Здравствуй процедурное».
taujavarob
Хм. Нужен ли goto?
Idot
Кнут в «Искусстве программирования» не стремался его активно использовать.