Уважаемый Валерий!
Я пытаюсь разбить на субэтапы, четвертый этап "Разработка архитектуры", у меня получились такие подэтапы:
1. На основании документов описывающих систему выделить сущности
2. На основании сущностей выделить классы
На основе 11-летнего опыта разработки программ могу сказать, что все-таки правильнее начинать проектирование не с выявления сущностей, а с формулировки функций, которые должна выполнять программа. Такие функции я называю пользовательскими или бизнес-функциями, потому что они решают задачи пользователя (или бизнеса). Как правило, одна бизнес-функция может быть реализована при помощи нескольких классов и/или функций, написанных на языке программирования.
В процессе проектирования важна концентрация на функциях именно потому, что разрабатываемая программа должна решать определенные бизнес-задачи. Нередко – даже опытные! – программисты забывают об этом, и, в результате, получается нечто монстроподобное, что трудно сопровождать и что не решает необходимые задачи.
Как правило, когда обращаешь внимание программистов на то, что нужно проектировать программу для выполнения бизнес-функций, сталкиваешься с двумя эффектами непонимания.
Эффект № 1. «Очевидный факт».
Программисты понимают, что программа разрабатывается для решения определенных бизнес-задач Клиента, и считают, что им сообщают очевидный факт.
Между тем, не смотря на понимание этой, вроде бы очевидной, вещи, в процессе проектирования бизнес-задачи не берутся в расчет. Обычно процесс проектирования начинается с одной из четырех вещей:
1. с поиска объектов предметной области и проб их на роль классов;
2. с поиска какого-нибудь паттерна проектирования;
3. с поиска уже готового кода (например, в виде бесплатной библиотеки), который можно просто вставить в программу или который можно «передрать»;
4. с выявления произвольных атрибутов объектов предметной области и попытки сгруппировать эти атрибуты в иерархию классов – когда атрибуты, присутствующие у всех объектов, выносятся в базовые, а все остальные – в производные классы.
Общая ошибка, допускаемая разработчиками – это «проектирование от объекта» (или «выведение класса из объекта»). Наиболее детально она описана в этой статье.
Эффект № 2. «Вы нам рассказываете про структурное программирование…»
Увидев или услышав слово «функция», программисты считают, что речь идет о структурном программировании. А поскольку объектно-ориентированное программирование позволяет лучше бороться со сложностью, чем структурное программирование, совет «проектировать от функций» воспринимается ими, как попытка вернуться в прошлое.
Между тем, речь идет не о функциях языка программирования, а о бизнес-функциях.
Как же все-таки проектировать программу? В следующей части сообщения постараюсь описать ряд шагов, которые на практике показали свою эффективность. Это – не полный алгоритм, а лишь некоторые шаги…
Шаг 1. Сформулируйте назначение программы.
Говоря языком ТРИЗ, сформулируйте главную полезную функцию (ГПФ) и вспомогательные полезные функции (ВПФ) программы.
ПРИМЕР. Необходимо разработать GPS-навигатор для автомобильного компьютера (карпьютера). Основная задача GPS-навигатора – автоматическая прокладка маршрута в заданную точку.
Шаг 2. Сформулируйте операции, необходимые для достижения ГПФ.
Для выполнения главной полезной функции, программа должна выполнить ряд операций. На этом шаге нужно сформулировать эти операции. Как правило, на макро-уровне, без детализации.
ПРИМЕР. Для прокладки маршрута GPS-навигатор должен:
1. Получить текущее местоположение пользователя у GPS-приемника.
2. Считать карту местности.
3. Проложить маршрут в заданную точку.
4. Отобразить карту местности и проложенный маршрут.
5. Определить положение пользователя на маршруте и выдать указание по движению.
Шаг 3. Постройте структурную модель программы (в первом приближении).
Для этой цели каждую макро-операцию можно вынести в отдельный модуль. Структурная модель программы представляется в виде набора модулей и связей между ними.
ПРИМЕР. Структурная модель GPS-навигатора в первом приближении:
Модуль | Назначение | Использует |
Reader | Считывает картографические данные. | |
Router | Прокладывает маршрут. | Reader |
Rasterizer | Отображает карту местности и маршрут. | Reader, Router |
Navigator | Определяет положение пользователя на маршруте и выдает указание по движению. | Reader, Router |
Location Module | Получает текущие координаты у GPS-приемника. | |
Шаг 4. Упорядочите действия из шага 2.
Расположите их в порядке следования. Возможно, при выполнении этого шага появится необходимость в добавлении к уже известному списку новых действий.
ПРИМЕР. Для GPS-навигатора последовательность операций может быть такова:
1. Получить текущие координаты.
2. Загрузить карту местности.
3. Выполнить снэппинг (прикрепить точку к ближайшей дороге).
4. Построить маршрут.
5. Определить положение на маршруте.
6. Если пользователь находится на маршруте, то получить указание по дальнейшему движению.
7. Если пользователь находится не на маршруте, то пересчитать маршрут.
8. Отобразить карту, маршрут и местоположение пользователя.
9. Отобразить указания по движению.
Как видите, при упорядочении в список добавились дополнительные действия.
Шаг 5. Постройте модель выполнения программы.
Данный шаг позволяет распределить операции по потокам. В таком распределении возникает необходимость, когда выполнение операции может занять значительное время.
В самом простейшем случае операции из списка, созданного на шаге 4, надо выполнять последовательно, друг за другом. Но если операция занимает значительное время, ее можно вынести в отдельный поток.
ПРИМЕР. По прогнозам более-менее длительное время занимают такие операции:
1. Построение и пересчет маршрута.
2. Определение указания по дальнейшему движению.
3. Отображение карты местности (формирование растрового изображения).
Загрузка необходимых данных из карты местности выполняется более-менее быстро за счет кэширования и оптимальной структуры базы данных с картографическими данными.
Перечисленные медленные операции можно вынести в отдельные потоки. Кроме того, в отдельный поток можно вынести и операцию получения текущих координат у GPS-приемника. Это лучше сделать, чтобы не нагружать главный поток программы чтением данных из COM-порта. Такая операция должна выполняться периодически (например, раз в секунду). И в главном потоке будет трудно за этим следить, поскольку он должен выполнять еще массу других операций.
В результате получился такой список потоков:
Поток | Назначение |
Location Thread | Получение координат у GPS-устройства. |
Routing Thread | Расчет и пересчет маршрута. |
Navigation Thread | Определение указания по дальнейшему движению. |
Rasterizing Thread | Отображение карты, маршрута и местоположения пользователя. |
Main Thread | Загрузка карты местности. Выполнение снэппинга (прикрепление точки к ближайшей дороге). Определение положения на маршруте. Отображение указания по движению. |
Потоки общаются друг с другом только посредством сообщений. Каждый поток работает только со своей копией данных. Таким образом, мы перешли от синхронной к асинхронной модели выполнения необходимых операций.
Далее проектирование идет в двух направлениях:
1. Детализация интерфейсов модулей и проектирование модулей.
2. Детализация интерфейсов потоков, которые обмениваются друг с другом посредством асинхронных сообщений.
С уважением,