Конструирование программ. Структурное программирование
Одной из основных задач проектирования является декомпозиция системы, т.е. разделение системы в целом на совокупность взаимосвязанных элементов. При декомпозиции последовательно меняется уровень детализации системы.
По направлению процесса декомпозиции принято выделять три основных метода:
- нисходящее проектирование;
- восходящее проектирование;
- расширение ядра (проектирование от центра).
При нисходящем проектировании (проектировании «сверху вниз») проектирование начинается с верхнего уровня. Система иерархически разбивается на подсистемы и т.д., вплоть до компонентов нижнего уровня. Это метод общего назначения, с его помощью можно проектировать любую систему.
При восходящем проектировании (проектировании «снизу вверх») сразу выделяются необходимые компоненты нижнего уровня реализации, на основе которых строятся подсистемы уровня выше и т.д. до верхнего уровня. Этот метод используют для относительно небольших систем, как правило, инструментального назначения. В таких системах обычно четко прослеживается большое количество инструментальных компонентов нижнего уровня, а на верхнем уровне, практически, реализуется только интерфейс к ним.
При проектировании методом расширения ядра (проектировании «от центра») выделяется базовый процесс или объект (ядро), на котором основана вся система. Проектирование ведется одновременно «вниз» (для реализации ядра на низком уровне) и «вверх» (для использования ядра на верхнем уровне). Примером может служить реализация реляционной системы управления базами данных (СУБД). В ней четко прослеживается базовое понятие записи. Проектирование «вниз» нацелено на реализацию понятия записи, типов полей, операций над полями и т.д. Проектирование «вверх» предназначено для реализации собственно СУБД, т.е. таблиц, запросов и т.д.
Наиболее часто используется смешанный подход к проектированию, при котором основное проектирование ведется сверху вниз, однако используются элементы проектирования от центра и снизу вверх.
Проектирование средних и тем более крупных систем есть столь сложный и длительный процесс, что его стремятся также вести параллельно несколькими группами проектировщиков. При этом декомпозиция системы органично приводит к декомпозиции самого процесса проектирования.
Объектно-ориентированное программирование
В настоящее время наиболее привлекательные для программистов языки базируются на так называемой объектной модели, которая имеет четыре главных элемента: абстрагирование, инкапсуляция, модульность, иерархия.
- Абстрагирование — выделение абстракций (abstraction), под которыми понимаются существенные характеристики объекта, которые отличают его от всех других объектов и четко определяют его концептуальные границы для наблюдателя.
- Инкапсуляция (encapsulation) — разделение элементов абстракции, которые образуют ее структуру и поведение.
- Модульность (modularity) — разделение системы на модули (module), под которыми понимаются единицы кода, служащие блоками физической структуры системы.
- Иерархия (hierarchy) — подчинение или упорядочение абстракций. Две типичных иерархии в сложной системе — иерархия наследования «общее/частное», присущая, обычно, типам (классам) и иерархия агрегирования «целое/часть», присущая, обычно, элементам (объектам). Иерархия присуща также модулям и другим частям системы.
Наиболее явно эти черты присутствуют в языках, реализующих концепции объектно-ориентированного программирования. Под последним понимается методология реализации, при которой программа организуется как совокупность сотрудничающих объектов, каждый из которых является экземпляром какого-либо класса, а классы образуют иерархию наследования. При этом классы обычно статичны, а объекты очень динамичны, что поощряется динамическим связыванием и полиморфизмом.
Для ООП характерны понятия наследования и полиморфизма.
- Наследование (inheritance) — отношение между классами, при котором класс использует структуру или поведение другого (одиночное наследование) или других (множественное наследование). Наследование вводит иерархию «общее/частное».
- Полиморфизм (polymorphism) — положение теории типов, согласно которому имена (например, переменных) могут обозначать объекты разных (но имеющих общего родителя) классов. Следовательно, любой объект (метод), обозначаемым полиморфным именем, может по-своему реагировать на некий общий набор операций (аргументов).
Модульное программирование
Понятие модуля является в значительной степени аксиоматичным и трудно поддается формальному определению, общему для всех языков. Обычно под модулем понимают компонент программной системы, оформляемый, как правило, в виде отдельного файла с целью раздельной компиляции. Модуль в программных проектах также является единицей описания и администрирования и, как правило, кодируется на этапе программирования одним программистом. Модульное программирование является воплощением в процессе разработки программ обоих общих методов борьбы со сложностью и обеспечение независимости компонент системы, и использование иерархических структур. Для воплощения первого метода формулируются определенные требования, которым должен удовлетворять программный модуль, т.е. выявляются основные характеристики «хорошего» программного модуля. Для воплощения второго метода используют древовидные модульные структуры программ (включая деревья со сросшимися ветвями).
Оценка программного модуля осуществляется на основании следующих конструктивных характеристик: размер модуля, прочность модуля, сцепление с другими модулями, рутинность модуля.
Размер модуля измеряется числом содержащихся в нем операторов или строк. Модуль не должен быть слишком маленьким или слишком большим. Маленькие модули приводят к громоздкой модульной структуре программы и могут не окупать накладных расходов, связанных с их оформлением. Большие модули неудобны для изучения и изменений, они могут существенно увеличить суммарное время повторных трансляций программы при отладке программы. Обычно рекомендуются программные модули размером от нескольких десятков до нескольких сотен операторов.
Прочность модуля — это мера его внутренних связей. Чем выше прочность модуля, тем больше связей он может спрятать от внешней по отношению к нему части программы и, следовательно, тем больший вклад в упрощение программы он может внести. Самой слабой степенью прочности обладает модуль, прочный по совпадению. Это такой модуль, между элементами которого нет осмысленных связей. Такой модуль может быть выделен, например, при обнаружении в разных местах программы повторения одной и той же последовательности операторов, которая и оформляется в отдельный модуль. Функционально прочный модуль — это модуль, выполняющий (реализующий) одну какую-либо определенную функцию. Информационно прочный модуль — это модуль, выполняющий (реализующий) несколько операций (функций) над одной и той же структурой данных (информационным объектом), которая считается неизвестной вне этого модуля. Для каждой из этих операций в таком модуле имеется свой вход со своей формой обращения к нему.
Сцепление модуля — это мера его зависимости по данным от других модулей. Характеризуется способом передачи данных. Чем слабее сцепление модуля с другими модулями, тем сильнее его независимость от других модулей. Для оценки степени сцепления используется упорядоченный набор из шести видов сцепления модулей. Худшим видом сцепления модулей является сцепление по содержимому. Таким является сцепление двух модулей, когда один из них имеет прямые ссылки на содержимое другого модуля (например, на константу, содержащуюся в другом модуле). Не рекомендуется использовать также сцепление по общей области — это такое сцепление модулей, когда несколько модулей используют одну и ту же область памяти. Единственным видом сцепления модулей, который рекомендуется для использования современной технологией программирования, является параметрическое сцепление — это случай, когда данные передаются модулю либо при обращении к нему как значения его параметров, либо как результат его обращения к другому модулю для вычисления некоторой функции. Такой вид сцепления модулей реализуется на языках программирования при использовании обращений к процедурам (функциям).
Рутинность модуля — это его независимость от предыстории обращений к нему. Модуль будем называть рутинным, если результат (эффект) обращения к нему зависит только от значений его параметров (и не зависит от предыстории обращений к нему). Модуль будем называть зависящим от предыстории, если результат (эффект) обращения к нему зависит от внутреннего состояния этого модуля, изменяемого в результате предыдущих обращений к нему.
Насколько возможно, все модули:
- должны быть связаны друг с другом иерархическим образом, причем структура связей, по возможности, должна представлять собой дерево;
- должны иметь небольшие размеры (не более 100 операторов языка программирования высокого уровня);
- должны быть функционально простыми (чем меньше число получаемых модулем управляющих признаков, которые им анализируются, тем он более функционально прост);
- должны иметь четко выраженное функциональное назначение;
- по возможности не должны использовать общие блоки данных (глобальные переменные), т.е. должны получать входные данные в форме параметров вызова и возвращать выходные данные вызывающему модулю в явном виде.
Сложность разработки модулей для программиста состоит в отсутствии возможности использовать модули (классы, процедуры), которые еще не созданы. При этом отсутствие модулей нижележащего уровня иерархии приводит к проблеме невозможности их вызова. Отсутствие модулей вышележащего уровня иерархии, из которых должен вызываться данный модуль (с передачей ему входной информации), не позволяет вести его отладку. Таким образом, создается парадоксальная ситуация: все модули должны создаваться одновременно, что, разумеется, невозможно. В случае, если разработка ведется коллективно, ситуация еще больше усложняется.
Для решения указанной проблемы предложен метод «драйверов» и «заглушек». Суть его состоит в том, что: вместо реальных вызываемых модулей нижнего уровня создаются так называемые «заглушки». Такие модули, имеют необходимое имя и принимают необходимые параметры, но не содержат кода. Возвращать они могут произвольные значения, либо некоторые константы.
Использование заглушек позволяет создать исходный текст и выполнить компиляцию реализуемого модуля. Разумеется, возможности его запуска и отладки весьма малы, если вообще возможны.
Для обеспечения возможности отладки созданного модуля создается так называемый «драйвер», т.е. программа, предназначенная исключительно для вызова данного модуля с передачей ему необходимых параметров.
По мере продвижения процесса разработки, заглушки и драйверы заменяются реальными модулями.
Проектирование интерфейса пользователя
Интерфейсом пользователя — далее интерфейсом — будем называть совокупность способов и правил взаимодействия программы с пользователем.
В большинстве современных программных продуктов проектированию интерфейса уделяется огромное внимание. Даже такие программы, которые должны работать без вмешательства человека, например, управляя искусственным спутником Земли, снабжаются интерфейсом пользователя для обеспечения возможностей их установки, контроля и настройки. Исключением, пожалуй, являются встраиваемые микропрограммы, которые функционируют полностью автономно.
В настоящее время в прикладном программировании широко используются визуальные интерфейсы, предоставляющие пользователю набор отображаемых стандартизованных элементов управления программой. Для визуального интерфейса существует ряд общепринятых требований. Среди них можно выделить требования предсказуемости (интуитивной понятности), привлекательности, максимальной независимости от конкретных характеристик устройств ввода-вывода, настраиваемости и целостности.
CC-BY-CA Цыганенко В.Н., 20.10.2013