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

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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