Pull to refresh

Comments 34

Хорошая статья, автору большое спасибо. Достаточно просто и грамотно.
>>межпроцессорное взаимодействие
полагаю, имелось в виду межпроцессное

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

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


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

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

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

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

Описание всех этих аспектов, пускай и в компактном виде, но в одном месте — было одной из целей данной статьи.
И было бы здорово перенять положительный опыт и рисовать классы и методы в UML.

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

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

Расцвет ООП начался в эпоху оконных интерфейсов, где иерархическая модель однотипных визуальных компонентов с наследованием и инкапсуляцией поведения (отрисовка, реакция на ввод) хорошо вписывалась в предлагаемую парадигму. В реальных задачах концепты ООП становятся рудиментарными.
— Миллионы приложений используют реляционную модель и живут счастливо без всякого наследования в своей предметной области. Приложение в 99% случаев работает с конкретной сущностью, нежели абстрактным надклассом.
— Инкапсуляция становится бесполезной, когда изменения состояния затрагивает сразу много объектов, и когда зависимости одни от других нетривиальны и не вписываются в иерархическую схему. Пример: бильярдный стол с шарами и физикой. Каждый шар хранит и изменяет состояние. Инкапсулировать физику отскоков в шары невозможно, поскольку шар ничего не знает о других.
— Переопределяя очередной метод в большой иерархии, программист не может быть уверен, что не нарушит деятельность объекта на каком-то уровне.
Я тоже стал думать в эту сторону. Автор начинает с того, что предлагает задуматься над причиной принятия наших решений, а потом без всяких раздумий выбирает ООП как концепцию.
В то же время, существует (и поддерживается многими) мнение, что ООП-моделирование в классическом понимании красиво в теории, но создает огромное количество проблем на практике. А также существуют другие подходы, такие как функциональное программирование, пытающиеся преодолеть эти ограничения.
Понятно, что с таким разбором статья получилась бы совсем большой. Но стоило хотя бы отметить существование выбора.
Я тоже стал думать в эту сторону. Автор начинает с того, что предлагает задуматься над причиной принятия наших решений, а потом без всяких раздумий выбирает ООП как концепцию.

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

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

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

ФП не пытается преодолеть «ограничения», введёнными ООП, т.к. появилось оно на 20 лет раньше и никак не могло чего-либо знать об этих «ограничениях». Контекст применения ФП несколько другой, им удобно делать обработку данных, но вот там, где у нас много состояния, ФП создаёт лишние сложности. Просто иногда там, где лучше было бы задачу решать с точки зрения ФП, её начинают решать с помощью ООП и сильно обламываются. Обратное, кстати, тоже случается.
ФП это очень обобщенное понятие. Современное (практическое) ФП появилось только в языках OCaml и Haskell. То есть примерно в то же время, что и ООП. И если бы в C++ основной упор не был сделан на ООП, а на ФП, то мы бы сейчас писали на совсем других языках.
Кстати товарищ Степанов еще 25 лет назад в ФП увидел бОльший потенциал и спроектировал STL для C++ в функциональном стиле. Но не помогло.

Так что последние 25 лет ФП как раз догонял ООП, ичсх таки догнал, потому что ООП уперся в потолок выразительности, а ФП все еще есть куда расти.

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

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

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

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

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

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

Для второго даже придуман принцип OCP, который говорит нам, что как только мы начали использовать класс нельзя больше вносить изменений в его контракт (привет итерационное проектирование).

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

Тут просто существует общая проблема изменения поведения кода, который уже где-то используется.
Еще раз: вызовы процедур — явные. Вызовы виртуальных методов классов — нет. Более того, у вас может и не быть исходников классов, от которых вы наследуетесь (ага OCP). А вот как заставить код в другом модуле вызвать мою процедуру, да еще и без моего ведома — понятия не имею
Я не понимаю, как Ваш комментарий связан с проблемой изменения существующего поведения, на основе которого реализовано другое поведение.
Если Вы используете чужую процедуру, исходного кода которой у Вас нет и полагаетесь на ее поведение, что произойдет если ее поведение поменяется? У Вас ничего не поломается все равно?
ОК, не понимаете. Давайте с примерами.

сlass Base {
    public void Public() { this.Internal(); }
    protected abstract void Internal();
}

class Derived: Base {
    int x = 0;
    protected override void Internal() { x++; }
}

Предположим что авторы классов Base и Derived — разные.

Потом автор меняет метод Base.Public() на такой:
        public void Public() 
        { 
            this.Internal(); 
            this.Internal(); 
        }


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

