Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsii_TP.doc
Скачиваний:
203
Добавлен:
31.03.2015
Размер:
741.38 Кб
Скачать

2. Приемы обеспечения технологичности программных продуктов

2.1. Понятие технологичности программного обеспечения

Под технологичностью понимают качество проекта программного продукта, от которого зависят трудовые и материальные затраты на его реализацию и последующую модификацию. Хороший проект быстро и легко кодируется, тестируется и модифицируется. Технологичность ПО определяется проработанностью его модулей, уровнем независимость модулей, стилем программирования и степенью повторного использования кодов. Чем выше независимость модулей, тем легче их понять, реализовать, модифицировать, находить и исправлять ошибки. Высокая технологичность особенно важна, если разрабатываемый продукт рассчитан на долговременное использование.

2.2. Модули, их свойства.

Результатом процедурной декомпозиции является иерархия процедур, в которой функции, связанные с принятием решения, реализуются с подпрограммами верхних уровней, а непосредственная обработка с подпрограммами нижних уровней. Это согласуется с принципом вертикального управления. Результатом объектной декомпозиции является совокупность объектов, которые затем реализуют, как переменные некоторых классов. Таким образом, при любой декомпозиции получают набор связанных соответствующими данными подпрограмм, которые в процессе реализации организуют модули. Модулем называют автономно компилируемую программную единицу. Термин модуль используется в двух смыслах:

  1. когда размер программы был невелик, и все подпрограммы компилировались отдельно, под модулем понималась подпрограмма.

  2. когда размер программы вырос, появилась возможность создавать библиотеки, термин модуль стал использоваться и в смысле автономно компилируемого набора программных ресурсов. Данный модуль можно получать и/или возвращать через общие области памяти или параметры.

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

Сцепление модулей

Сцепление является мерой взаимозависимости модулей, которая определяет, насколько хорошо модули отделены друг от друга. Модули независимы, если каждый из них не содержит о другом никакой информации.

5 типов сцепления:

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

  2. сцепление по образцу – модули обмениваются данными, объединенными в структуры. Здесь характеристики хуже, чем в первом случае, так как уменьшается прозрачность между модулями.

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

Function min_max (a, b: integer; f: boolean ): integer;

begin

if (a>b) and (f) then min_max:=a

else min_max:=b

end;

4) сцепление по общей области данных предполагает, что модули работают с общей областью данных. Этот тип является недопустимым, так как программы сложны для понимания. Ошибка одного модуля, приводящая к изменению общих данных, может проявиться при выполнении другого модуля. При ссылке к данным в общей области модули используют конкретные имена, что уменьшает гибкость разрабатываемого ПО. Например, функция maxиспользует глобальный массивa, но сцеплена с основной программой по общей области. Подпрограммы “с памятью”, действие которых зависит от истории вызова используемого сцепления по общей области, что делает их работу в общем случае непредсказуемой. Этот вариант использует статические переменныеCиC++.

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

Характеристики различных типов сцепления по экспертным оценкам

Тип сцепления

Балл

Устойчивость к ошибкам других модулей

Наглядность (понятность)

Возможность изменения

Вероятность повторного использования

1) по данным

1

хорошая*

хорошая

хорошая

большая

2) по образцу

3

средняя

хорошая*

средняя

средняя

3) по управлению

4

средняя

плохая

плохая

малая

4) по общей области

6

плохая

плохая

средняя

малая

5) по содержанию

10

плохая

плохая

плохая

малая

*- зависит от количества параметров интерфейса.

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

Связность модулей

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

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

  1. Функциональная

  2. Последовательная

  3. Информационная (коммуникативная)

  4. Процедурная

  5. Временная

  6. Логическая

  7. Случайная

1). Все объекты модуля предназначены для выполнения одной функции: операции, объединяемые для выполнения одной функции, или данные, связанные с одной функцией.

Рис.2.1. Связность одной функции.

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

2). Выход одной функции служит исходными данными для другой функции.

Рис.2.2.

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

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

Рис.2.3.

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

4). Процедурно связаны функции или данные, которые являются частями одного процесса.

Рис.2.4.

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

5). Временная связность функций подразумевает, что эти функции выполняются параллельно или в течение некоторого времени.

Рис.2.5.

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

6). Логическая связность базируется на объединении данных или функций в одну логическую группу.

Рис.2.6.

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

7). Если связь между элементами мала или отсутствует, то они имеют случайную связность. Такие модули имеют самый низкий показатель технологичности.

Вид связности

Сцепление (балл)

Наглядность (понятность)

Возможность изменения

Сопровождаемость

1). Функциональная

10

Хорошая

Хорошая

Хорошая

2). Последовательная

9

Хорошая

Хорошая

Хорошая

3). Информационная

8

Средняя

Средняя

Средняя

4). Процедурная

5

Средняя

Средняя

Плохая

5). Временная

3

Средняя

Средняя

Плохая

6). Логическая

1

Плохая

Плохая

Плохая

7). Случайная

0

Плохая

Плохая

Плохая

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

Библиотеки ресурсов

