Каковы признаки плохого объектно-ориентированного дизайна?

При разработке новой системы или изучении чужого кода, какие явные признаки указывают на то, что на этапе проектирования что-то пошло не так? Есть ли подсказки, которые нужно искать в диаграммах классов и иерархиях наследования или даже в самом коде, который просто требует пересмотра дизайна, особенно на ранних этапах проекта?

В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Принцип подстановки Лискова
Принцип подстановки Лискова
Принцип подстановки Лискова (LSP) - это принцип объектно-ориентированного программирования, который гласит, что объекты суперкласса должны иметь...
31
0
11 038
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

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

Другими примерами могут быть:

  • Чрезмерное использование операторов switch
  • Производные классы, которые отменяют все

«базовый класс низводит себя до производного класса» - как всегда, существует исключение, которое возникает при использовании CRTP для реализации имитации динамического связывания в C++. Производный класс предоставляется базовому классу в качестве параметра шаблона, поэтому вы абсолютно точно знаете, что приведение допустимо.

Steve Jessop 06.12.2008 04:35

Это совсем не то, о чем я говорю. Я говорю о том, когда класс Vehicle знает, что класс Car существует. В вашем случае базовый класс по-прежнему не знает производного класса. Как вы сказали, исключения из правил. Я бы даже не назвал их правилами ... может быть, предупреждающими запахами?

Brian Genisio 06.12.2008 04:38

Что ж, у базового класс действительно есть определенные знания о производном классе, просто программист имеет дело только непосредственно с базовым шаблон класса, чего нет. C++ - чистое золото для педантов ;-)

Steve Jessop 06.12.2008 04:57

Вот несколько:

  • Круговые зависимости
  • Вы со свойством XYZ базового класса не были защищены / закрыты
  • Вы хотите, чтобы ваш язык поддерживал множественное наследование

Я не согласен с третьим. Иногда ИМ может оказаться полезным при правильном использовании.

Jason Baker 06.12.2008 05:15
Ответ принят как подходящий

Мне больше всего нравится "код пахнет".

В основном я чувствителен к вещам, которые идут вразрез с "хорошей практикой".

Вещи как:

  • Методы, которые делают вещи, отличные от того, что вы могли бы подумать по названию (например, FileExists (), который молча удаляет файлы с нулевым байтом)

  • Несколько очень длинных методов (признак объектной оболочки вокруг процедуры)

  • Повторное использование операторов switch / case для одного и того же перечисляемого члена (признак подклассов, требующих извлечения)

  • Множество переменных-членов, которые используются для обработки, а не для захвата состояния (может указывать на необходимость извлечения объекта метода)

  • Класс с множеством обязанностей (нарушение принципа единой ответственности)

  • Длинные цепочки доступа к членам (this.that - нормально, this.that.the Other - нормально, но my.very long.chain.of. member.accesses.for.a. result - хрупкий)

  • Плохое наименование классов

  • Использование слишком большого количества шаблонов проектирования на небольшом пространстве

  • Слишком много работы (переписывание функций, уже присутствующих в структуре или где-либо еще в том же проекте)

  • Плохое правописание (где угодно) и грамматика (в комментариях) или комментарии, которые просто вводят в заблуждение

Хороший список, +1. Однако мне любопытна чрезмерная плотность паттернов дизайна. Вы действительно сталкивались с этим? Я бы сказал, что это больше, когда шаблоны используются неправильно и злоупотребляют.

philant 10.12.2008 22:51

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

Конечно, если вы прочитаете мой последний вопрос, вы поймете, почему я немного измучен.

Ключевая проблема ООП заключается в том, что он не делает очевидным, что граф построения вашего объекта должен быть независимым от вашего графа вызовов.

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

Привет, я не понимаю. Не могли бы Вы привести пример? Спасибо.

user712092 29.09.2015 14:09

Все ваши объекты наследуют некоторый базовый служебный класс, чтобы вы могли вызывать свои служебные методы, не набирая так много кода.

Невозможно правильно провести модульное тестирование.

Бинго! См. Мой комментарий. Хуже всего то, что по крайней мере 90% кода, который вы когда-либо увидите, невозможно правильно протестировать.

Simon Johnson 06.12.2008 04:58

