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


Одна вещь, которую я ненавижу видеть, - это преобразование базового класса в производный класс. Когда вы это видите, вы понимаете, что у вас проблемы.
Другими примерами могут быть:
Это совсем не то, о чем я говорю. Я говорю о том, когда класс Vehicle знает, что класс Car существует. В вашем случае базовый класс по-прежнему не знает производного класса. Как вы сказали, исключения из правил. Я бы даже не назвал их правилами ... может быть, предупреждающими запахами?
Что ж, у базового класс действительно есть определенные знания о производном классе, просто программист имеет дело только непосредственно с базовым шаблон класса, чего нет. C++ - чистое золото для педантов ;-)
Вот несколько:
Я не согласен с третьим. Иногда ИМ может оказаться полезным при правильном использовании.
Мне больше всего нравится "код пахнет".
В основном я чувствителен к вещам, которые идут вразрез с "хорошей практикой".
Вещи как:
Методы, которые делают вещи, отличные от того, что вы могли бы подумать по названию (например, FileExists (), который молча удаляет файлы с нулевым байтом)
Несколько очень длинных методов (признак объектной оболочки вокруг процедуры)
Повторное использование операторов switch / case для одного и того же перечисляемого члена (признак подклассов, требующих извлечения)
Множество переменных-членов, которые используются для обработки, а не для захвата состояния (может указывать на необходимость извлечения объекта метода)
Класс с множеством обязанностей (нарушение принципа единой ответственности)
Длинные цепочки доступа к членам (this.that - нормально, this.that.the Other - нормально, но my.very long.chain.of. member.accesses.for.a. result - хрупкий)
Плохое наименование классов
Использование слишком большого количества шаблонов проектирования на небольшом пространстве
Слишком много работы (переписывание функций, уже присутствующих в структуре или где-либо еще в том же проекте)
Плохое правописание (где угодно) и грамматика (в комментариях) или комментарии, которые просто вводят в заблуждение
Хороший список, +1. Однако мне любопытна чрезмерная плотность паттернов дизайна. Вы действительно сталкивались с этим? Я бы сказал, что это больше, когда шаблоны используются неправильно и злоупотребляют.
На мой взгляд, весь код ООП вырождается в процедурный код в течение достаточно длительного промежутка времени.
Конечно, если вы прочитаете мой последний вопрос, вы поймете, почему я немного измучен.
Ключевая проблема ООП заключается в том, что он не делает очевидным, что граф построения вашего объекта должен быть независимым от вашего графа вызовов.
Как только вы исправите эту проблему, ООП действительно обретет смысл. Проблема в том, что очень немногие команды знают об этом шаблоне проектирования.
Привет, я не понимаю. Не могли бы Вы привести пример? Спасибо.
Все ваши объекты наследуют некоторый базовый служебный класс, чтобы вы могли вызывать свои служебные методы, не набирая так много кода.
Невозможно правильно провести модульное тестирование.
Бинго! См. Мой комментарий. Хуже всего то, что по крайней мере 90% кода, который вы когда-либо увидите, невозможно правильно протестировать.
Я считаю, что это то же самое, что сказать «высокая связь».
Я бы сказал, что правило номер один плохого объектно-ориентированного дизайна (и да, я был виноват в этом слишком много раз!):
С последующим:
Правда. Хотел бы включить еще парочку: «Программирование интерфейса» и «Разделение проблем».
В этом вопросе делается предположение, что объектно-ориентированный означает хороший дизайн. Бывают случаи, когда другой подход более уместен.
+1, хотя я бы сказал «другой подход», нежели «функциональный подход». Существует очень мало подходов к проектированию, которые не имеют хотя бы небольшого класса проблем, для которых они являются наиболее подходящим выбором.
Один запах - это объекты, имеющие жесткие зависимости / ссылки на другие объекты, которые не являются частью их естественной иерархии объектов или композиции, связанной с предметной областью.
Пример. Допустим, у вас есть симуляция города. Если у объекта Person есть свойство NearestPostOffice, у вас, вероятно, проблемы.
Найдите программиста, имеющего опыт работы с кодовой базой. Попросите их объяснить, как что-то работает.
Если они говорят, что «эта функция вызывает эту функцию», их код является процедурным.
Если они говорят «этот класс взаимодействует с этим классом», их код является объектно-ориентированным.
Что делать, если оба класса синглтоны =)
Я имею в виду «если они описывают свой код как отношения между классами, это объектно-ориентированный подход; как отношения между методами - процедурные; как отношения между операторами - это спагетти».
В длинном методе разделы окружены #region / #endregion - почти в каждом случае, который я видел, этот код можно было легко извлечь в новый метод ИЛИ нужно было каким-то образом отрефакторить.
Чрезмерно сложные деревья наследования, в которых подклассы делают очень разные вещи и только косвенно связаны друг с другом.
Нарушение DRY - подклассы, каждый из которых переопределяет базовый метод почти одинаково, с небольшими вариациями. Пример: недавно я работал над некоторым кодом, в котором каждый подкласс переопределяет базовый метод, а единственная разница заключается в проверке типа («x is ThisType» против «x is ThatType»). Я реализовал в базе метод, который взял общий тип T, который затем использовал в тесте. Затем каждый дочерний элемент может вызвать базовую реализацию, передав тип, который он хочет проверить. Это отсекло около 30 строк кода от каждого из 8 различных дочерних классов.
Антипаттерны проектирования программного обеспечения
Антипаттерны объектно-ориентированного дизайна
В чем особенность полтергейста? Извлечение информации определенным образом кажется задачей не для доступа, а для держателя этой информации.
@ThomasE Инициализация и выпуск недолговечных объектов - это пустая трата ресурсов и признак плохого объектно-ориентированного проектирования, см. en.wikipedia.org/wiki/Poltergeist_(computer_programming)
Дублированный код = код, который делает то же самое ... По моему опыту, это самая большая ошибка, которая может произойти в объектно-ориентированном дизайне.
Дублирование кода - это неплохо, если он выполняет одно и то же, но по разным причинам. Чрезмерное рвение в применении DRY может привести к ненужному объединению функций между классами или доменами. Тот факт, что A выполняет f, а B выполняет f, а Af и Bf делают то же самое, не означает, что вам нужно создать Ff, который принимает A или B, просто чтобы вы не дублировали код ... особенно позже, когда вы добавляете Df и когда нужно реорганизовать Bf.
Объекты хороши, создать миллиард из них - плохой объектно-ориентированный дизайн.
Ниже приведены наиболее характерные черты плохого дизайна:
Взгляните на Принцип инверсии зависимостей
Когда у вас есть не просто класс 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 или других инструментов, проектирование для поддержки модульных тестов и т. д. - все это должно вызывать тревогу, потому что это значительное отклонение от разработки рабочего программного обеспечения для решать заданную категорию проблем или заданный набор функций.
«базовый класс низводит себя до производного класса» - как всегда, существует исключение, которое возникает при использовании CRTP для реализации имитации динамического связывания в C++. Производный класс предоставляется базовому классу в качестве параметра шаблона, поэтому вы абсолютно точно знаете, что приведение допустимо.