Различают библиотеки программ и библиотеки классов.

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

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

2.3. Нисходящая и восходящая разработка ПО

В литературе встречается ещё один подход – «расширение» ядра. Он предполагает, что в первую очередь проектируют и разрабатывают ядро ПО, например структуры данных и процедуры, связанные с ними. Затем ядро наращивают, комбинируя нисходящий и восходящий методы. На практике данный подход в зависимости от уровня ядра сводится к нисходящему и восходящему подходу.

Восходящий подход. Сначала проектируют и реализуют компоненты нижнего уровня, затем следующего и т.д. По мере завершения тестирования и отладки компонентов осуществляют их сборку. Компоненты нижнего уровня часто помещают в библиотеки компонентов. Для тестирования и отладки компонентов проектируют и реализуют специальные тестирующие программы. Недостатки:

  • увеличение вероятности несогласованности компонентов вследствие неполноты спецификации;

  • наличие издержек на проектирование и реализацию тестирующих программ;

  • позднее проектирование интерфейса, следовательно невозможность продемонстрировать его заказчику.

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

Нисходящий подход. При использовании нисходящего подхода применяют иерархический, операционный и комбинированный методы определения последовательности проектирования и реализации компонентов. Иерархический метод предполагает выполнение разработки строго по уровню. Исключения допускаются при наличии зависимости по данным, то есть если обнаруживается, что некоторый модуль использует результаты другого, то его рекомендуется программировать после этого модуля. Основная проблема этого метода – большое количество достаточно сложных заглушек. Также основное количество модулей разрабатывается и реализуется в конце работы над проектом, что затрудняет распределение человеческих ресурсов. Операционный метод связывает последовательность разработки модулей с порядком их выполнения при запуске программы. Применение метода усложняется тем, что порядок выполнения модулей может зависеть от данных. Кроме этого модули вывода результатов (хотя они вызываются последними) должны разрабатываться вначале. С точки зрения распределения человеческих ресурсов сложным является начало работы, пока не закончены все модули, находящиеся на так называемом критическом пути. Комбинированный метод учитывает факторы, влияющие на последовательность разработки:

  • достижимость модуля (наличие всех модулей в цепочке вызова данного модуля),

  • зависимость по данным (модули, формирующие некоторые данные, должны создаваться раньше обрабатывающих),

  • обеспечение возможности выдачи результатов,

  • готовность вспомогательных модулей,

  • наличие необходимых ресурсов.

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

  1. максимально полное определение спецификации проектируемого компонента и согласованность компонентов между собой.

  2. раннее определение интерфейса пользователя, демонстрация которого заказчику, позволяет уточнить требования к создаваемому ПО.

  3. возможность нисходящего тестирования и комплексной отладки.

2.4.Структурное и неструктурное программирование. Средства описания структурных алгоритмов.

Структурное программирование – это один из способов обеспечения высокого уровня технологичности разрабатываемого ПО.

Существует три вида вычислительного процесса:

  1. линейный, 2)разветвляющий, 3) цикличный.

Для реализации этих процессов в программировании используют соответствующие управляющие операторы. Для изображения схем алгоритмов таких программ был разработан ГОСТ 19.701-90.

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

1) следования,

2) ветвления,

3) цикл “пока”.

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

  1. выбор – case

  2. цикл “до”

  3. цикл for.

Любая дополнительная конструкция легко реализуется через базовые. Эти шесть конструкций были положены в основу структурного программирования.

1).

2).

3).

Дополнительные конструкции:

1).

2).

3).

Рис.2.7.

Слово структурное в данном случае подчеркивает тот факт, что при программировании используются только эти структуры.

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

  1. Псевдокод – это формализованное текстовое описание алгоритма. Текстовая нотация. В литературе предложено несколько вариантов псевдокодов.

  2. Flow-диаграммы (формы) – графическая нотация описания структурных алгоритмов, которая иллюстрирует вложенность структур. Каждый символflow-формы соответствует управляющей структуре и изображается прямоугольником.

<g1>

<g2>

<g3>

если <условие>

то

<g1>

иначе

<g2>

пока <условие>

<g1>

<g1>

до <условие>

Рис.2.8.

  1. Диаграммы Насси - Шнейдермана являются развитием flow-форм. Основное отличие в том, что область обозначений условий и вариантов ветвления изображается в виде треугольников.

Рис.2.9.

Общим недостатком flow-форм и диаграмм Насси - Шнейдермана является сложность построения изображений для больших алгоритмов.

2.5. Стиль оформления программ.

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

  • правила именования объектов программ

  • правила оформления модулей

  • стиль оформления текстов модулей.

Имя объекта должно соответствовать его содержанию, для визуального разделения имен используется знак подчеркивания. Следует избегать близких по написанию имен.

Правила оформления модуля

Каждый модуль должен иметь заголовок, который как минимум содержит название модуля, краткое описание его назначения, краткое описание входных и выходных параметров с указанием единиц измерения, список используемых вызываемых модулей, краткое описание алгоритма (метода и/или ограничений), ФИО автора, идентифицирующую информацию.