Я считаю, что это то же самое, что сказать «высокая связь».

Jay Bazuzi 06.12.2008 19:02

Я бы сказал, что правило номер один плохого объектно-ориентированного дизайна (и да, я был виноват в этом слишком много раз!):

  • Классы, нарушающие Одинокий Принцип ответственности (SRP) и выполнить слишком много действий

С последующим:

  • Слишком много наследования вместо состав, т.е. классы, которые происходят от подтипа чисто так они получают функциональность бесплатно. Отдавайте предпочтение композиции перед наследованием.

Правда. Хотел бы включить еще парочку: «Программирование интерфейса» и «Разделение проблем».

Adeel Ansari 06.12.2008 06:27

В этом вопросе делается предположение, что объектно-ориентированный означает хороший дизайн. Бывают случаи, когда другой подход более уместен.

+1, хотя я бы сказал «другой подход», нежели «функциональный подход». Существует очень мало подходов к проектированию, которые не имеют хотя бы небольшого класса проблем, для которых они являются наиболее подходящим выбором.

Dave Sherohman 06.12.2008 09:17

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

Пример. Допустим, у вас есть симуляция города. Если у объекта Person есть свойство NearestPostOffice, у вас, вероятно, проблемы.

Найдите программиста, имеющего опыт работы с кодовой базой. Попросите их объяснить, как что-то работает.

Если они говорят, что «эта функция вызывает эту функцию», их код является процедурным.

Если они говорят «этот класс взаимодействует с этим классом», их код является объектно-ориентированным.

Что делать, если оба класса синглтоны =)

FlySwat 06.12.2008 19:10

Я имею в виду «если они описывают свой код как отношения между классами, это объектно-ориентированный подход; как отношения между методами - процедурные; как отношения между операторами - это спагетти».

Jay Bazuzi 31.12.2008 22:14

В длинном методе разделы окружены #region / #endregion - почти в каждом случае, который я видел, этот код можно было легко извлечь в новый метод ИЛИ нужно было каким-то образом отрефакторить.

Чрезмерно сложные деревья наследования, в которых подклассы делают очень разные вещи и только косвенно связаны друг с другом.

Нарушение DRY - подклассы, каждый из которых переопределяет базовый метод почти одинаково, с небольшими вариациями. Пример: недавно я работал над некоторым кодом, в котором каждый подкласс переопределяет базовый метод, а единственная разница заключается в проверке типа («x is ThisType» против «x is ThatType»). Я реализовал в базе метод, который взял общий тип T, который затем использовал в тесте. Затем каждый дочерний элемент может вызвать базовую реализацию, передав тип, который он хочет проверить. Это отсекло около 30 строк кода от каждого из 8 различных дочерних классов.

Анти-паттерны

Антипаттерны проектирования программного обеспечения

  • Инверсия абстракции: не раскрытие реализованной функциональности, необходимой пользователям, чтобы они повторно реализовали ее, используя функции более высокого уровня.
  • Неоднозначная точка зрения: представление модели (обычно OOAD) без указания ее точки зрения.
  • Большой комок грязи: система без узнаваемой структуры
  • Blob: обобщение объекта God из объектно-ориентированного дизайна
  • Газовый завод: излишне сложная конструкция
  • Входной кладж: неспособность указать и реализовать обработку возможно недопустимого ввода
  • Раздутие интерфейса: сделать интерфейс настолько мощным, что его будет чрезвычайно сложно реализовать.
  • Волшебная кнопка: кодирование логики реализации непосредственно в коде интерфейса, без использования абстракции.
  • Опасность гонки: неспособность увидеть последствия разного порядка событий.
  • Железнодорожное решение: предлагаемое решение, которое, хотя и является неудовлетворительным, является единственным доступным из-за плохого предвидения и негибкости в других областях проектирования.
  • Повторное соединение: введение ненужной зависимости объекта
  • Система дымовых труб: сложно обслуживаемая совокупность плохо связанных компонентов.
  • Staralized schema: Схема базы данных, содержащая таблицы двойного назначения для нормализованного использования и использования витрины данных.

