Лекция № 6. Структурное и «неструктурное» программирование
Содержание лекции: понятия структурного и «неструктурного» программирования; программирование с «защитой от ошибок»; сквозной структурный контроль.
Цель лекции: выявить разницу между структурным и «неструктурным» программированием, а также приемы обеспечения защиты от ошибок.
Одним из способов обеспечения высокого уровня технологичности разрабатываемого ПО является структурное программирование. Различают три вида вычислительного процесса, реализуемого программами: линейный, разветвленный и циклический. Линейная структура процесса вычислений предполагает, что для получения результата необходимо выполнить операции в определенной последовательности. При разветвленной структуре процесса вычислений конкретная последовательность операций зависит от значений одной или нескольких переменных. Для получения результата при циклической структуре некоторые действия необходимо выполнить несколько раз. Для реализации этих вычислительных процессов в программах используют соответствующие управляющие операторы. Для изображения схем алгоритмов таких программ был разработан ГОСТ 19.701-90, согласно которому каждой группе действий ставится в соответствие специальный блок. Стандарт предусматривает блоки для обозначения циклов, не запрещает произвольной передачи управления и допускает использование команд условной и безусловной передачи управления при реализации алгоритма.
После того, как в 60-х годах XX в. было доказано, что любой сложный алгоритм можно представить, используя три основные управляющие конструкции, в языках программирования высокого уровня появились управляющие операторы для их реализации [6]. К базовым относят:
а) следование - обозначает последовательное выполнение действий;
б) ветвление - выбор одного из двух вариантов действий;
в) цикл-пока - определяет повторение действий, пока не будет нарушено некоторое условие, выполнение которого проверяется в начале цикла.
Кроме базовых, процедурные языки программирования высокого уровня используют три дополнительные конструкции, реализуемые через базовые:
а) выбор - выбор одного варианта из нескольких в зависимости от значения некоторой величины;
б) цикл-до - повторение действий до выполнения заданного условия, проверка которого осуществляется после выполнения действий в цикле;
в) цикл с заданным числом повторений (счетный цикл) - повторение некоторых действий указанное количество раз.
Перечисленные конструкции были положены в основу структурного программирования. Программы, написанные с использованием только структурных операторов передачи управления, называют структурными, чтобы подчеркнуть их отличие от программ, разрабатываемых с использованием низкоуровневых способов передачи управления. Недостатки схем:
а) низкий уровень детализации, что скрывает суть сложных алгоритмов;
б) использование неструктурных способов передачи управления, которые на схеме выглядят проще, чем эквивалентные структурные.
Кроме схем, для описания алгоритмов можно использовать псевдокоды, Flow-формы и диаграммы Насси-Шнейдермана, которые базируются на тех же основных структурах, допускают разные уровни детализации и делают невозможным описание неструктурных алгоритмов [1].
Псевдокод- формализованное текстовое описание алгоритма (текстовая нотация внескольких вариантах, таблица В.1). Изначально ориентирует проектировщика только на структурные способы передачи управления и требует более тщательного анализа разрабатываемого алгоритма. Псевдокоды не ограничивают степень детализации проектируемых операций, позволяют соизмерять степень детализации действия с рассматриваемым уровнем абстракции и хорошо согласуются с методом пошаговой детализации.
Flow-формы- графическая нотация описания структурных алгоритмов, иллюстрирующая вложенность структур. Каждому символу Flow-формы соответствует управляющая структура, изображаемая в виде прямоугольника и содержащая текст в математической нотации или на естественном языке. Для демонстрации вложенности структур символ Flow-формы вписывается в соответствующую область прямоугольника любого другого символа. Размер прямоугольника определяется длиной вписанного в него текста и размерами вложенных прямоугольников. В таблице В.1 приведены символы Flow-форм, соответствующие основным и дополнительным управляющим конструкциям.
Диаграммы Насси-Шнейдерманаявляются развитием Flow-форм лишь с той разницей, что область обозначения условий и вариантов ветвления изображают в виде треугольников (таблица В.1), обеспечивающих большую наглядность представления алгоритма.
Графические нотации лучше отображают вложенность конструкций, чем псевдокоды. Недостаток: сложность построения изображений символов усложняет их практическое применение для описания больших алгоритмов.
С точки зрения технологичности хорошим считают стиль оформления программы, облегчающий ее восприятие, как самим автором, так и другими программистами, которым придется ее проверять или модифицировать. Автор известной монографии, посвященной проблемам программирования, Д. Ван Тассел призывал: «Помните, программы читаются людьми» [7]. Исходя из того, что любую программу придется неоднократно просматривать, следует придерживаться хорошего стиля написания программ, который включает:
а) правила именования объектов программы;
б) правила оформления модулей;
в) стиль оформления текстов модулей.
Любая из ошибок программирования, которая не обнаруживается на этапах компиляции и компоновки программы, в итоге может проявиться тремя способами: привести к выдаче системного сообщения об ошибке, «зависанию» компьютера и получению неверных результатов. Однако до того, как результат работы программы становится фатальным, ошибки много раз проявляются в виде неверных типах данных, неверных промежуточных результатах, неверных управляющих переменных, индексах структур данных и т. п. (рисунок В.1). Часть ошибок можно обнаружить и нейтрализовать, пока они не привели к тяжелым последствиям. Программирование, при котором применяют специальные приемы раннего обнаружения и нейтрализации ошибок, названо защитнымили программированием «с защитой от ошибок».Детальный анализ ошибок и их возможных ранних проявлений показывает, что целесообразно проверять правильность выполнения операций ввода-вывода, а также допустимость промежуточных результатов (значений управляющих переменных, индексов, типов данных, числовых аргументов).
Причинами неверного определения исходных данных могут являться, как внутренние ошибки (ошибки устройств ввода-вывода или программного обеспечения), так и внешние ошибки (ошибки пользователя). Различают:
а) ошибки передачи - аппаратные средства искажают данные;
б) ошибки преобразования - программа неверно преобразует исходные данные из входного формата во внутренний;
в) ошибки перезаписи - пользователь ошибается при вводе данных;
г) ошибки данных — пользователь вводит неверные данные.
Ошибки передачи обычно контролируются аппаратно.
Для защиты от ошибок преобразования данные после ввода сразу демонстрируют пользователю («эхо»), выполняя преобразование сначала во внутренний формат и обратно. Предотвратить все ошибки преобразования на этом этапе крайне сложно, поэтому фрагменты программы тестируют [15], используя методы эквивалентного разбиения и граничных значений.
Обнаружить и устранить ошибки перезаписи можно только при вводе избыточных данных, например, контрольных сумм. Если ввод избыточных данных нежелателен, то по возможности проверяют входные данные и контролируют интервалы возможных значений, определенных в техническом задании, а также выводят введенные данные для проверки пользователю.
Неверные данные обычно может обнаружить только пользователь.
Проверки промежуточных результатов позволяют снизить вероятность позднего проявления не только ошибок неверного определения данных, но и некоторых ошибок кодирования и проектирования. Для этого необходимо, чтобы в программе использовались переменные с ограничениями любого происхождения, например, связанные с сущностью моделируемых процессов.
Любые дополнительные операции в программе требуют использования дополнительных ресурсов (времени, памяти) и могут содержать ошибки. Имеет смысл проверять только те промежуточные результаты, проверка которых целесообразна и не сложна, например, допустимость индекса.
Для снижения погрешности результатов вычислений следует
а) избегать вычитания близких чисел (машинный ноль);
б) избегать деления больших чисел на малые;
в) сложение длинной последовательности чисел начинать с меньших по абсолютной величине;
г) стремиться по возможности уменьшать количество операций;
д) использовать методы с известными оценками погрешностей;
е) не использовать условие равенства вещественных чисел;
ж) вычислять с двойной точностью, а результат выдавать - с одинарной.
Поскольку полный контроль данных на входе и в процессе вычислений невозможен, следует предусматривать перехват и обработку аварийных ситуаций. Для перехвата и обработки аппаратно и программно фиксируемых ошибок в некоторых языках программирования (Delphi Pascal, C++ и Java) предусмотрены средства обработки исключений. Использование этих средств позволяет не допустить выдачи пользователю сообщения об аварийном завершении программы, а программист получает возможность предусмотреть действия, позволяющие исправить эту ошибку или выдать пользователю сообщение с точным описанием ситуации и продолжить работу.
Сквозной структурный контроль (ССК) – это совокупность технологических операций контроля на всех этапах разработки при наличии четких рекомендаций по их выполнению, позволяющих обеспечить как можно более раннее обнаружение ошибок в процессе разработки. ССК должен выполняться на специальных контрольных сессиях, в которых, помимо разработчиков, могут участвовать специально приглашенные эксперты. Время между сессиями определяет объем материала, который выносится на сессию. Материалы для очередной сессии должны выдаваться участникам заранее для обдумывания. Одна из первых сессий должна быть организована на этапе определения спецификаций для проверки полноты и точности спецификаций. На этапе проектирования вручную проверяют алгоритмы разрабатываемого ПО на конкретных наборах данных и сверяют полученные результаты с соответствующими спецификациями, чтобы убедиться в правильности понимания спецификаций и проанализировать достоинства и недостатки концептуальных решений, закладываемых в проект. На этапе реализации проверяют последовательность реализации модулей, набор тестов, а также тексты отдельных модулей. Для всех этапов следует иметь списки наиболее часто встречающихся ошибок, формируемые по литературным источникам и опыту предыдущих разработок. Такие списки позволяют сконцентрировать усилия на конкретных моментах, а не проверять все подряд. При этом все найденные ошибки фиксируют в специальном документе, но не исправляют. Помимо раннего обнаружения ошибок, ССК обеспечивает своевременную подготовку качественной документации по проекту.
Дополнительную информацию по теме можно получить в [1, 6, 7, 15].