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

Назва: 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.
Замість рефакторінгу знизу-вгору відразу всієї ієрархії класів ви зможете розширити клас «головна підпрограма» до класу «координатор», який буде керувати всім функціоналом системи.
Функціональні класи можуть бути трансформовані в квазі-об'єктно-орієнтовані класи шляхом їх комбінування а також перенесення частини їх функціоналу в клас «координатор». У результаті повинна вийти більш працездатна ієрархія класів.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.