Pull to refresh

Антипаттерны проектирования: Functional Decomposition

Reading time 5 min
Views 46K
Original author: Alexander Shvets
Наименование: Functional Decomposition (функциональная декомпозиция)
Другие наименования: No Object-Oriented AntiPattern «No OO»
Масштаб: приложение
Рефакторинг: объектно-ориентированный реинжиниринг

Функциональная декомпозиция — хорошая практика процедурного программирования, так как она позволяет разделить приложение на отдельные функциональные модули.

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

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

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

С антипаттерном зачастую можно столкнуться, когда С-программист начинает писать на C++, или пытается подключить CORBA интерфейсы, а также пытается использовать какую-либо объектную технологию. В долгосрочной перспективе порой бывает проще нанять программистов с объектно-ориентированным мышлением, нежели тратить время на обучение объектно-ориентированным технологиям.

Признаки появления и последствия антипаттерна


  • Классы с именами — «функциями», например CalculateInterest или DisplayTable.
  • Все атрибуты класса являются приватными и используются только внутри класса.
  • Классы с единственным действием, аналогичным процедурной функции.
  • Вырожденная архитектура, которая полностью не соответствует объектно-ориентированной концепции.
  • Неэффективное использование объектно-ориентированных принципов, таких как наследование и полиморфизм. В результате ПО может быть чрезвычайно дорогим в сопровождении.
  • Невозможность ясно задокументировать (иногда даже описать) как система работает. Модель классов не имеет никакого смыслового значения для понимания архитектуры системы.
  • Сложность (иногда невозможность) последующего повторного использования кода.
  • Сложность тестирования ПО.

Типичные причины


  • Отсутствие понимания объектно-ориентированного подхода — обычная практика, когда разработчики переходят на объектно-ориентированный язык программирования с процедурного. Так как переход к ООП требует смены парадигм разработки архитектуры, проектирования и реализации, то полный переход отдельной компании к объектно-ориентированному подходу может занять до 3 лет.
  • Отсутствие контроля за соблюдением принятой архитектуры. Если программисты не знакомы с ООП, то уже не имеет значения, насколько хорошо была спроектирована архитектура: они просто не поймут, что нужно сделать. И без соответствующего внимания к соблюдению принципов, заложенных в архитектуре, они найдут способ, как уклониться от архитектуры, используя хорошо знакомые методы процедурного программирования.
  • Иногда автор спецификаций/описаний требований не достаточно знаком с объектно-ориентированными системами. Если на этапе написания спецификаций или анализа требований делаются предположения об архитектуре будущей системы, то зачастую это приводит к таким антипаттернам, как «функциональная декомпозиция».

Исключения


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

Рефакторинг


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

Далее создайте модель проектирования, которая включает основные части существующей системы. Фокусируйтесь не на улучшении модели а на создании основы для описания как можно большей части системы.

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

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

Исследуйте архитектуру и найдите схожие подсистемы — это кандидаты для повторного использования. В рамках сопровождения программы выполняйте рефакторинг кодовой базы для повторного использования кода в схожих подсистемах (см. решение антипаттерна Spaghetti Code («спагетти-код») для детального описания рефакторинга).

Пример


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

Простой пример на рисунке ниже демонстрирует процедурную версию сценария расчета кредита для клиента:

Сценарий расчета:
  1. Добавить нового клиента.
  2. Обновить адрес клиента.
  3. Рассчитать кредит для покупателя.
  4. Рассчитать проценты по кредиту
  5. Рассчитать график погашения кредита.
  6. Сохранить новый график платежей.

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

Связанные решения


Если на разработку системы уже затрачено значительно усилий, то вы можете применить подход, похожий на альтернативное решение проблемы антипаттерна Blob.
Вместо рефакторинга снизу-вверх сразу всей иерархии классов вы сможете расширить класс «главная подпрограмма» до класса «координатор», который будет управлять всем функционалом системы.
Функциональные классы могут затем быть трансформированы в квази-объектно-ориентированные классы путем их комбинирования а также переноса части их функционала в класс «координатор». В результате должна получиться более работоспособная иерархия классов.
Tags:
Hubs:
+2
Comments 106
Comments Comments 106

Articles