понедельник, 5 декабря 2011 г.

О том, как путают функциональный подход к проектированию с процедурным программированием


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


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

Какие же недостатки приписывают функциональному подходу?
Наиболее часто встречаются такие:

  1. При его использовании задача системы размывается в коде.
  2. Он создаёт нерасширяемые системы.
  3. Он плохо подходит для разработки больших программ.
  4. Он является "шщагом назад" от объектно-ориентированного подхода, потому что предполагает отказ от объединения данных и функций в одной сущности.

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

Следуют различать различные подходы к программированию – структурное программирование, процедурное программирование, объектно-ориентированное программирование – и подходы к проектированию программ – OOD, функциональный подход.

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

Подход
Единица структурирования кода
Структурное программирование
Оператор, цикл, ветвление.
Процедурное программирование
Процедура.
Объектно-ориентированное программирование
Объект.

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

Основное различие между OOD и функциональным подходом заключается в том, что они ставят во главу угла. Классический OOD, например, изложенный в книгах Буча и Рамбо, предполагает, что проектировщик должен сконцентрироваться на ключевых абстракциях предметной области – таких, как: датчик, время, температура, банкомат, клиент и т.д. Предполагается, что, получив ключевые абстракции и установив между ними отношения, их можно перенести в код и, таким образом, создать программу или, по крайней мере, получить её архитектуру.

Такая методология исходит из предположения, что всё уже изобретено до нас, и надо только подсмотреть и сделать по аналогии. На мой взгляд, такой подход сродни первобытному мышлению. Он позволяет сконцентрироваться на внешних проявлениях без понимания глубинных связей. В одной из своих статей я приводил пример о том, что попытки копирования птиц не привели к изобретению самолёта. Конечно, можно создать и навесить на человека крылья. Но это не значит, что он полетит.

При этом, с точки зрения OOD древние изобретатели всё делали правильно – создавали крылья, заставляли человека ими махать. Внешние проявления были скопированы. Однако результат – в виде создания машины, которая может летать – был нулевой.

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

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

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

"Классы-данные предназначены для временного или постоянного хранения групп данных, а также – для передачи этих данных от сервиса к сервису. Можно сказать, что классы-сервисы – это обработчики, а классы-данные – это коммуникации между этими обработчиками. У "железячников" есть хороший термин – шина данных. Так вот, проектирование классов-данных сродни прокладке шины данных, соединяющей одни функциональный подсистемы с другими".

Примеры классов-данных:

  • сообщение,
  • строка (std::string),
  • массив (std::vector),
  • дата и время
  • и т.д.

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

Например, в библиотеке STL существуют контейнеры (list, vector, map), итераторы и алгоритмы. Алгоритмы работают с контейнерами через итераторы. Но наличие алгоритмов не означает, что у классов-контейнеров отсутствуют методы.

6 комментариев:

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

    ОтветитьУдалить
  2. Борис, функциональный подход подробно изложен на конкретных примерах (см. кейсы 1 и 2 сна боковой панели).

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

    ОтветитьУдалить
  4. Кирилл, спасибо. Статья интересная. Но все-таки я из нее не понял, что есть функциональный подход. Я продел небольшой поиск и разместил его здесь: http://insectfinder.blogspot.com/2011/12/blog-post_05.html. Мне кажется противопоставление ф.подхода и п.программирования неуместно. По-моему процедурное программирование логично вытекает из функционального подхода: описание элемента программы(системы) как черного ящика, устройство которого неизвестно, а известно лишь его внешнее проявление и закон функционирования - отсюда собственно функциональный подход. При подходе к разработке программы "сверху-вниз", вся программа определяется как черный ящик, имеющий назначение и происходит последовательная декомпозиция. Получаем иерархию черных ящиков с названиями функциями - это вертикальные связи, потом формируются горизонтальные связи - на одном уровне иерархии, т.е. по сути процессный подход к описанию программы. Все это удачным образом ложится на структурно-ориентированные == процедурные языки (спасибо Дейкстре).
    Функциональный подход сильно развит в психологии для описания поведения личности.

    Разделение на классы-данные и классы-сервисы - это вообще визитная карточка RUP - процессная методология базирующаяся на UML. Они ее дополняют еще и граничными классами, базируюсь на MVC.

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

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

    Спасибо, что Вы находите силы, энергию и возможность делиться им

    ОтветитьУдалить
  5. Эдуард, спасибо за ссылку - прочитал. Интересно. Но опять-таки автором используется описательная модель модель. Нет моделей, которые можно было бы применить для решения практических задач.

    Общий взгляд на функциональный подход я привел в следующих сообщениях:
    http://askofen.blogspot.com/2010/10/blog-post_31.html
    http://askofen.blogspot.com/2010/10/blog-post_14.html

    Если говорить кратко, то отличия таковы:
    1) Определяем назначение системы.
    2) Определяем системный уровень решения задачи.
    3) Описываем flow (как оно выглядит со стороны) на данном системном уровне. Т.е. задумываемся о том, как будет реализована полезная функция.
    4) Перестраиваем flow - как оно реально будет работать (здесь показаны некоторые приемы и критерии - http://askofen.blogspot.com/2011/10/blog-post.html).
    5) Flow даёт нам список функций - строим функциональную модель.
    6) Группируем функции в блоки (http://askofen.blogspot.com/2011/10/1.html).
    7) Сначала строим функциональную модель класса - потом думаем над реализацией.

    ОтветитьУдалить
  6. Кирилл, на здоровье. Рад, если это было полезно. А что значит описательная модель и модель для практических задач? Чем одно отличается от другого?

    Что касается списка рекомендаций. Они, конечно, весьма интересны, т.к. отражают личный опыт и успешность применения на практике.

    Хотел бы прояснить некоторые моменты для себя и, возможно, других.
    - что такое системный уровень решения задачи,
    - 3 и 4 пункты не уловил серьезной разницы. Добавлю собственные наблюдения, большинство практических рекомендаций касаются выстраивания процесса, т.е. некоторого потока событий, работ. Это мы видим и в стандартных блок-схемах, и в IDEF3 (а в нем существуют два подхода), и в DFD, и в use cases, которые предлагаются уже как универсальный инструмент описания функциональных требований, и в ARIS, и в BPMN и т.д. Однако помимо связей предшествования, между функциями могут быть установлены более сложные зависимости или отношения
    - группировка функций по блокам - очевидно это задача синтеза, которая идет вслед за анализом. Но анализ часто ассоциирован с описательными моделями, а синтез с конструктивными. в западной практике мы находим такой важный пункт как allocation (распределение) функций...
    - а что Вы называете функциональной моделью класса? Список его обязанностей? Иерархия его обязанностей?

    ОтветитьУдалить