Отдельный человек не в состоянии полностью осмыслить и построить программное обеспечение большой системы. В данном контексте под построением программного обеспечения большой системы мы подразумеваем разработку сложной прикладной программы. Сложная программа - это программа, состоящая из многих программных единиц (модулей). Обычно считается, что сложная программа - это программа, в которой от нескольких тысяч до десятков тысяч строк.
В настоящее время сложилась парадоксальная ситуация с литературой по данному вопросу. На полках книжных магазинов практически по любому программному продукту, мало-мальски используемому у нас, можно гарантированно найти по крайней мере две-три книги. Однако трудов, посвященных современным проблемам разработки программного обеспечения, принципам построения сложных систем, таких, как компиляторы, СУБД, операционные системы, антивирусные комплексы и т.п., нет. Приятно удивило выступление на эту тему такого уважаемого издания, как PC Magazine/Russion Edition, который в своем спецвыпуске № 5 за декабрь 1997г. опубликовал статью Евгения Зуева “Профессия редкая”. В ней автор делится своим опытом работы над крупными программными проектами. Статья отражает современное состояние вопроса и может быть рекомендована всем, кто занимается разработкой сложных программных комплексов.
Так как современных книг по разработке программных средств нет, а книги, изданные в 70-80-х годах, стали библиографической редкостью, то в рамках данного пособия постараемся осветить вопрос разработки программ.
О терминологии. В литературе 70-80-х годов, посвященной разработке программных средств, в основном используются термины “разработка”, “создание”, “проектирование” и гораздо реже термин “конструирование” программных средств [8]. Если первые два термина употребляются почти как синонимы, то термин “проектирование” уже, а “конструирование” по контексту понимается обычно как еще более узкий термин, означающий создание программы из отдельных конструктивов - “кубиков” программы.
Для лучшего управления ходом разработки больших программных проектов выделяются шесть этапов, составляющих цикл разработки (“цикл жизни”) программ (в скобках показано приблизительное распределение временных затрат на данный этап):
1) анализ требований, предъявляемых к системе (10%);
2) определение спецификаций (10%);
3) проектирование (15%);
4) кодирование (20%);
5) тестирование автономное (25%), тестирование комплексное (20%);
6) эксплуатация и сопровождение.
Рассмотрим кратко содержание первых двух этапов.
Анализ требований, предъявляемых к системе. Этот этап часто неоправданно опускается, а именно на нем определяются требования, выполнение которых позволяет получить приемлемое решение проблемы. Необходимо делать различие между жесткими требованиями и требованиями, выполнение которых не является строго обязательным. Следует выявить пространственно-временные ограничения, налагаемые на систему, средства системы, которые в будущем могут претерпеть изменения, а также средства, используемые в различных версиях системы для разных применений.
Важной задачей является определение ресурсов, требуемых для реализации системы. При этом решаются экономические вопросы, вопросы о требованиях к аппаратным средствам, о квалификации и обучении пользователя, о потребности в повседневном обслуживающем персонале и персонале сопровождения программного продукта, о возможности использования существующих пакетов программ. После того как все эти вопросы выяснены, переходят к планированию работ. Решаются вопросы управления развитием системы, устанавливаются показатели оценки достигнутого уровня.
Определение спецификаций. Здесь описываются подробно и точно все функции, реализуемые на компьютере. Задается структура входных и выходных данных. Определяется возможность использования базы данных. Если система создается на основе существующего процесса, то необходимо предусмотреть возможность ее модернизации при изменении процесса.
Все эти вопросы должны быть отражены в функциональных спецификациях, которые представляют собой документ, отображающий реализацию системы программирования. На этих данных базируется разработка системы тестирования программы. В общем, чем подробнее составлены спецификации, тем меньше вероятность возникновения ошибок, путаницы или взаимных обвинений заказчиков и разработчиков. Спецификации лишь определяют те функции, которые система должна выполнять, не указывая, каким образом это достигается. Составление подробных алгоритмов реализации функций системы на данном этапе преждевременно и может вызвать нежелательные осложнения.
Проектирование. На этом этапе разрабатываются алгоритмы, задаваемые спецификациями, и формируется общая структура вычислительной системы. Систему необходимо разбить на небольшие части таким образом, чтобы ответственность за реализацию каждой такой части можно было возложить либо на одного разработчика, либо на группу исполнителей. При этом для каждого определенного таким образом блока системы должны быть сформулированы предъявляемые к нему требования: реализуемые функции, размеры, время выполнения, особенности процесса обработки информации.
Поскольку в начале этапа проектирования решение задачи может быть не определено, разбиение задачи на подзадачи (блоки) может оказаться весьма сложным. Обычной является ситуация, когда заказчик системы точно не знает, чего он хочет. Поэтому по мере разработки проекта заказчик часто меняет спецификацию. К этому надо быть готовым, но все равно разработка усложняется.
Кодирование - перевод алгоритмической структуры на язык программирования. Данный этап обычно является наиболее простым, а его реализация облегчается при использовании алгоритмических языков высокого уровня и методов структурного программирования. Кодирование - это этап разработки программного обеспечения, доставляющий наименьшее беспокойство разработчикам. По некоторым опубликованным данным 64% всех ошибок вносится на этапе проектирования и лишь 36% - на этапе кодирования. Так, при разработке проекта НАСА “Аполлон” 73% ошибок было связано с проектированием интерфейса.
Тестирование - выполнение действий, предусмотренных задачей контроля правильности функционирования программы. Этап тестирования может повлечь затраты, которые составят половину общих расходов на создание системы. Плохо спланированное тестирование приводит к увеличению затрат и времени тестирования.
В процессе тестирования используются данные, характерные для системы в рабочем состоянии. Большую часть тестовых данных следует определить на этапе проектирования системы.
Последний пример неполного тестирования - операционная система “Windows-95”, в которой пользователи обнаружили более десятка значительных ошибок функционирования.
Тестирование подразделяется на три стадии: автономное, комплексное и системное. При автономном тестировании каждая часть программы проверяется с помощью данных, подготавливаемых программистом. При этом программная среда имитируется с помощью программы управления тестированием, содержащей фиктивные программы вместо реальных подпрограмм, к которым имеются обращения из данной части. Подобную процедуру иногда называют программным тестированием, а программу, подлежащую тестированию, - тестирующей программой. Часть программы, прошедшая автономное тестирование, подвергается комплексному тестированию.
В процессе комплексного тестирования производится совместная проверка групп программных компонентов. В результате имеем полностью проверенную систему. На данной стадии тестирования часто обнаруживаются ошибки, пропущенные на предыдущей стадии автономного тестирования. Исправление этих ошибок может вызвать до четверти общих затрат.
Системное или оценочное тестирование - это завершающая стадия проверки системы, т.е. испытание системы в целом с помощью независимых тестов. Независимость тестов является существенным обстоятельством. Заказчик при приемке может настоять на проведении своего собственного системного тестирования. Для случая, когда сравниваются характеристики нескольких систем (например, когда требуемое программное обеспечение поставляется различными изготовителями), такая процедура известна как сравнительное тестирование.
В процессе тестирования для определения правильности выполнения программы используются различные критерии, наиболее важными из которых являются следующие:
1. Каждый оператор должен быть выполнен по крайней мере один раз для заданного набора тестовых данных, и программа должна выдать правильные результаты.
2. Каждая ветвь программы должна быть опробована, и программа при этом должна выдать правильные результаты.
3. Каждый путь в программе должен быть испытан хотя бы один раз с использованием набора тестовых данных, и программа должна выдать правильные результаты.
4. Для каждой спецификации программы необходимо располагать набором тестовых данных, позволяющим установить, что программа правильно реализует данную спецификацию.
Хотя на первый взгляд пп.1 и 2 могут показаться схожими, в действительности они сильно разнятся. Например, для сложного оператора
IF <условие> THEN IF <условие> THEN <оператор 1>
ELSE <оператор 2>
ELSE IF <условие> THEN <оператор 3>
ELSE <оператор 4>;
критерий 1 лишь подразумевает, что IF выполнен, в то время как в соответствии с критерием 2 различные наборы тестовых данных должны вызвать передачу управления на операторы 1, или 2, или 3, или 4.
Этот пример показывает, что не существует единого критерия, определяющего “хорошо проверенную программу”.
Тесно связаны с тестированием понятия “верификация” и “испытания”.
Испытания системы осуществляются посредством её тестирования. Цель проведения такой проверки заключается в том, чтобы показать, что система функционирует в соответствии с разработанными для нее спецификациями.
Верификация системы заключается в выполнении доказательства, что программы удовлетворяют своим спецификациям. Современный процесс разработки программ не позволяет реализовать полностью обе указанные концепции. Для ситуаций, не контролируемых тестовыми данными, система, прошедшая испытание, может выдавать неверные результаты. После проведения верификации система работает правильно лишь относительно первоначальных спецификаций и допущений о поведении окружающей среды; формальные доказательства правильности программ весьма длинны и могут содержать ошибки. Общий процесс создания правильных программ с помощью процедур испытания и верификации называют аттестацией.
Различают три вида отклонений от нормальной работы системы. Сбой системы - это явление, связанное с нарушением системой установленных на нее спецификаций. Данные, при обработке которых правильными алгоритмами системы происходит сбой, называют выбросом. Исправление выброса можно предусмотреть в программе (например, с помощью оператора ON в языке ПЛ1, так что не каждый выброс приводит к сбою). Ошибка - это механический или алгоритмический дефект, который создает выброс (например, программная ошибка).
Не следует путать понятия надежности и правильности программы. Правильная программа - это такая программа, для которой доказано, что она удовлетворяет своим спецификациям. Что касается надежной программы, то она не обязательно является правильной, но выдает приемлемый результат даже в том случае, когда входные данные либо условия ее использования не удовлетворяют принятым допущениям. Естественно стремление иметь живучую систему, т.е. систему, способную воспринимать и правильно обрабатывать широкий спектр входных данных при неблагоприятных условиях. Обычно работа системы считается правильной, если в ней нет ошибок, а ее внутренние данные не содержат выбросов; система является надежной, если, несмотря на сбои, она продолжает удовлетворительно функционировать.
Различие между надежностью и правильностью можно понять на примере операционных систем, включающих процедуры обработки сбоев. При обнаружении выброса такая система прекращает работу с сохранением текущей информации и возможности продолжения работы после устранения выброса. Подобная система может не быть правильной, поскольку она подвержена воздействию выбросов; однако благодаря последовательному характеру функционирования система является надежной. Программа, работающая в реальном времени, может быть правильной до тех пор, пока датчик выдает точную информацию; однако эта программа может оказаться ненадежной, если неверные показания датчика не учтены в спецификациях (и, следовательно, при реализации программы).