Пример:

{Функция: Length_Path (n: word; L: array of real)

Цель: определение суммарной длины отрезков.

Исходные данные:

n- количество отрезков,

L– массив длин отрезков (в метрах)

Результат: длина (в метрах).

Вызываемые модули: нет.

Описание алгоритма:

отрезки суммируются методом накопления,

n>=0

Дата: 9.11.2004

Версия: 1.01

Автор:

Исправления: нет }

Стиль оформления текстовых модулей

Определяет использование отступов, пропусков строк и комментариев. Обычно пропуски строк и комментариев используют для визуального разделения частей модуля. Для языков Pascal, C++, Java использование отступов позволяет прояснить структуру программы. Обычно дополнительный отступ обозначает вложение операторов языка. Сложнее обстоит дело с комментариями. Не нужно переводить с английского языка каждый оператор программы. Комментировать следует цели выполнения действий и группы операторов, связанные общим действием, то есть комментарий должен содержать дополнительную неочевидную информацию. Для языков низкого уровня стиль, облегчающий понимание, предложить труднее. Здесь целесообразно комментировать и блоки операторов, и каждый оператор. Например, цикл суммирования элементов массива.

2.6. Эффективность и технологичность.

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

Разумный подход к обеспечению эффективности разрабатываемого ПО состоит в том, чтобы в первую очередь оптимизировать те элементы программы, которые существенно влияют на характеристики эффективности. Для уменьшения времени выполнения программы в первую очередь следует проанализировать циклические алгоритмы с большим числом повторений. Необходимо помнить, что многие способы снижения временных затрат приводит к увеличению объема памяти и наоборот. Не следует “платить” за увеличение эффективности снижением технологичности ПО, исключения возможны только при очень жестких требованиях и наличии соответствующего контроля за качеством. Частично проблему эффективности программ решают за программиста компиляторы. Средства оптимизации, используемые компиляторами, делят на две группы:

  1. машинно-зависимые (ориентированы на конкретный машинный язык; выполняют оптимизацию кодов на уровне машинных команд)

  2. машинно-независимые (выполняют оптимизацию на уровне входного языка, например, вынесение константных выражений из циклов и т. д.).

Способы экономии памяти.

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

Способы уменьшения времени выполнения.

В первую очередь необходимо анализировать циклические участки программы с большим числом повторений. При их написании при возможности необходимо:

  • выносить вычисления константных, то есть не зависящих от параметров цикла, выражений из цикла;

  • избегать длинных операций умножения и деления, заменяя их сложением, вычитанием и сдвигом;

  • оптимизировать запись условных выражений, исключать лишние проверки;

  • минимизировать преобразование типов выражений;

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

Рассмотрим пример:

1). Пусть имеется цикл

for y:=0 to 99 do

for x:=0 to 99 do

a[320*x+y]:=s[k,l]

Оптимизируем цикл, используя, что 320=28+ 2 6

Выполняя оптимизацию можно ухудшить технологичность.

2). for k:=2 to n do

begin

if x[k]>y1 then

s:=s+y[k]-x[k];

if (x[k]<=y1) and (y[k]<y1) then

s:=s+y1 –x[k];

end;

Здесь можно убрать лишние проверки.

2.7. Программирование защиты от ошибок.

Любая ошибка программирования, не обнаруженная на этапе компиляции и компоновки программы, в конечном счете может проявиться тремя способами:

  1. получение неверных результатов;

  2. выдача системного сообщения об ошибке;

  3. зависание компьютера.

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

Рис.2.10. Способы проявления ошибок.

Целесообразно проверять правильность выполнения операция ввода-вывода и допустимость промежуточных результатов.

Проверки правильности выполнения операций ввода-вывода

Причинами неверного определения исходных данных могут быть внутренние ошибки и внешние (ошибки пользователя). Принято различать:

  1. Ошибки передачи: аппаратные средства искажают данные

  2. Ошибки преобразования: программа неправильно преобразует исходные данные из входного формата во внутренний

  3. Ошибки перезаписи: пользователь ошибается при вводе данных

  4. Ошибки данных: пользователь вводит неверные данные.

Ошибки передачи обычно контролируются аппаратно. Для защиты от ошибок преобразования данные после ввода обычно сразу демонстрируют пользователю («эхо»). Обнаружить и устранить ошибки перезаписи можно, если пользователь водит избыточные данные, например контрольные суммы. Если это нежелательно, то следует по возможности проверять вводимые данные, контролировать интервалы возможных значений (ТЗ). Неверные данные обычно можно обнаружить только пользователь.

Проверка допустимости промежуточных результатов

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

  • избегать вычитания близких чисел,

  • избегать деления больших чисел на маленькие,

  • сложение длинной последовательности чисел начинать с меньших по абсолютной величине,

  • стремиться уменьшать количество операций,

  • использовать методы с известными оценками погрешности,

  • не использовать условия равенства вещественных чисел,

  • вычисления производить с двойной точностью, а результаты с одинарной.

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

2.8. Сквозной структурный контроль

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]