Антипаттерны объектно-ориентированного дизайна

  • Модель анемичного домена: использование модели предметной области без какой-либо бизнес-логики, которая не является ООП, потому что каждый объект должен иметь как атрибуты, так и поведение.
  • BaseBean: наследование функциональности от служебного класса, а не делегирование ему
  • Вызов super: Требование подклассов для вызова переопределенного метода суперкласса
  • Задача круг-эллипс: выделение подтипов переменных-типов на основе подтипов-значений
  • Ошибка пустого подкласса: создание класса, который не проходит «Тест пустого подкласса», поскольку ведет себя иначе, чем класс, производный от него, без изменений.
  • Божественный объект: сосредоточение слишком большого количества функций в одной части дизайна (классе).
  • Помойка объектов: повторное использование объектов, состояние которых не соответствует (возможно, неявному) контракту, для повторного использования.
  • Объектная оргия: неспособность должным образом инкапсулировать объекты, разрешающие неограниченный доступ к их внутренним компонентам.
  • Полтергейсты: объекты, единственной целью которых является передача информации другому объекту.
  • Последовательная связь: класс, который требует, чтобы его методы вызывались в определенном порядке.
  • Синглтонит: чрезмерное использование одноэлементного паттерна
  • Еще один бесполезный слой: добавление ненужных слоев в программу, библиотеку или фреймворк. Это стало популярным после выхода первой книги по шаблонам программирования.
  • Проблема йо-йо: структура (например, наследования), которую трудно понять из-за чрезмерной фрагментации.

В чем особенность полтергейста? Извлечение информации определенным образом кажется задачей не для доступа, а для держателя этой информации.

Thomas E 01.07.2017 16:06

@ThomasE Инициализация и выпуск недолговечных объектов - это пустая трата ресурсов и признак плохого объектно-ориентированного проектирования, см. en.wikipedia.org/wiki/Poltergeist_(computer_programming)

philant 04.07.2017 09:41

Дублированный код = код, который делает то же самое ... По моему опыту, это самая большая ошибка, которая может произойти в объектно-ориентированном дизайне.

Дублирование кода - это неплохо, если он выполняет одно и то же, но по разным причинам. Чрезмерное рвение в применении DRY может привести к ненужному объединению функций между классами или доменами. Тот факт, что A выполняет f, а B выполняет f, а Af и Bf делают то же самое, не означает, что вам нужно создать Ff, который принимает A или B, просто чтобы вы не дублировали код ... особенно позже, когда вы добавляете Df и когда нужно реорганизовать Bf.

Justin Ohms 31.10.2017 02:50

Объекты хороши, создать миллиард из них - плохой объектно-ориентированный дизайн.

Ниже приведены наиболее характерные черты плохого дизайна:

  1. Жесткость
  2. Хрупкость
  3. Неподвижность

Взгляните на Принцип инверсии зависимостей

Когда у вас есть не просто класс Money \ Amount, а класс TrainerPrice, класс TablePrice, класс AddTablePriceAction и так далее.

Разработка, управляемая IDE, или разработка с автозаполнением. В сочетании с очень строгим набором текста это идеальный шторм.

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

Это создает возможность генерировать почти бесконечное количество кода, что отлично, если у вас есть оплачиваемые часы. Когда переменные, контексты, свойства и состояния превращаются в гипер-явные и чрезмерно определенные классы, это создает экспоненциальный катаклизм, поскольку рано или поздно эти вещи умножаются. Думайте об этом как о [a, b] x [x, y]. Это может быть дополнительно усугублено попыткой создать полностью свободный интерфейс, а также придерживаться как можно большего количества шаблонов проектирования.

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

В ООП вы можете увидеть формы повторения, которые трудно обнаружить автоматически, которые можно превратить в более динамичный код с помощью карт. Хотя такие языки менее динамичны, вы можете добиться динамических функций с некоторыми дополнительными усилиями.

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

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

То, что происходит без дженериков, - это когда у вас должно быть A для X = [Q, W, E] и Y = [R, T, Y], вместо этого вы видите [AQR, AQT, AQY, AWR, AWT, AWY, AER, AET, АЭЙ]. Часто это происходит из-за страха, использования без типа или передачи типа в качестве переменной для потери поддержки IDE.

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

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

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

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

Другие вопросы по теме