А теперь другой сценарий. Вносят такие изменения в код:
class Derived: Base {
    int x = 0;
    protected override void Internal() { 
        if(x++==100) throw new Exception(); 
    }
}

При этом код, вызывающий Base.Public, выглядит примерно так:
try {
   b.Public();
} catch (InvalidOperationException e) {
   //some code 
}


Это нарушение LSP, которое очень легко допустить из-за неявных контрактов.

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

В случае функционального подхода можно представить иерархию функций:
let func1 f =
  let func2 f =
    let funcX f = f + 1
    funcX f;
  func2 f;
   
printfn "%i" (func1 1)

И предположить, что функция funcX сделана другим автором, который потом берет и меняет ее поведение на:
let funcX f = f - 1

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

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

Это вы сейчас так говорите, а в статье ни слова.
Это вы сейчас так говорите, а в статье ни слова.

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

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

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

Именно поэтому во фреймворках довольно мало используется наследование. Массово — только в UI на десктопе.

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

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

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

Похоже на краткую выборку из книг:
1) «Объектно-ориентированный анализ и проектирование» Буча
2) «Agile Software Development, Principles, Patterns, and Practices» Мартина
3) «АКПП» Фаулера
4) «DDD» Эванса
Но без ссылок на сами книги.
К сожалению, написать статью так, чтобы каждый нашел для себя что-то новое и интересное, затрагивая большой круг тем, да еще и в компактном виде, наверное, нереально.
Жаль, что это статья не смогла Вам угодить. Надеюсь, в какой-нибудь другой раз все будет иначе.
Пока дочитал только до динамиков, но мне нравится.

Придирки к автору на тему ООП vs ФП непонятные какие-то. Статья вроде как об ООП, что прямо указано в заголовке. Это как раньше в каждый пост про винду сбегались линуксоиды (и наоборот :)), так теперь модно похоливарить в постах об объектно-ориентированном программировании про функциональное?)
UFO just landed and posted this here
ООП это инструмент им надо уметь пользоваться и знать где. ФП другой инструмент и к нему те же требования. Конечно плохо когда язык (Java) навязывает выбор инструмента, демонстрируя одновременно недостатки такого подхода (классовые методы вместо функциональных программ). Я думаю что негативная оценка ООП возникла от его бездумного применения. Концепции ООП я считаю прорывными в ИТ отрасли они очень полезны. С ними связана целая эпоха в программировании и это уже не выбросить. Но применять его везде и всюду нельзя. ООП консервирует объекты и если они динамически меняются то для них ООП не подходит. Но почти всегда в ИТ проекте есть базовые типы стабильные сущности которые хорошо описываются классами. Для них ООП идеальный инструмент. Динамические объекты меняющие набор своих свойств должны описываться по другому. Статья очень полезная, по крайней мере для меня.
Пользователь высказал пожелание о том, что в комнаты домов днем должен попадать дневной свет так, чтобы все было видно. Разработчик, недолго думая, выдолбил в стене круглую дыру и проверил — оказалось, что света все еще недостаточно. Тогда разработчик выдолбил еще две дыры рядом и опять проверил. Убедившись, что света достаточно, разработчик решил, что задача выполнена. По этому принципу дыры были выдолблены во всех домах. Требования были соблюдены.
Но лето кончилось и наступила зима, а вместе с ней и холода. Злой пользователь прибежал и стал ругаться на то, что в комнатах стало ужасно холодно. При выяснении причин стало ясно, что из-за дыр, выдолбленных под солнечный свет, в комнату попадает много холодного воздуха с улицы и она промерзает. После выяснений обстоятельств оказалось, что пользователь хотел обычное окно, но какие-то требования забыл упомянуть он, а что-то по-своему передал аналитик, и получилось то, что получилось. Так как все промерзло – решать проблему пришлось на ходу, времени на разработку хороших окон и переделку дыр под окна в зимний период уже не было. Поэтому было принято решение обойтись «костылем» и заделать дыры полиэтиленом. Такой способ помог избавиться от сильного промерзания и позволил оставить солнечный свет днем. Но кто знает, сколько новых проблем неполное понимание изначальных требований еще принесет…

Божественное сравнение, надо будет запомнить =)
-А зачем, вместо того, чтобы сделать нормальный запрос к БД, он вытягивает обе таблицы на сервер приложения, затем героически объединяет их и фильтрует результат для получения необходимых данных?
— Когда у тебя в руках только молоток, все задачи кажутся гвоздями.

Еще одна крылатая цитата, круто, спасибо, в копилку ))
Sign up to leave a comment.

Articles