Лекция
Это окончание невероятной информации про недостатки ооп.
...
иное, как костыли. Они существуют исключительно для устранения недостатков ООП. На эту тему было написано множество книг. И они не были бы такими плохими, если бы не вносили огромную сложность в кодовые базы.
Хотя легкое изменение кода под известный шаблон может упростить понимание кода, по мнению Стива Макконнелла, с применением шаблонов могут быть связаны две сложности. Во-первых, слепое следование некоторому выбранному шаблону может привести к усложнению программы. Во-вторых, у разработчика может возникнуть желание попробовать некоторый шаблон в деле без особых оснований.
Многие шаблоны проектирования в объектно-ориентированном проектировании можно рассматривать как идиоматическое воспроизведение элементов функциональных языков . Питер Норвиг утверждает, что 16 из 23 шаблонов, описанных в книге «Банды четырех», в динамически-типизируемых языках реализуются существенно проще, чем в С++, либо оказываются незаметны . Пол Грэхэм считает саму идею шаблонов проектирования — антипаттерном, сигналом о том, что система не обладает достаточным уровнем абстракции, и необходима ее тщательная переработка . Нетрудно видеть, что само определение шаблона как «готового решения, но не прямого обращения к библиотеке» по сути означает отказ от повторного использования в пользу дублирования. Это, очевидно, может быть неизбежным для сложных систем при использовании языков, не поддерживающих комбинаторы и полиморфизм типов, и это в принципе может быть исключено в языках, обладающих свойством гомоиконичности (хотя и не обязательно эффективно), так как любой шаблон может быть реализован в виде исполнимого кода
Фактически невозможно написать хороший и поддерживаемый объектно-ориентированный код.
На одной стороне спектра есть кодовая база, которая является непоследовательной и не придерживается каких-либо стандартов. По другую сторону — башня с чрезмерно сконструированным кодом, куча ошибочных абстракций, построенных одна поверх другой. Шаблоны проектирования очень помогают при построении таких башен абстракций.
Вскоре добавление новой функциональности и даже понимание всей сложности становится все труднее и труднее. Кодовая база будет полна таких вещей, как SimpleBeanFactoryAwareAspectInstanceFactory, AbstractInterceptorDrivenBeanDefinitionDecorator, TransactionAwarePersistenceManagerFactoryProxy или RequestProcessorFactoryFactory.
Нужно тратить драгоценные интеллектуальные ресурсы, пытаясь понять башню абстракций, которую создали сами разработчики. Отсутствие структуры во многих случаях лучше, чем плохая структура.
Рисунок 6 Идеальное предтсаление и реальная реализация ООП
“Все методологии основаны на страхе.” — Кент Бэк
Похоже, некоторые из моих студентов работают по Agile-методологии в стиле Чака Норриса:
“Чак Норрис не занимается итеративной разработкой. Все получается с первого раза, каждый раз.”
“Чак Норрис не пишет документацию. Он смотрит в упор на код до тех пор, пока тот не скажет все, что надо знать.”
Бертран Мейер рассказывает о том, что его удивляло, почему схематические языки программирования всегда были такими популярными, пока однажды его не осенило: “Пузыри не ломаются!”. (Другое высказывание, принадлежащее Мейеру: “All you need is code”)
Похоже, что с тем, что обычно понимается под разработкой через моделирование, тоже что-то не так: не код должен генерироваться из модели — модель должна быть кодом.
Между прочим, FORTRAN продавался как язык высокого уровня, из которого генерировался исходный код. А теперь язык высокого уровня для нас и есть исходный код.
Надеюсь, однажды, когда мы повзрослеем, модель будет считаться считаться исходным кодом.
И наконец, коронная фраза, которая звучит очень осмнительно: “Объектов недостаточно. Нужно еще...”.
Все эти годы нам нужны были фреймворки, компоненты, аспекты, сервисы (которые, похоже, любопытным образом вернули нас к процедурному программированию!)
Если объектов никогда не было достаточно, почему же они исправно служили нам все эти годы?
Автоматическое тестирование является важной частью процесса разработки и очень помогает в предотвращении регрессий (ошибок, вносимых в существующий код). Модульное тестирование играет огромную роль в процессе автоматического тестирования.
Некоторые могут не согласиться, но ООП-код общеизвестно труден для модульного тестирования. Модульное тестирование предполагает изоляцию, и чтобы создать метод для такого вида тестирования, нужно:
Сколько еще препятствий нужно преодолеть, чтобы сделать фрагмент кода тестируемым? Сколько времени было потрачено впустую? Кроме того, нужно создавать экземпляр всего класса, чтобы протестировать один метод. Это подтянет код из всех его родительских классов. С ООП писать тесты для унаследованного кода еще сложнее, практически невозможно. Целые компании были созданы (TypeMock) из-за проблемы тестирования легаси-кода.
Шаблонный код (бойлерплейт) является самой большой проблемой, когда речь идет о соотношении сигнал/шум. Шаблонный код — это «шум» для компиляции программы. Такой код требует времени для написания и делает кодовую базу менее читаемой.
Хоть в ООП пропагандируется «программа для интерфейса, а не для реализации», не все должно становиться интерфейсом. Следовало бы прибегнуть к использованию интерфейсов во всей кодовой базе с единственной целью — тестирование. Также, вероятно, пришлось бы использовать внедрение зависимостей, что в дальнейшем привело бы к ненужной сложности.
Некоторые утверждают, что приватные методы не должны тестироваться. Я склонен не согласиться, модульное тестирование называется «модульным», так как тестируются небольшие изолированные блоки кода. Тем не менее, тестирование приватных методов в ООП практически невозможно. Не следует делать приватные методы внутренними только ради тестирования.
Чтобы достичь тестируемости частных методов, их обычно извлекают в отдельный объект. Это, в свою очередь, вносит ненужную сложность и шаблонный код.
Рефакторинг является важной частью работы разработчика. По иронии судьбы ООП-код трудно реорганизовать. Рефакторинг должен сделать код менее сложным и более понятным. Напротив, реорганизованный код в ООП становится значительно более сложным. Чтобы сделать код тестируемым, нужно использовать внедрение зависимостей и создать интерфейс для реорганизованного класса. И даже тогда рефакторинг ООП-кода сложен без специальных инструментов, таких как Resharper.
// До рефакторинга: public class CalculatorForm { private string aText, bText; private bool IsValidInput(string text) => true; private void btnAddClick(object sender, EventArgs e) { if ( !IsValidInput(bText) || !IsValidInput(aText) ) { return; } } } // После рефакторинга: public class CalculatorForm { private string aText, bText; private readonly IInputValidator _inputValidator; public CalculatorForm(IInputValidator inputValidator) { _inputValidator = inputValidator; } private void btnAddClick(object sender, EventArgs e) { if ( !_inputValidator.IsValidInput(bText) || !_inputValidator.IsValidInput(aText) ) { return; } } } public interface IInputValidator { bool IsValidInput(string text); } public class InputValidator : IInputValidator { public bool IsValidInput(string text) => true; } public class InputValidatorFactory { public IInputValidator CreateInputValidator() => new InputValidator(); }
В этом простом примере количество строк увеличилось более чем в два раза только для извлечения одного метода. Почему рефакторинг создает еще большую сложность, когда код подвергается рефакторингу, который нужен в первую очередь для уменьшения сложности?
Сравните это с аналогичным рефакторингом не-ООП-кода в JavaScript:
// До рефакторинга: // calculator.js: const isValidInput = text => true; const btnAddClick = (aText, bText) => { if (!isValidInput(aText) || !isValidInput(bText)) { return; } } // После рефакторинга: // inputValidator.js: export const isValidInput = text => true; // calculator.js: import { isValidInput } from './inputValidator'; const btnAddClick = (aText, bText, _isValidInput = isValidInput) => { if (!_isValidInput(aText) || !_isValidInput(bText)) { return; } }
Код буквально остался прежним. Функция isValidInput() просто переместилась в другой файл и добавилась одна строка для импорта этой функции. Также добавилась _isValidInput() к сигнатуре функции для удобства тестирования.
Это простой пример, но на практике сложность ООП-кода возрастает экспоненциально по мере увеличения кодовой базы. И это еще не все. Рефакторинг ООП-кода крайне рискован. Сложные графики зависимостей и состояния, разбросанные по всей кодовой базе ООП, не позволяют человеческому мозгу рассмотреть все потенциальные проблемы.
Я не думаю, что существует «серебряная пуля», поэтому просто опишу то, как это обычно сегодня работает в моем коде.
Если коротко - то применяется некая технологическая эклеткита и разумное сочетени всех методологи разработки программногно обеспечения.
Первым делом я изучаю данные и очень хорошо беседую с заказчком. Т к м пишем не код, а решаем некую проблему заказчка. Тоест сначала нужно использовать ООА (объектно ориентированны анализ) Анализирую, что поступает на вход и на выходы, формат данных, их объем. Разбираюсь, как данные должны храниться во время выполнения и как они сохраняются: какие операции должны поддерживаться и с какой скоростью (скорость обработки, задержки) и т.д. Создаю коцептуальную модель базы данных в сочетании коротко и понятной CRUD matrix + Use case UML.
Обычно если данные имеют значительный объем, моя структура близка к базе данных. То есть у меня будет некий объект, например DataStore с API, обеспечивающим доступ ко всем необходимым операциям для выполнения запросов и сохранения данных. Сами данные будут содержаться в виде структур ADT/PoD (Простая структура данных (англ. plain old data, POD) abstract data type (ADT), а любые ссылки между записями данных будут представлены в виде ID (число, uuid или детерминированный хеш). По внутреннему устройству это обычно сильно напоминает или на самом деле имеет поддержку реляционной базы данных: Вeкторы или HashMap хранят основной объем данных по Index или ID, другие структуры используются как «индексы», необходимые для выполнения быстрого поиска, и так далее. Здесь же располагаются и другие структуры данных, например кеши LRU и тому подобное.
Основная часть логики программы получает ссылку на такие DataStore и выполняет с ними необходимые операции. Ради параллелизма и многопоточности я обычно соединяю разные логические компоненты через передачу сообщений наподобие акторов. Пример актора: считыватель stdin, обработчик входящих данных, trust manager, состояние игры и т.д. Такие «акторы» можно реализовать как пулы подпроцессов, элементы конвейеров и т.п. При необходимости у них могут может быть собственный или общий с другими «акторами» DataStore.
Такая архитектура дает мне удобные точки тестирования: DataStore могут с помощью полиморфизма иметь различные реализации, а обменивающиеся сообщениями экземпляры акторов могут создаваться по отдельности и управляться через тестовые последовательности сообщений.
Иногда система сокращается просто до использования сервисов, моделей, некторых сущностей , представлени , контроллеров (чтоб каждая из них не стала жирным).
Основная идея такова: только потому, что мое ПО работает в области, где есть концепции, например, клиентов и заказов, в нем не обязательно будет класс Customer и связанные с ним методы. Все наоборот: концепция Customer — это всего лишь набор данных в табличной форме в одном или нескольких DataStore, а код «бизнес-логики» непосредственно манипулирует этими данными. и не забвате что SQL и терия реляционніх данніх гораздо проработанне и проверенная временем чем таже xDD.
Как вариант можно пофилософствовать на тему функционального или логичекого программирования
Теперь мы знаем, что ООП — эксперимент, который провалился. Настало время двигаться дальше. Настало время нам, как сообществу, признать эту идею провальной и отказаться от нее.
Лоуренс Крабнер
Функциональное программирование кажеся сложным из-за наличия сложных названий основных идей.Такими являются функторы и монады, замыкания, лямда функции. Функторы – это то, что можно преобразовать с помощью функции, например, list.map. Монады – просто цепочка связанных вычислений!
Функциональное программирование сделает из вас лучшего разработчика. Вы наконец-то начнете писать код, который решает проблемы реального мира, вместо того, чтобы тратить время на обдумывание абстракций и шаблонов проектирования.
Возможно, вы еще не осознали себя функциональным программистом. Вы используете функции в повседневной работе? Да? Тогда вы уже функциональный программист! Осталось научиться эффективному использованию функций.
Два хороших функциональных языка с легким обучением – это Elixir и Elm. Они позволяют разработчику сосредоточиться на том, что действительно важно – на написании надежного софта и удалении сложности традиционных функциональных языков.
Альтернативы? Ваша организация использует C#? Попробуйте F# – классный функциональный язык, совместимый с .NET. Используете Java? Тогда Scala и Clojure – неплохие варианты для вас. Используете JavaScript? С правильным руководством и линтингом JavaScript превращается в хороший функциональный язык.
. Бессмертные слова Эдсгера Дейкстры гласят:
«Объектно-ориентрованное программирование — это исключительно плохая идея, которую могли придумать только в Калифорнии.”
Не забывайте, что программирование -творческая деятельность, поэтому кричино осноситесь к каждой методологии, и применяйте все инструмент которые необходимы для реализации данных задач.
Некоторые люди склонны считать функциональное программирование очень сложной парадигмой, которую применяют только в научной среде, и которая непригодна для «реального мира». Конечно, это не так!
Да, функциональное программирование имеет под собой сильное математическое основание и уходит своими корнями в лямбда-исчисления. Но большинство идей возникли в нем, как ответ на слабость мейнстримных языков программирования. Функция – это основная абстракция функционального программирования. При правильном использовании функции дают модульность и повторное использование кода, невиданное в ООП.
Функциональное программирование отлично справляется с задачей написания надежного софта. Необходимость в дебаггере полностью исчезает. Больше не нужно перемещаться по своему коду и отслеживать переменные.
Наконец, если вы умеете использовать функции, вы уже функциональный программист. Осталось научиться использовать функции наилучшим образом!
Если такие термины, как функторы и монады ни о чем вам не говорят, то вы не одиноки! Функциональное программирование не было бы таким пугающим имей оно более интуитивные названиями основных идей. Функторы – это то, что можно преобразовать с помощью функции, например, list.map. Монады – просто цепочка связанных вычислений!
Функциональное программирование сделает из вас лучшего разработчика. Вы наконец-то начнете писать код, который решает проблемы реального мира, вместо того, чтобы тратить время на обдумывание абстракций и шаблонов проектирования.
Возможно, вы еще не осознали себя функциональным программистом. Вы используете функции в повседневной работе? Да? Тогда вы уже функциональный программист! Осталось научиться эффективному использованию функций.
Два хороших функциональных языка с легким обучением – это Elixir и Elm. Они позволяют разработчику сосредоточиться на том, что действительно важно – на написании надежного софта и удалении сложности традиционных функциональных языков.
Альтернативы? Ваша организация использует C#? Попробуйте F# – классный функциональный язык, совместимый с .NET. Используете Java? Тогда Scala и Clojure – неплохие варианты для вас. Используете JavaScript? С правильным руководством и линтингом JavaScript превращается в хороший функциональный язык.
В прошлом веке мы не ожидали, что “новый” феномен ООП проживет столь долго. Мы думали, что ОО-конфереции типа ECOOP, OOPSLA просуществуют лет 5, а затем затеряются в мейнстриме. Но и сейчас слишком рано игнорировать ООП как часть мейнстрима. А тот факт, что научные и промышленные исследования в области объектно-ориентированного программирования еще продолжаются, подсказывает, что происходит что-то важное, чего мы еще не понимаем полностью.
Несмотря на то что, ООП имеет множество недостатков,и даже методы решения этих недостатков тоже имеют неостатки, ООП позволяет упрощать сложные вещи через моделирование, но мы все еще не овладели этим, возможно, потому, что плохо различаем существенные и несущественные сложности.
Чтобы двигаться дальше, мы должны сосредоточиться на изменчивости и на том, как ООП может содействовать изменчивости. Спустя столько лет мы все еще находимся в самом начале, пытаясь понять, что же ООП может предложить нам.
Реакция от защитников ООП вполне ожидаема. Они скажут, что эта статья полна неточностей. Некоторые могут даже начать называть имена. Они могли бы даже назвать меня junior-разработчиком без реального опыта ООП. Кто-то может сказать, что мои предположения ошибочны, а примеры бесполезны. Это не имеет значения.
Они имеют право на свое мнение. Однако их аргументы в защиту ООП обычно довольно слабы. По иронии судьбы, большинство из них никогда не программировали на настоящем функциональном языке. Как можно провести сравнение между двумя вещами, если вы никогда не пробовали их обе? Такие сравнения не очень хороши.
Закон Деметры не очень полезен — он ничего не делает для решения проблемы недетерминированности. Общее изменяемое состояние все еще является общим изменяемым состоянием, независимо от того, каким образом вы получаете доступ или изменяете его. Метод a.total() не сильно лучше a.getB().getC().total(). Это просто заметает проблему под ковер.
Предметно-ориентированное проектирование? Это полезная методология разработки, она немного помогает со сложностью. Но она по-прежнему ничего не делает для решения фундаментальной проблемы общего изменяемого состояния.
Таким образом
есдинственным решением проблемы увеличения зацеления, связности и связанности - является уменьшение количества элементов и слоев;
незабывате про понятие оптимы при принятии архитектурных решений;
Также нужно не забывать что мы разрабатываем АРХИТЕКТУРУ ПО, а не применяем кокойто неидеалный подход (идеального подхода НЕ может быть)!
Вспомним, что такое архитектура программного обеспечения - это совокупность важнейших решений об организации программной системы.
Архитектура включает:
Сайт Software Engineering Institute приводит более 150 определений этого понятия.
Архитектура ПО обычно содержит несколько видов, которые аналогичны различным типам чертежей в строительстве зданий. В онтологии, установленной ANSI / IEEE 1471—2000, виды являются экземплярами точки зрения, где точка зрения существует для описания архитектуры с точки зрения заданного множества заинтересованных лиц.
Архитектурный вид состоит из 2 компонентов:
Архитектурные виды можно поделить на 3 основных типа:
Для удовлетворения проектируемой системы различным атрибутам качества нужноразумно применять различные архитектурные шаблоны (паттерны). Каждый шаблон имеет свои задачи и свои недостатки.
Примеры архитектурных шаблонов:
Как и многое в проектировании программного обеспечения, критика ООП — непростая тема. Возможно, мне не удалось четко донести свою точку зрения и/или убедить вас. Но если вы заинтересовались, то вот еще несколько ссылок:
Недоволен чем то? велком все высказать в комментах ниже
Исследование, описанное в статье про недостатки ооп, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое недостатки ооп, альтернатива ооп, ddd, архитектура программного обеспечения и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Объектно-ориентированное программирование ООП
Часть 1 Недостатки ООП, DDD (Domain-driven design) и паттернов. Альтернатива ООП
Часть 2 Какие же есть решения проблем ООП? - Недостатки ООП, DDD
Часть 3 Имитация Сложности - Недостатки ООП, DDD (Domain-driven design) и паттернов.
Часть 4 Архитектурные шаблоны - Недостатки ООП, DDD (Domain-driven design) и паттернов.
Да если Рисунок 3 повернуть на 80 градусов против часовой стрелки то реально)) внизу грешные люди, пользователи, а вверху божества и ангелы, сверхвозможностями
Комментарии
Оставить комментарий
Объектно-ориентированное программирование ООП
Термины: Объектно-ориентированное программирование ООП