Правильная ссылка на эту страницу
http://az-design.ru/Support/SoftWare/l/GlassRob/02h003.shtml

Глава 3. ТЕХНИЧЕСКИЙ АСПЕКТ СОПРОВОЖДЕНИЯ

       Процесс создания программного обеспечения можно сравнить с процессом воспитания человека. Степень приспособленности систем к выполнению своих функций зависит, так же как у человека, от суммы знаний, заложенных в них на ранних стадиях разработки. В данной главе мы рассмотрим различные методы создания удобного и надежного программного обеспечения.
       Если, читая эту главу, вы надеетесь получить ответ на вопрос “Как сократить затраты на сопровождение?” применительно к уже работающей системе, то вы опоздали. Сложность и возможность модификации программы уже определены, а следовательно, определены и затраты. Задайтесь тем же вопросом применительно к вашей будущей системе.
       Это не означает, что в существующей системе бесполезно что-либо изменять радикально. Придерживаясь методики, изложенной в книге, можно постепенно превратить трудномодифицируемую систему в такую, которую модифицировать не слишком трудно. Однако, так же как и в случае воспитания ребенка, формирование правильного поведения системы лучше всего начать на самой ранней стадии ее развития.
       В предыдущей главе достаточно хорошо сформулированы задачи специалиста по сопровождению и определены приоритеты решений этих задач. В данной главе мы постараемся подробно рассмотреть способы их решения. Рассмотрим также технические приемы сопровождения и будем ими пользоваться. К сожалению, их невозможно определить так же точно, как задачи специалиста по сопровождению программного обеспечения (СПО). Для того чтобы выполнить поставленную перед ним задачу, сопровождающий программист руководствуется определенной схемой, включающей требования пользователя, существующую программу, все доступные ему средства, окружение и его личные возможности.
       Сопровождающий программист несет ответственность за все, что относится к программному обеспечению. Наиболее очевидным результатом хорошего сопровождения является безошибочная работа всего программного обеспечения в целом. Справочники пользователя часто включают ошибки, неточности и недоработки, которые приводят к затруднениям при работе пользователя. Исправление документации отнимает у сопровождающего программиста много времени. Но документирование возникающих в ходе работы проблем и возможных путей и устранения необходимо для работы пользователя. Спецификации программного обеспечения могут также содержать неточности и ошибки. И их исправление входит в функции сопровождающего программиста.
       Итак, специалист по СПО встречается с разного рода задачами, которые обычно решаются вручную или с помощью соответствующей данному случаю методики. Однако с увеличением объема программного обеспечения становится все очевиднее, что программист для решения своих задач должен применять новейшую технологию программирования. Это достигается посредством использования методов системного анализа, а также современных методов автоматизации программирования. Системный анализ ведет к лучшей технологии программирования, а автоматизация дает лучшие средства программирования. Разбор технологии и средств программирования является темой настоящей главы.
       Но прежде, чем перейти к обсуждению этой темы, рассмотрим работу сопровождающего программиста. Тем самым мы определим требования, предъявляемые к технологии и средствам программирования.

3.1. ЧЕМ ЗАНИМАЕТСЯ СОПРОВОЖДАЮЩИЙ ПРОГРАММИСТ

       Одна из трудностей руководителя, контролирующего работу сопровождающего программиста, состоит в том, что он часто не знает, что делает этот программист. Здесь мы постараемся показать весь процесс его работы. Возьмем для примера одну задачу и рассмотрим весь перечень работ, связанных с ее решением. Затем повторим этот процесс много раз и перетасуем его составные части. В результате мы получим представление о том, как много работ выполняет сопровождающий программист.
       Специалист по сопровождению обычно тратит много времени, анализируя необработанные данные о предполагаемых причинах ошибок. Обычно все начинается с появления раздраженного заказчика. Претензии могут быть самыми разными в зависимости от типа программного обеспечения системы. Например, пользователь программы, с помощью которой готовится платежная ведомость, может сказать: “У меня цифры не сходятся. В вашей программе ошибка!” В то же время пользователь компилятора может сказать: “Моя программа ведет себя как-то странно. Вероятно, в компиляторе ошибка”.
       Порой, чтобы отличить настоящие ошибки от воображаемых, сопровождающему программисту приходится потратить немало времени. За это время он составляет определенное мнение о причине ошибок. Он может задать пользователю много вопросов, воспользоваться справочниками (например, справочником пользователя), спецификациями программы, документами по сопровождению, распечатками программ. В течение этого времени он должен установить истину: ошибка ли это пользователя, сбой ли это в программе или программа работает верно, но нечетко. Может быть, допущена неточность в документации? Или все же дело в программе? Учтите также, что размышлять приходится в самый неподходящий момент.
       И когда сопровождающий программист занят решением какой-то определенной задачи, его прерывают, и очередной вновь пришедший клиент жизнерадостно спрашивает: “Можно вас на минуточку?” Как тут оставаться спокойным!
       Итак, он отправляет своего предыдущего собеседника, наскоро пообещав ему внести исправление в руководство пользователя. Но у этого вновь пришедшего в самом деле что-то серьезное. После 20-минутного разговора и просмотра дополнительной информации оба приходят к выводу, что действительно что-то неверно. Сопровождающий программист берет журнал, в котором фиксируются замечания пользователя, и начинает регистрировать отказ в программе. Заказчик падает духом. “Как, неужели ОН не сможет исправить это за 20 минут?” — думает пользователь. Вслух он ничего не произносит, но эта мысль написана на его лице. Подумав 1—2 мин, сопровождающий программист находит какой-нибудь выход из положения, записывает его в журнал (на случай, если кто-то столкнется с подобной проблемой), а пожалуйста! Еще один довольный заказчик отправляется по своим делам.
       Вот теперь он может действительно подумать о причинах ошибки. (Он пришел в субботу в 10 ч 30 мин вечера, чтобы поработать спокойно.) Из своей предварительно разложенной по степени важности стопки бумаг он достает одну — с надписью “Очень срочно!”. Берет длинный список возможных ошибок в программах, руководство пользователя, спецификации, документацию и, обложившись всеми этими материалами, принимается за дело. Читает, строит графики, повторяет все это по многу раз и очень быстро (время летит незаметно, когда занимаешься любимым делом) обнаруживает ошибку в программе. Возможно, что так бывает.
       А теперь вернемся к реальности. Если бы все программы писались “идеальными программистами”, документация строго отражала бы каждый шаг в их мышлении (включая и те варианты, которые были отброшены), программы были бы совершенными, четкими, имели бы простую структуру, простые модули и в них бы не было ошибок (перепутанных строчек и т.д.), то сопровождение было бы просто развлечением. Но в жизни все обстоит иначе. Большинство программ претерпевает значительные изменения. Поэтому найти ту часть программы, где требуется внести исправление, нелегко. Тем более нелегко сделать это исправление.
       Однако вернемся к нашей задаче. Есть ошибка в программе. Ее следует устранить. Но это еще не все. Надо провести кропотливую работу, чтобы гарантировать качество. Сопровождающий программист должен проверить исправленную им программу. В большинстве случаев проверка покажет качество всей программы в целом. Добавьте специальную проверку, направленную конкретно на решенную вами задачу. Придется потратить время и на это. Иногда проверка проходит легко. Но не всегда. Иногда обнаруживается дефект в программе. Надо вновь искать ошибку и устранить ее. Наконец все работает! Но закончена ли работа специалиста по СПО? Нет. Теперь следует оформить документацию, написать примечания для пользователя, записать возникшие в ходе работы проблемы. Вот теперь все. К сожалению, по этой схеме сопровождающие программисты никогда не работают.

3.1.1. СОПРОВОЖДАЮЩИЙ ПРОГРАММИСТ И ПОЛЬЗОВАТЕЛЬ

       Как вы уже поняли, сопровождающий программист тратит много времени, отвечая на вопросы, устраняя замечания пользователя, анализируя требования и даже помогая в вопросах, не имеющих прямого отношения к программированию. Известно, что пользователь не всегда хорошо разбирается в системе, а сопровождающий программист знает ее до тонкостей. При обучении пользователей можно пойти по двум направлениям: 1) предварительно обучать, как пользоваться системой, или 2) консультировать их во время работы с системой. Часто необходимо и то, и другое. В результате эта работа также ложится на сопровождающего программиста.
       Кроме того, сопровождающий программист не всегда находится в непосредственной близости от пользователя. Чем доступнее он для пользователя, тем больше претензий выпадает на его долю.
       В любом случае сопровождающий программист оказывает дополнительную помощь пользователю, который постоянно обращается к нему со своими проблемами. Это делает сопровождение более дорогостоящим, так как отнимает у программиста время.

3.1.2. СОПРОВОЖДАЮЩИЙ ПРОГРАММИСТ И ЕГО ЖУРНАЛ

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

3.2. КАК РАБОТАЕТ СОПРОВОЖДАЮЩИЙ ПРОГРАММИСТ

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

3.2.1. СРЕДСТВА

       Как уже говорилось, сопровождение составляет значительную часть жизненного цикла программного обеспечения, которую чаще, чем другие этапы цикла, не учитывают. Программное обеспечение существует в первую очередь для того, чтобы способствовать все более широкому применению вычислительных машин, чтобы заменить труд человека трудом машины. Но в большинстве случаев в самой области программного обеспечения для решения собственных задач вычислительные машины не применяются.
       Следует признать, что “системе” предстоит просуществовать хотя и долгий, но все же не бесконечный отрезок времени. Необходимость сопровождения также следует признать и заранее запланировать.
       Существует ли такая часть программного обеспечения, которая бы сразу удовлетворила пользователя, не имела ошибок и не требовала модификации? Вряд ли.
       Наличие ошибок и длительность процесса сопровождения требуют долгосрочного планирования. Такое планирование, как и в других областях, требует применения соответствующих (включая машинные) средств. Пришло время дать их проектировщикам программного обеспечения и сопровождающим программистам. Время, когда дамп (вывод данных и контроль правильности: выполнения операции вывода путем сопоставления контрольных сумм) был единственным средством сопровождения, прошло. Мы нуждаемся в более эффективных средствах. (Понятие “средства” подробно дается в [23].)
       Существуют две основные группы средств, используемых при сопровождении. Первая группа связана непосредственно с технической стороной сопровождения. Она включает: компиляторы; ассемблеры; редакторы связей; операционные системы; захватывание и трассировку; дампы; символьный вывод; анализ по уровням; компараторы; анализаторы, использующие полное множество тестов; средства верификации; средства аттестации; варианты тестирования; таблицы перекрестных ссылок; редакторы текста; преобразователи и препроцессоры.
       Вторая группа средств дает возможность сопровождающему программисту выполнять административные функции. Эти средства включают: составление перечня ошибок, ведение журнала отказов, фиксации вносимых поправок, ведение документации и написание отчетов.
       Обе категории средств могут применяться либо в пакетном режиме (пользователь ставит задачу и приходит за готовым решением), либо в интерактивном режиме разделения времени (пользователь вмешивается в работу программы с помощью терминала). Применяемые средства в этих случаях будут различны из-за задержки во времени при работе в пакетном режиме.
       Авторы рекомендуют при всякой возможности работать в интерактивном режиме, так как он значительно продуктивнее. Однако приходится признать, что в большинстве вычислительных центров реальное положение дел, к сожалению, пока не соответствует этой рекомендации.
       Конечно, интерактивный режим имеет свои преимущества и недостатки. Преимуществом работы в интерактивном режиме является возможность для сопровождающего программиста избежать с помощью терминала многократной прогонки программы, которая во многих вычислительных центрах требует несколько дней. При этом ход мысли сопровождающего программиста не прерывается из-за необходимости дожидаться конца прогона программы.
       Однако у этого метода есть и недостатки. Сопровождающий программист, работая в пакетном режиме, может оформить документацию, пока у него идет программа. В интерактивном режиме такой возможности у него нет. Кроме того, в результате оперативного устранения ошибки с помощью терминала может быть принято поспешное, плохо продуманное решение. Чтобы этого не случилось, программист должен быть очень дисциплинированным и внутренне организованным.
       И все же преимущества работ в интерактивном режиме убедительно показаны экспериментально. Такая точка зрения изложена в литературе [43, 47]. Обобщая все сказанное, можно заключить, что в процессе разработки программного обеспечения больше всего не хватает думающих специалистов. Поэтому важны такие средства, как работа в интерактивном режиме. Они позволяют сделать мыслительную деятельность более продуктивной.

3.2.1.1. Средства сопровождения

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

Язык программирования
       Выбор языка программирования является одним из наиболее важных решений, которое приходится принимать по отношению к программному обеспечению. Обычно сопровождающий программист не участвует в выборе языка, однако необходимо учитывать влияние, которое язык окажет на сопровождение. Мы настоятельно рекомендуем применять языки самого высокого уровня как при проектировании системы, так и на этапе ее реализации.
       Тема описания процесса проектирования будет изложена ниже. Здесь, однако, следует заметить, что форма описания процесса проектирования (которая все чаще в наши дни является языком программирования) должна выбираться с учетом ряда ее экспрессивных возможностей:
          1) декомпозиции системы на подсистемы;
          2) выбора интерфейса как внешнего, так и внутреннего;
          3) определения структуры данных и потока данных системы;
          4) представления структуры алгоритма и управления.
       Еще важнее, пожалуй, чтобы форма описания процесса проектирования системы:
          5) была совместима с выбранным языком программирования;
          6) позволяла включать комментарии в программу.
       Два последних требования делают языковую форму описания процесса проектирования наиболее предпочтительной. Более старые способы, такие, как использование структурных схем при написании программ, неудобны для этапа сопровождения, так как с трудом поддаются модификации и поэтому быстро устаревают.
       Мы считаем, что языки более высокого уровня должны использоваться не только на этапах проектирования и реализации системы, но также на протяжении всего жизненного цикла программного обеспечения, так как зачастую различные модули системы запрограммированы на разных языках, что неудобно для всей системы в целом.
       Бывает так, что большая часть системы только с виду пригодна для применения единого языка на всех стадиях ее жизненного цикла, однако вскоре становится ясно, что без жесткого администрирования количество языков, применяемых в системе, быстро растет. Во многих случаях системный программист приходит к выводу, что отдельные части системы не поддаются описанию с помощью “официального” языка системы. Тогда он делает исключение и начинает применять другие языки.
       И здесь первая ошибка, которую он может совершить, состоит в применении языка Ассемблера вместо языка высокого уровня (ЯВУ). Обычной причиной такого заблуждения является добросовестность программиста. Он, например, рассуждает так: “Этот компилятор не обеспечивает возможности эффективного вычисления специальных функций; значит, необходимо составить программу их расчета на Ассемблере”.
       Вторым препятствием к применению единого ЯВУ для всей системы является недостаток мастерства программиста. Если в работе участвуют программисты, не имеющие опыта работы с языком, используемым в системе, то по соглашению с администрацией им разрешают для некоторых программ применить другой язык высокого уровня или Ассемблер.
       Кроме того, множество применяемых вспомогательных средств не считается частью системы, поэтому их создателям разрешается выходить за рамки требований единого ЯВУ. В особенности это бывает в тех случаях, когда вспомогательные средства создаются в процессе работы и официально не считаются частью системы. Такие случаи выходят за рамки проекта, и соответствующие программы часто пишут на другом языке.
       В результате нередко можно встретить “систему”, состоящую из множества программ, написанных на четырех или пяти различных языках.
       Рассмотрим теперь сопровождение такой “системы”. Ее коррекция потребует специальных знаний (а точнее, знания различных языков). Обстоятельное обсуждение важности языков высокого уровня читатели найдут в литературе [3, 41, 42, 46, 58, 59].
       Следует заметить, что сам по себе язык без компилятора не является средством сопровождения программного обеспечения. Об этом говорится в следующем разделе.

Компилятор
       Самым необходимым и подчас единственным средством сопровождающего программиста является компилятор [2, 41].
       Каждая часть программного обеспечения существует в виде “логического образа”. Заказчик формулирует задачу с помощью спецификаций. Разработчик придает “образу” осязаемую форму, оговорив в деталях все части задачи и определив границы между ними. Вырабатываются требования к языку, который следует применить при решении задачи. Устанавливается тип машины, которая будет использоваться. Разработчик предлагает свое решение задачи, и все множество ограничений и требований к обеспечению материализуется в программе. Насколько хорошо программа отвечает своим задачам, судить приходится обычно многим людям.
       Будь то программа, написанная на ЯВУ, или это — набор команд в кодах машины, в конечном счете появляется исходная программа, которую компилятор преобразует в рабочую программу, а затем машина выдает ее распечатку {листинг программы). Поскольку листинг программы представляет собой последовательность команд машины, выполнение которых приводит к решению, опытные программисты считают его реальной программой. Когда в программу необходимо внести исправления, то они обычно вносятся в листинг, выведенный либо на бумагу, либо на экран дисплея.

Ввод Обработка Вывод
1. Прикладная программа(ы), написанная на каком-либо языке программирования 1. Перевод (трансляция) исходной программы на язык машины 1. Объектный модуль, получаемый в результате трансляций исходной программы (программ)
2. Листинг исходной программы, дополненный: а) соответствующими заголовками, включая идентификацию версии компилятора, время/дат у компиляции, имя программы; б) нумерацией строк; в) диагностикой ошибок; г) таблицей перекрестных ссылок; д) распределением памяти; е) результатами компиляции, включая распределение памяти, внешние ссылки, ресурсы, используемые в процессе компиляции
3. Файлы, используемые средствами интерфейса, если они есть
Пример: Программа, написанная на языке Ада, вводится в компилятор вычислительной машины VAX. Выходом компилятора является объектная программа (объектный модуль), допускающая ее перемещение и дальнейшую обработку на микро-ЭВМ Z8000, и вспомогательный листинг.

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

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

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

       Однако существующие редакторы связей слишком часто налагают ограничения на возможности программиста. Например, они ограничивают пользователя шести- или восьмизначными внешними именами. Такая практика сохраняется еще с тех пор, когда Фортран и Ассемблер были единственными языками программирования. Но теперь, когда появились языки, позволяющие присваивать более длинные и легкочитаемые имена, а именно в них больше всего нуждаются сопровождающие программисты, необходимо, чтобы и редактор связей мог работать с более длинными именами. Несоответствие должно быть устранено. Например, для недавно появившегося в Министерстве обороны США языка Ада, обслуживающего три области техники, требуется редактор связей, возможности которого по обработке имен согласовывались бы с соответствующими возможностями языка [41]. Таким образом, мы находимся на пути к устранению этого недостатка.
       Необходимо также иметь возможность получать таблицу перекрестных ссылок всех имен, известных системе (а не только внешних). Очевидно, что редактор связей может собрать и обработать таблицы имен, полученных от компилятора путем отдельных трансляций, с тем чтобы получить полную таблицу перекрестных ссылок. Однако это редко удается.
       Существует и еще одна функция, которая может выполняться редактором связей, но редко выполняется им. Она заключается в проверке списков параметров. Использует ли вызывающая подпрограмма ту же последовательность вызовов, которая предусмотрена в вызываемой подпрограмме? Для внешних программ такую проверку может обеспечить только редактор связей.
       Итак, сопровождающий программист имеет инструмент редактирования связей, который, прежде чем стать полезным, претерпел длительную эволюцию.

Операционная система/исполнительная программа
       Как операционная система, так и исполнительные программы обычно создаются одновременно с техническими средствами и, взаимодействуя с ними, позволяют вычислительной машине выполнять свои функции. Они предоставляют пользователю много услуг.
       Различие между ОС и исполнительными программами обычно зависит от использования системы. ОС применяются в том случае, когда вычислительная машина используется для общих целей: имеется много пользователей с различными требованиями к вычислительным возможностям машины. Услуги, предоставляемые ОС, состоят в управлении ресурсами, управлении данными и управлении задачами. Язык, используемый для связи с ОС, обычно грубый, низкого уровня и ничем не напоминает язык высокого уровня или даже Ассемблер.

Ввод Обработка Вывод
1. Прикладные программы и исполнительные системы 1. Технические возможности, предоставляемые машиной прикладным программам: а) плановое обслуживание; б) распределение ресурсов; в) возможность прерываемого обслуживания 1. Правильно выполненные прикладные программы
Пример: Система PAYROLL реализована на вычислительной машине CYBER. Операционная система ЭВМ CYBER устанавливает очередность выполнения программ, начинает выполнение, обеспечивает ввод/вывод, контролирует другие выполнения, пока система находится в режиме ожидания, и заканчивает выполнение.

       Программы, выполняющие точно такие же или подобные функции в режиме реального времени, называются исполнительными программами. Исполнительная программа обычно специализируется на конкретном приложении. Хотя между операционной системой и исполнительной программой нет никаких принципиальных различий, на практике они представляют собой две отдельные части программного обеспечения, которые включаются в работу, исходя из потребностей области применения.
       Для сопровождающего программиста скорее представляет интерес способ, которым операционная система либо исполнительная программа предоставляет услуги, необходимые пользователям. Возможно, между двумя рассматриваемыми типами ЭВМ и их операционными системами существуют различия в этом вопросе. Но над физической связью с операционной системой пользователь задумываться не должен, и такие детали, как драйвер, стек, очередность, буферизация и т.д., не должны быть ему известны. Все, что ему необходимо, — это лишь удобный доступ к тем функциям, которые требуются для решения его задачи. Степень достижения этой цели в значительной мере определяет, насколько убодно сопровождающему программисту осуществлять изменения программ. Это также влияет на интерфейсы операционной системы. Часто удобство сопровождения требует выделения интерфейсов в модули прикладных программ, с тем чтобы, если понадобятся изменения, их можно было бы внести только в эти модули.

Встроенные средства отладки
       В любой большой программе должна быть предусмотрена возможность получения информации о ее внутреннем состоянии. С помощью большинства методов доступа общего назначения невозможно получить эту информацию в таком формате, который позволил бы немедленно оценить состояние программы. Поэтому значительная часть программы должна содержать специализированные средства доступа, позволяющие сопровождающему программисту “увидеть” данные внутри программы в ходе ее выполнения. Роль таких специализированных средств для ключевых переменных могут выполнять операторы печати, а в более общем случае — программы трассировки или операторы, которые выдают информацию о нарушении условия (например, если РАЗМЕРА 100, то результат неверен).
       Очевидно, что нормальное использование программы встроенных средств отладки не потребует. Более того, при работе системы в реальном масштабе времени они требуют дополнительных затрат памяти и времени счета, что нежелательно. Но сопровождающий программист должен иметь возможность провести дополнительный анализ ошибок [35, 41].
       Обычно такую возможность предоставляют программы, которые печатают необходимые данные в символической форме (например, СКОРОСТЬ = 55). Этими программами управляют с помощью параметров. При обычных условиях работы программы управляющий параметр не используется (находится в состоянии “off”). Когда сопровождающему программисту информация нужна, он просто осуществляет выполнение того же самого задания, переводя управляющий параметр в состояние “on”.

Ввод Обработка Вывод
1. Отладочные данные, получаемые в ходе работы программы: а) трассировка данных; б) трассировка логики; в) неправильные результаты 1. Распечатка данных в виде, удобном для чтения 1. Распечатка требуемой ин формации либо в составе выводимой программы, либо в виде отдельного файла: а) изменяемые имена и их величины; б) индикация выполняемых логических элементов программы; в) диагностика неправильных результатов
Пример: Программа, написанная на языке Ада и ранее скомпилированная, не проходит тест. Программист, используя трансляцию, вставил в исходный текст программы отладочные команды для трассировки переменных ALPHA и BETA и входов процедур Он также определил диапазон изменений переменных BETA и GAMMA и просит, чтобы были выданы все те их значения, которые не входят в этот диапазон. Для компиляции (с использованием средств условной компиляции), редактирования и повторного выполнения программы необходима программа отладки, которая обеспечивает трассировку и диагностическую информацию. Программист получает результаты отладки, которые помогают ему решить задачу.

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

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

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

       Необходимым условием применения компаратора является подобие двух данных, которое и обеспечивает их сравнимость. Сравнение яблок и апельсинов никогда еще не приносило пользы. (Не будет ли результатом этого сравнения лимон?)
       Возможность сравнивать две различные версии одной и той же программы и получать соответствующий листинг их различий может быть непосредственно использована специалистом по сопровождению. Обычно ему задают следующий вопрос: “В чем различие между вариантами X и Y этой программы?” или “Эта программа работала, а теперь перестала. Что изменилось?” Компараторы всегда готовы помочь ответить на эти вопросы.
       Анализ результатов тестирования — еще одна сфера применения компаратора. Сопровождающий программист выполняет два варианта программы с тестовыми данными и получает результаты. Он заинтересован в обнаружении всех тех мест, где имеются различия, вызванные внесенными изменениями. В результате может быть установлена причина этих различий.
       В обоих описанных выше случаях на выходе компаратора должна получаться информация, соотносимая с данными ввода. Например, при сравнении двух листингов желательно получить сравнительный листинг источников с отмеченными в нем различиями или отдельно список различий. Простые различия, как, например, порядковые номера перфокарт, могут быть опущены, а вот содержание строк исходной программы является важным различием и должно быть ясно отмечено. Было бы хорошо, если бы изменения формата строки исходной программы учитывались только на уровне опций. В любом случае и те различия, которые опускаются, и те, которые регистрируются, должны легко поддаваться описанию.
       При создании компаратора возникают следующие проблемы. Во-первых, мы должны создать либо очень “хитрый” компаратор на все случаи жизни, либо целую серию их, для каждого типа сравнения свой. Во-вторых, следует уделить внимание ресинхронизации процесса компарации, с тем чтобы из-за начального несоответствия все последующие не регистрировались. В-третьих, это средство, как и все остальные, должно быть экономичным в использовании.
       К счастью, уже существуют коммерческие программные средства, которые отвечают всем этим требованиям. Например, программа FILCOM для DEC 10 представляет собой универсальное средство, которое выдает выборочную информацию о различиях, одновременно выполняя ресинхронизацию. Она предназначена для сравнения исходных файлов (программ, вариантов тестов, результатов тестирования и т.д.).
       Пусть, например, два файла состоят из данных

I. А   и   II. А
   В           В
   С           D
   D           F1
   Е
   F

       соответсгвенно. В этом случае программа FILCOM выдаст на выходе

1) С
1) D
* * *
2) D

       ******* (Эта запись означает отсутствие символа С в файле 2)

1) Е
1) F
* * *
2) F1

       ***** (Эта запись сообщает, что на месте символов Е и F в файле 2 стоит символ F 1)

Редактор текста
       Редактор текста необходим, поскольку сопровождение предполагает внесение изменений в исходную программу, а с его помощью текст программы может быть дополнен, уничтожен или исправлен. Простейшими являются редакторы, обрабатывающие отдельную строку исходной программы, что позволяет вставлять или аннулировать отдельные записи. Применение подобных средств можно сравнить с работой механика, вынужденного чинить автомобиль, обходясь только отверткой. Когда требуется изменить один знак в строке, приходится заменять всю строку, при этом увеличивается вероятность возникновения других ошибок. Редакторы текста должны быть просты в обращении, обладать большими возможностями и позволять осуществлять взаимодействие с пользователем.
       Перемещение сегментов программ должно легко осуществляться с помощью простых команд редактора. Замена одной последовательности операторов, многократно встречаемой в программе, на другую должна производиться одной командой. А неизбежные ошибки, связанные с побочными эффектами, необходимо свести к минимуму и, где это возможно, следует выдавать соответствующие сообщения.

Ввод Обработка Вывод
1. Текст 2. Команды, управляющие текстом 1. Выполнение команд управления текстом: а) стереть информацию; б) вписать информацию; в) заменить информацию; г) распечатать информацию 1. Отредактированный текст
Пример: Переменная величина BETA, определенная в одном из модулей программы,, написанном на языке Ада, по ошибке используется в другом модуле, который также содержит переменную BETA. Кроме того, первоначальное значение переменной BETA присвоено неверно. Для того чтобы все BETA во втором модуле программы заменить на BETA LOCAL и исправить первоначальное значение переменной BETA, используется редактор текста.

       Ниже будут перечислены возможности, которые предоставляют программистам имеющиеся в настоящее время редакторы текста.
       1. Редакторы текста, ориентированные на обработку строки (выделяется строка, которую следует редактировать):
       а) стирание;
       б) вставка;
       в) замена;
       г) распечатка.
       2. Редакторы текста, ориентированные на обработку цепочек (выделяется цепочка, которую следует редактировать):
       а) поиск;
       б) замещение.
       3. Редакторы текста, ориентированные на обработку блоков (выделяются блоки, которые следует редактировать):
       а) копирование;
       б) замена.
       Как видим, им под силу почти любая манипуляция с исходным кодом, которая может понадобиться сопровождающему программисту. Например, их можно использовать при редактировании программы для того, чтобы:
       1. Заменить неправильную строку программы исправленной (замена).
       2. Вставить три новые дополнительные строки (вставка).
       3. Заменить все ссылки к переменной RMS на ROOT_MEAN_SQUARE (замещение).
       4. Обнаружить места всех ссылок на процедуру DIAGNOSTIC (поиск).
       5. Сохранить и старую, и новую версии программы (копирование).
       Некоторые редакторы текста выполняют и более интеллектуальные операции. Они, например, могут писать “программы”, состоящие из цепочек команд, где каждая из команд в цепочке вы^ полняет одну из вышеназванных функций. При возрастании интеллектуального уровня редактора текста возрастает и риск. Такой редактор может внести в исходный текст коренные изменения, которые могут оказаться либо нужными, либо нежелательными. И все же, несмотря на это, хороший редактор текста может стать лучшим другом и помощником сопровождающего программиста. Известно, что программисты питают самые теплые чувства к своим любимым редакторам.
       К счастью, фирмы, торгующие вычислительными машинами, предлагают вместе с аппаратурой программное обеспечение. При этом редакторы текста пользуются большим спросом, особенно для работы в режиме разделения времени.

Преобразователь формата

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


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

a) for I in 1.. FILE-TABLE SIZE loop
      if FILE_TABLE(I) = FILE_NAME then return I; end if;
      if FILE-TABLE(I) = NULL then
         FILE-TABLE(I): = FILENAME; return I; end if;
   end loop;
b) for I in 1.. FILE_TABLE_SIZE loop if
   FILE_TABLE(I) = FILE_NAME then return I;
   end if; if FILE_TABLE(I) = NULL
   then FILE-TABLE(I): = FILE_NAME; return I;
   end if; end loop;

       Содержание обоих текстов идентично, однако формат первого фрагмента читается легко, а второго — нет.
       Большинство программистов придерживается первого способа написания программы. Однако для тех, кто пишет программы по второму способу, для тех, кто работает с этими программами, и в особенности для тех, кто работает в режиме разделения времени, весьма желательно иметь автоматическое устройство, облегчающее чтение текстов “b”. Таким устройством и является преобразователь формата. Очевидно, что он должен располагать набором правил, определяющих, что и как следует преобразовывать.

Структурные схемы и языки проектирования программ
       Структурные схемы являются традиционным способом представления проектируемой программы. С появлением мощных языков высокого уровня структурные схемы были вытеснены из обращения и применяются в качестве черновой записи при разработке программы для уяснения ее структуры. (Такая структурная схема может быть использована для дополнительной проверки надежности программы.) Во всех остальных случаях, где требуется документирование программ, структурные схемы бесполезны (кроме случая, когда она физически соответствует исходному листингу). Большинство сопровождающих программистов не пользуются структурными схемами, так как последние быстро устаревают. Всю необходимую информацию они берут из листинга и постоянно ищут новые, более удобные способы представления разрабатываемых программ. В работе [15] этот процесс эволюции рассматривается с юмором. Особенно многообещающим с этой точки зрения является использование языка проектирования программы (ЯПП). Применение ЯПП в программах в виде комментариев более эффективно по сравнению со структурными схемами и позволяет сопровождающему программисту быстро разобраться в ее сути. Как уже говорилось в разделе, где описаны языки программирования, ЯПП должен быть совместим с языком программы с точки зрени7 соответствия правилам комментирования.
       Описание этапа проектирования программ заносится в измять ЭВМ. Во-первых, это гарантирует, что описание не будет потеряно (часто оно теряется). Во-вторых, сопровождающий программист будет иметь под рукой необходимые сведения о всех стадиях жизненного цикла программного обеспечения для анализа требований, а также для дальнейшего совершенствования и реализации программ.
       Часто работа средств, используемых для удобного представления программ, основана на применении одной из приведенных выше методик. Если возникла необходимость получить структурную схему программы, такое средство извлекает информацию о строении программы и выдает ее структурную схему в графическом виде. (Применение таких средств часто полезно лишь в том случае, когда комментарии к структурной схеме совпадают с комментариями к программе, что помогает описать ход ее работы.) Средства ЯПП считывают язык проектирования программы, подобно тому как компилятор считывает текст программы, и выдают на выходе: 1) результаты проверки ЯПП (нет ли лишних или недостающих элементов проектирования?), 2) таблицы перекрестных ссылок ЯПП и 3) листинг текста программы. Средство ЯПП является сравнительно новым достижением в наборе средств программиста.

Ввод Обработка Вывод
1. Представление текста программы в форме, читаемой вычислительной машиной: а) структурная схема — текст программы, дополненный комментариями, б) ЯПП-текст, имеющий вид программы 1. Структурная схема — подготовить графические представления программы
2. ЯПП-проверка состава языка проектирования программы
1. Структурная схема — вариант графического представления входного варианта программы
2. ЯПП-проект программы с преобразованным форматом и с результатами проверки
Примеры: а) Ряд преобразований привел к тому, что структурная схема программы PAYROLL устарела. Необходимо создать новую и верную структурную схему, б) Модификация BETA в программе, написанной на языке Ада, ее не корректирует. Становится ясно, что при проектировании программы допущена ошибка. Для того чтобы получить листинг проектируемой программы и таблицы перекрестных ссылок на BETA, используется ЯПП.

Средства верификации
       Мы уже говорили о том, что мир сопровождения, как в капле воды, отражает в себе мир создания программного обеспечения. Все задачи разработчика становятся и задачами сопровождающего программиста. Верификация программного обеспечения не является исключением. Сопровождающий программист крайне заинтересован в создании надежного обеспечения.
       В последние годы появилось большое количество средств и методов проверки. Они подробно описываются и обсуждаются в “Руководстве по сопровождению программного обеспечения”; там же есть ссылки на книгу Гласса (Glass, Software Reliability Guidebook, Prentice-Hall, 1979), в которой дается исчерпывающий анализ всех средств верификации. Поэтому в данной книге мы не будем повторяться.
       Другими источниками, вышедшими за последнее время и имеющими отношение к средствам верификации, являются следующие [6, 10, 12, 19, 20, 29 — 33, 37, 38, 53].

Ввод Обработка Вывод
1. Программа нуждается в проверке
2. Варианты тестов или критерии
1. Выполнение программы с использованием различных вариантов тестов или применением разных критериев 1. Результаты выполнения программы
2. Неправильные результаты
3. Анализ возникновения ошибочных результатов
Пример: Требуется узнать, учитывалась ли когда-нибудь в программе PAYROLL (ЗАРПЛАТА) разница в начале рабочего дня каждого сотрудника. Тестовый анализатор использует для получения результата специальную версию программы PAYROLL, которая позволяет подсчитать общее время работы по частям. Затем прогоняются все варианты тестов программы. В результате становится ясно, что разница в начале работы в программе не учитывалась.

Управление версиями
       Создатель программного обеспечения системы представляет себе свое детище как сверкающий монолит, совершенный и непогрешимый. Совсем иначе представляется эта система специалисту по сопровождению, которому приходится непосредственно иметь с ней дело. Любая система программного обеспечения существует в виде серии версий. Версии появляются из-за того, что всякую систему необходимо изменять. Вызваны ли эти изменения исправлением ошибок или изменившимися техническими требованиями, они неизменно повлекут за собой новые версии.
       Возникает вопрос: как уследить за этими версиями? Ответ на него непрост, потому что каждая версия состоит из большого числа модулей и каждый из модулей либо изменяется, либо остается неизменным при переходе от одной версии к другой. К тому же каждый модуль существует как минимум в двух формах — исходной и объектной; исходный модуль может меняться, в то время как объектный модуль остается неизменным (например, когда изменяется основная база данных, внешняя справка или файл копирования). Специалисты только начинают искать автоматические средства решения проблемы. Создана специальная дисциплина, называемая конфигурационным управлением, которая все еще находится в состоянии зарождения (разд. 4.2.4). Но уже появляется система автоматического конфигурационного управления, которую иногда называют системой управления версиями [7].

Ввод Обработка Вывод
1. Модули, входящие в состав системы 1. Выбрать те модули, которые составляют определенную версию, и создать ее
2. Проследить взаимоотношения между модулями — родственными модулями и их производными
3. Осуществить управляющий доступ к системе с помощью автоматизированного набора слов
1. Версия системы, состоящая из набора взаимно координированных модулей
2. Распечатанный список межмодульных отношений
Пример: Модули создаваемой версии программы PAYROLL отличаются от модулей текущей версии, которая в свою очередь отличается от предыдущей версии. Предыдущая версия содержала устаревшие вычисления вычетов системы социального страхования. Надо разработать систему, в которой бы исключалось применение как экспериментальных, так и устаревших модулей.

       Управление версиями должно прослеживать возможные изменения на двух уровнях: 1) на уровне модулей (какие модули взаимодействуют между собой, какие являются головными, а какие — производными) и 2) на уровне системы (какие модули следует собрать и объединить в специальную версию).
       Проблема управления версиями есть частный случай более общей проблемы базы данных. Каждый модуль должен быть соединен с соседними модулями двусторонней связью, с тем чтобы было ясно, из чего состоит система, и к какой системе относится данный модуль.
       Системе управления версиями может понадобиться механизм системной защиты. Обычно для большой системы программного обеспечения изменения могут вноситься лишь при определенных обстоятельствах (например, в случае одобрения специальной комиссией). Поэтому существует потребность в создании как относительно бесконтрольных экспериментальных версий (для целей проверки), так и строго контролируемых версий для производственных систем. Это наводит на мысль о необходимости выхода за рамки средств защиты, таких, как, например, пароли и механизмы блокировки.
       Проблема усложняется, если над одной системой одновременно работают несколько сопровождающих программистов. Могут ли они одновременно создавать перекрещивающиеся экспериментальные версии? Как это сделать, если они вынуждены работать отдельно друг от друга?
       Круг вопросов, которые должны решаться таким средством, как управление версиями, ясен. Однако разнообразие и сложность этих вопросов приводят к тому, что число общих решений минимально. Здесь, как и в других областях сопровождения программного обеспечения, остро ощущается потребность в разработке и внедрении передовых методологий.

Суперкомпилятор
       В предыдущих разделах говорилось о различных идеях совершенствования доступных сопровождающему программисту средств. В этом разделе попытаемся объединить все средства в одну систему, которую назовем суперкомпилятором. Насколько нам известно, такой системы пока еще нет. Существуют отдельные ее компоненты в виде трудносовместимых между собой программных средств, назначение которых до конца неясно; но некоторых частей пока нет вовсе. Возможно, суперкомпилятор будет реализован в будущем, когда сопровождению начнут уделять должное внимание.
       Чтобы составить представление о том, что такое суперкомпилятор, мы сначала должны определить базу, на которой он существует. Она состоит из вычислительной машины и множества работающих стандартных подпрограмм, которые все вместе называются операционной системой. Операционная система — это минимальное множество стандартных подпрограмм, необходимых для того, чтобы соединить физические возможности вычислительной машины с логическими возможностями, присущими программам или задачам, которые решаются на ЭВМ. Значит, суперкомпилятор будет управлять всеми программами в рамках системы программного обеспечения. Он “знает” каждую часть системы. В него заложены данные о всех множествах, которые действуют и над которыми совершаются действия. Он создает модули исходных программ, которые являются выражениями всех функций в форме, читаемой человеком; он “знает” базу данных, построенную на этих модулях; он “понимает” взаимоотношения между исходными модулями и их объектными модулями, которые представляют собой “несистематизированные” версии программ; он же обеспечивает объединение различных частей программ в более крупные, что мы называем систематизацией. Суперкомпилятор также допускает различные версии одних и тех же исходных /объектных/описательных модулей (дискрипторов) и позволяет производить подстановки одних элементов вместо других. Он отвечает за работу модуля. В случае прерывания суперкомпилятор сообщает о состоянии системы или о состоянии модуля в символической форме (читаемой человеком) в терминах исходной программы. О сбоях суперкомпилятор сообщает с помощью переменных, принадлежащих модулям и подпрограммам, в которых сбои были замечены. Он не указывает адрес, на котором произошел сбой, а сообщает, например, что программа XYZ дала сбой при обработке оператора 27 в процедуре ABRACADABRA, так как переменная ABC была использована в качесгве индекса со значением 19, превышающим допустимую величину индекса массива SMALL ARRAY.
       Кроме того, поскольку суперкомпилятор располагает сведениями о всех частях системы, он может информировать модули, которые обращаются к данным, о том, какие значения переменных уже были использованы, какие модули используют эти же переменные и т. д. Создание суперкомпилятора предполагает претворение в жизнь тех идей, о которых было сказано раньше в этой книге. К ним относятся средства, формирующие таблицы перекрестных ссылок, средства отладки, более сложные средства редактирования текстов, а также преобразователи форматов и средства верификации, о которых говорится в разд. 3.2.1.1. Суперкомпилятор способен превзойти любые наши ожидания, так как в процессе анализа программ, составляющих программное обеспечение, он собирает самую разнообразную информацию.

Ввод Обработка Вывод
1. Система программного обеспечения в целом 1. Скомпилировать и соединить отдельные части системы
2. Проанализировать перекрестные связи в системе
3. Произвести символическую отладку
4. Оптимизировать систему с точки зрения размера и/или времени выполнения.
1. Скомпилированный и отредактированный текст в системно-оптимизированной форме
2. Информация о связях системы
3. Символическая отладка, облегчающая выполнение программы
4. Текст программы
Пример: Необходимо перестроить программное обеспечение системы ABRACADABRA, написанное на языке Ада. Для получения объектного кода вместе с результатами символической отладки, которые облегчают проверку системы в диалоговом режиме, и с результатами проверки, которые позволяют проанализировать ее возможности, используется суперкомпилятор. Одновременно получается полный список всех: перекрестных ссылок системы.

       Суперкомпилятор должен иметь:
       1. Возможность объединять результаты компиляции для большого числа программ. Данные, являющиеся общими для всех программ, могут в дальнейшем анализироваться с точки зрения их влияния на систему.
       2. Возможность получать целые разделы программы (или системы) в символической форме (на ЯВУ), используя дамп (избирательного вывода) и модули описания.
       3. Возможность оптимизировать не только отдельные программы, но и всю систему.
       4. Возможность формировать системные таблицы перекрестных ссылок.
       5. Возможность создать матрицу, или древовидную структуру, или сеть, описывающую структуру всей системы.
       Как мог бы работать суперкомпилятор. Структура суперкомпилятора на данном этапе вполне предсказуема. Интерфейс операционной системы централизует все запросы компилятора к ресурсам и, если необходимо, управляет оверлейной структурой. На последующих этапах суперкомпиляции, часто перекрывающих друг друга, выполняются следующие функции:
       1. Обработка исходного текста — программа на исходном языке считывается и подвергается грамматическому разбору, создается таблица символов, в которой хранится вся информация, касающаяся имен, и файл на промежуточном языке.
       2. Оптимизация — текст на промежуточном языке просматривается с целью устранения лишних операций. В результате получается оптимизированная программа на промежуточном языке.
       3. Генерация кода — исправленная программа на промежуточном языке преобразуется в объектный модуль для данной вычисли тельной машины, причем производится локальная оптимизация, например распределение регистров.
       До настоящего времени считалось, что результат работы компилятора — это только оптимизированная объектная программа, дополненная соответствующими листингами, и данные диагностики. Сейчас становится ясно, что существует возможность получить и другие дополнительные данные.
       Мы уже упоминали о различных типах таблиц перекрестных ссылок. Они могут быть получены без каких-либо изменений в структуре обычных компиляторов, описанных выше. Устройство, обрабатывающее исходный текст, располагает достаточной информацией, для того чтобы выполнить эту функцию. А вот листинг перекрестных ссылок системы требует нарушения устоявшихся правил, и компилятор должен сохранять таблицы символов всех программных модулей системы, чтобы проанализировать их вместе, когда программа уже скомпилирована.
       Кроме того, сохранение таблиц символов может принести и другую пользу, если станет реальной возможность отладки программ, написанных на ЯВУ. В этом случае таблицы символов должны быть доступны во время выполнения программы, чтобы часть суперкомпилятора, отвечающая за отладку, могла получить необходимую информацию в символьной форме и сообщить программисту о возникающих проблемах. Таким образом, мы определили уже две причины, по которым таблицы символов компилятора должны являться его стандартным выходом.
       В действительности, кроме сведений такого рода, суперкомпилятор будет давать на выходе информацию, которую назовем дескриптивным модулем. Дескриптивный модуль будет ответственным за дополнительную (описательную) информацию, необходимую для всех последующих этапов суперкомпиляции; с помощью дескриптивных таблиц символов можно будет легко создать программу, инициирующую базу данных, которая содержала бы информацию, необходимую для проверки достоверности и проверки содержания программы. При предварительном задании большой базы данных потребность в программах такого рода часто возникает в системах реального времени при необходимости модификации базы данных без перекомпиляции всей системы.
       Кроме того, суперкомпилятор может обеспечить сопровождающих программистов такими средствами сопровождения, которые “порождают” программу для определенных целей. Порождение — это процесс спонтанной генерации программы, которая может выполнять некоторые дополнительные функции. Дополнение исходной программы соответствующим порождением приведет к тому, что она сможет, например, подсчитать время работы каждого сегмента ее логической структуры или проконтролировать утверждения, определенные программистом. Отметим, что средства, подобные порождению, легко включаются в традиционные компиляторы и поэтому являются кандидатами в функции суперкомпилятора.
       Еще одним классом средств, реализуемых на базе суперкомпилятора, являются препроцессоры. В то время как вышеупомянутые средства действуют во время или после традиционных стадий компиляции, препроцессор действует еще до них, переводя предложения какого-нибудь другого языка в форму, обрабатываемую компилятором. Например, “язык очень высокого уровня”, применяемый лишь в узкой прикладной области, либо “структурный язык”, являющийся версией базового языка, либо “язык скорописи”, представляющий собой упрощенную версию базового языка, могут быть переведены на базовый язык с помощью препроцессора, входящего в состав суперкомпилятора. (Предположим, что базовый язык имеет макросредства, для того чтобы можно было обобщить функции препроцессора.)
       Теперь, когда мы убедились, что компилятор способен выполнять значительно более широкие функции, чем принято считать, можно предположить, что он может послужить источником и других очень заманчивых идей. Пожалуй, лучше всего эти новые идеи суммированы в работе [51]. Недалеко то время, когда первый суперкомпилятор начнет свою работу. Не стоит удивляться, если мы узнаем, что первый суперкомпилятор используется для поддержки нового языка программирования Ада, разработанного Министерством обороны США [60].
       Интересные соображения по поводу суперкомпиляторов можно найти в работе [58], где обсуждается понятие “суперязык”.
       Проблемы суперкомпилятора. Как уже говорилось, на современном уровне состояния программного обеспечения суперкомпиляторы не используются. Частично это объясняется тем, что никто пока не попытался объединить в одном устройстве такое множество функций. Но будь эта причина единственной, можно было бы найти серьезного разработчика, который взялся бы за создание суперкомпилятора. Однако существуют и другие проблемы. Чтобы все стало ясно, уместно будет обратиться к истории создания компиляторов.
       Более 20 лет назад были сделаны первые попытки разработки суперкомпилятора ограниченного применения. Тогда был создан обобщенный промежуточный язык UNCOL (Universal Computer-Oriented Language) [48], облегчающий получение группы исходных компиляторов, связанных с группой генераторов кода посредством языка UNCOL. В этом случае любой язык, для которого был создан исходный компилятор, можно было бы использовать для работы на всех ЭВМ при наличии у них соответствующих генераторов кодов. Идея была блестящая, но она все еще ждет своего воплощения. Последние годы фирма JOCIT по внедрению средств компиляции, разрабатывающая С-компилятор, и, возможно, некоторые другие фирмы вплотную приблизились к решению этого вопроса. Проблема заключается в том, что мы не располагаем достаточными знаниями о промежуточных языках, чтобы создать промежуточный язык, совершенно независимый от исходных языков. При этом он должен быть независим от генераторов кодов, а также удобен для высококачественной оптимизации.
       По этой же причине мы пока не можем создать суперкомпилятор. Несмотря на то что этой проблеме уделяется серьезное внимание, много вопросов, касающихся именно промежуточного языка, остаются нерешенными. Они требуют дальнейших исследований. Кроме того, необходимо провести работу по стандартизации. По крайней мере таблица символов, являющаяся частью вывода дескриптивного модуля, должна иметь стандартизированное внешнее представление (при этом допустимо, чтобы внутреннее представление было для каждого компилятора специфическим). Тогда таблица перекрестных ссылок всей системы и (или) ее отладочный пакет оказывали бы поддержку группе суперкомпиляторов, предназначенных для различных языков, или сам суперкомпилятор мог бы обрабатывать больше одного языка.
       Итак, будущее принадлежит суперкомпиляторам. Частично проблему можно решить уже сейчас. Часть ее требует дальнейшего изучения. Однако в любом случае решение принесет большую пользу сопровождающим программистам.
       В следующем разделе речь пойдет еще об одном подходе к проблеме создания суперкомпилятора. В его рамках набор отдельно существующих мощных средств объединяется в “оборудование”. При условии что будет уделено особое внимание отдельным средствам и их интерфейсам, “оборудование” может выполнять функции суперкомпилятора. Такие системы, как мы увидим, уже существуют, хотя их возможности значительно более ограничены, чем те идеи, которые высказываются в этом разделе.

Ввод Обработка Вывод
1. Все материалы, необходимые программисту для создания системы 1. Все возможности, необходимые программисту: а) средства документирования; б) средства манипулирования текстом; в) средства анализа результатов проверки; г) средства конфигурационного управления 1. Готовая для работы и проверенная система программного обеспечения
Пример: Программист системы PAYROLL хочет получить окончательный (как он надеется) вариант программы со всеми исправленными ошибками и со всей новой документацией. Он вызывает редактор текста, генератор документации, компилятор, редактор связей, набор средств тестирования и анализа. При успешном завершении работы средств тестирования и анализа программы конфигурационного управления (с санкции программиста) замещают предыдущий вариант программы новым.

Рабочее место программиста
       В настоящее время все больше убеждаются в том, что для успешной работы как разработчик программного обеспечения, так и сопровождающий программист нуждаются в специальных средствах и оборудовании. В результате было создано рабочее место программиста (РМП) [21], а в Министерстве обороны США разработано вспомогательное оборудование программиста на Ада (ВОПА) [31]. В рамках ВОПА в распоряжение программиста-разработчика отдаются вычислительная машина и ряд относительно стандартизированных средств для работы с ней. С помощью этих средств возможно осуществлять:
       1) разработку документации;
       2) редактирование текста программ и вариантов тестов;
       3) синтаксическую проверку программ;
       4) анализ результатов тестирования;
       5) конфигурационное управление программами и базами данных;
       6) управление данными.
       Более мощные средства обеспечивают также:
       7) компиляцию, редактирование связей, выполнение и отладку программ;
       8) обобщенное и регрессионное тестирование систем. Обычно вспомогательные средства поддерживаются на ведущей ЭВМ, которая может быть или не быть той машиной, на которой будет работать система. Работающий сопровождающий программист выбирает ведущую ЭВМ в соответствии с требованиями к средствам. Зная заранее возможности выбранной ЭВМ и свои нужды, он может быть уверен, что независимо от назначения его программы она будет дополнена набором общих и эффективных средств.
       В концепции рабочего места программиста (РМП) основной упор делается на стандартную мини-ЭВМ (которую предполагается при необходимости подключать к большой ведущей ЭВМ для выполнения, например, компиляции). Это особенно удобно при вычислительных центрах, где создаются системы для различных мощных машин, не все из которых отвечают потребностям пользователя.
       В концепции ВОПА все средства содержатся на большой ЭВМ, включая сложные средства компиляции. Программы, полученные на ведущей ЭВМ, могут использоваться и на других машинах, в частности на микро- и мини-ЭВМ, предназначенных для решения частных задач.
       Очевидно, что раздел, посвященный рабочему месту программиста, обобщает содержание предыдущих разделов. Здесь представлена совокупность всех средств, необходимых сопровождающему программисту.

3.2.1.2. Методы администрирования над сопровождением программного обеспечения

       Одновременно с созданием системы программного обеспечения происходит создание и другой “системы” (имеется в виду набор средств), необходимой для того, чтобы разобраться в большом количестве бумаг, неизменно сопутствующих сопровождению программного обеспечения. Поэтому мы считаем необходимым создание полностью автоматизированной информационно-административной системы. Такие системы сейчас только начинают появляться. Частично об этот говорилось в выпусках по структурному программированию из серии “Библиотека в помощь программисту” (БПП) [1, 29], а также в литературе, посвященной рабочему месту программиста.
       С точки зрения контроля над изменениями эта система должна предоставлять:
       1) возможность доступа к отчетам об ошибках;
       2) возможность доступа к отчетам о внесенных в систему изменениях;
       3) возможность анализа взаимного влияния возникающих ошибок и связанных с ними изменений в системе, а также описание исключений;
       4) возможность докладывать о состоянии разработки (например, о предполагаемой дате окончания работ или о типах вносимых изменений);
       5) возможность создания инструкции пользователю.
       В отчете об изменениях содержится разнообразная информация, которая может быть полезна многим. Такая информация может быть представлена в виде листинга проблем по степени важности для пользовгпгеля, в виде данных о предполагаемом сроке окончания работ, в виде рекомендаций о возможных способах устранения неполадок. Такая информация может быть извлечена из базы данных отчетов об изменениях. Вопросы ведения документации и написания отчетов обсуждаются в разд. 3.1.2. Здесь достаточно будет сказать, что средства создания отчетов являются важной частью информационно-административной системы программиста. Например, взяв за основу файл отчетов об изменениях и дополнительную информацию, программист может подготовить отчет о положении дел, который явится исправленным и дополненным вариантом отчета о текущем состоянии всех проблем.
       Отчет о состоянии изменений, кроме вышеупомянутых требований контроля над изменениями в системе, включает в себя точно определенный набор данных, которые можно собрать и вручную, что, впрочем, делается очень редко. Эти данные должны включать:
       1) общее количество строк программы (конкретной) в системе;
       2) количество строк, измененных за отчетный период;
       3) взаимосвязь между модифицированными строками и вносимыми в систему изменениями;
       4) имя автора изменения;
       5) задания, полученные каждым сопровождающим программистом;
       6) общее ядро, использованное системой;
       7) нарушение модулем процедуры использования ядра.
       Такие отчеты необходимы не только пользователям и руководству, но и сопровождающему программисту, так как ему важно знать, на какой стадии работы он находится и что ему еще предстоит. Сопровождающий программист нередко задает себе вопросы:
       “Существуют ли ранее составленные отчеты по этой проблеме? Удалось ли мне решить задачу по-новому? Каким образом будут развиваться сделанные мною модификации?” Быстро ответить на эти вопросы помогут автоматизированные методы администрирования.
       Проблема разработки информационно-административных систем в помощь сопровождающему программисту требует дальнейшего изучения.

3.2.2. ТЕХНИЧЕСКИЕ ПРИЕМЫ СОПРОВОЖДЕНИЯ

       Сопровождение программного обеспечения все еще остается работой, требующей высокой квалификации. Она не имеет четкой грани между наукой и искусством. Как мы ни стараемся формализовать ее, пока не удается обойтись без учета личных качеств, которые должны быть присущи сопровождающему программисту (имеются в виду интуиция, догадка, “искусство” программирования).
       Так будет до тех пор, пока на практике не воплотят научные (или полунаучные) системные средства, описанные в разд. 3.2.1. А до этого сопровождающий программист будет вынужден изучать дампы, самостоятельно разбираться в листингах и пытаться соотнести результаты проектирования программ с требованиями и спецификациями.
       В следующем разделе речь пойдет о технических приемах сопровождения, которые должны оказать сопровождающему программисту помощь в его работе. Работа [12] поможет ему оценить, насколько какая-либо часть программного обеспечения будет поддаваться сопровождению.

3.2.2.1. Профилактика сопровождения

       Лучшим сопровождением программного обеспечения является его отсутствие. Это означает, что не было допущено ни одной ошибки, a все возможные изменения были заранее предусмотрены.
       Затем идет сопровождение, которое легко осуществляется благодаря проведенному этапу разработки программного обеспечения. Обе вышеупомянутые разновидности настолько хороши, что хочется написать о них еще раз. (Однако мы этого делать не будем, так что, пожалуйста, вернитесь назад и перечитайте начало раздела еще раз!)
       Очень важно, создавая программное обеспечение, руководствоваться идеей замены сопровождения системы ее развитием. Каждый шаг проектирования и реализации должен содержать в качестве своей неотъемлемой части цель — облегчить работу “следующему” в общей цепи. “Следующим” может оказаться проектировщик, работающий с требованиями, или программист, реализующий проект, или контролер, работающий с программой и спецификациями, или сопровождающий программист, замыкающий эту цепь, его работа зависит от всех предыдущих. И часто “следующим” может оказаться тот, кто работал на более раннем этапе. Таким образом, ключевым вопросом всего процесса разработки является: что нужно сделать для того, чтобы облегчить работу на последующих этапах?
       Ответ на этот вопрос прост: руководство должно увеличить объем затрат на сопровождение (вспомним рисунки гл.1). Пусть проектирование и реализация программного обеспечения обойдутся несколько дороже: это окупится за счет снижения стоимости сопровождения.
       Все сказанное можно свести к простой и известной истине: делай все хорошо с первого раза! Однако здесь необходимо дать точное и исчерпывающее определение тому, что значит это “хорошо”. Попытаемся это сделать, но прежде кое-что уточним.
       Этот раздел мы назвали “профилактика сопровождения”1 {Когда речь идет о качестве сопровождения, это понятие также называют “способностью поддаваться сопровождению” (“maintainability”) [4].}. Мы преднамеренно не воспользовались термином “профилактическое сопровождение”, так как он может ввести в заблуждение. Дело в том, что если в других областях возможен такой род деятельности, который направлен на улучшение продукции уже после ее создания, то в случае с программным обеспечением это невозможно. Материальная база ЭВМ, например, периодически проверяется с помощью раз и навсегда установленного набора тестов, чтобы выяснить, все ли функции ЭВМ исправны. Программное же обеспечение не ломается и не изнашивается, поэтому проверять, не сломалось ли оно, бессмысленно. Если в обеспечении имеется ошибка, это значит, что она была там с самого начала2 {Исключение составляют лишь ошибки, появившиеся при внесении новых изменений.}: она не может сама появиться или сама исчезнуть. Профилактика сопровождения — это работа, проводимая еще до того, как начнется жизненный цикл программного обеспечения. Профилактическое же сопровождение подразумевает работу, которая проводится уже во время сопровождения, чтобы сделать его более эффективным. Эта работа, возможно, тоже необходима, однако о ней речь пойдет в другом разделе нашей книги, где она будет названа иначе (“регрессионное тестирование”).
       Теперь мы можем обратиться к более подробному обсуждению понятия “профилактика сопровождения”. В последующих разделах мы расскажем о технических приемах, используя которые сопровождающий программист может облегчить задачу тем, кто придет следом, а значит, и себе самому.

Модульность
       Основным условием профилактики сопровождения является создание программы, состоящей из отдельных модулей. Это ни для кого не секрет. Все согласны с этим, и каждый убежден, что он использует модульный принцип написания программы, однако по непонятной причине у одних программистов получаются программы намного лучше, чем у других. Все верят в модульность, все стараются добиться ее. Остается только выяснить: что это такое?
       Дать ответ на этот вопрос не просто. Конечно, в литературе [31, 39, 40] имеются прекрасные определения этого понятия. Однако остается вопрос: как реализовать все сказанное в конкретном случае? Решение полностью зависит от индивидуальности программиста и, следовательно, от его мастерства. Это наглядно показано в литературе [34].
       В этом разделе мы попытаемся объяснить, что такое “хорошая модульность”, как ее добиться и распознать, а затем перейдем к рассмотрению примеров. Хорошая модульность — это в некотором роде шестое чувство программиста, которое невозможно подменить ни рассуждениями, ни советами, ни указаниями.
       Однако начнем с определения. Модульное программирование — это процесс реализации программного обеспечения в маленьких функционально-ориентированных блоках. Эти блоки называются модулями и обычно находят свое применение как подпрограммы или функции, или группы функций. (В некоторых языках их называют секциями или процедурами.) Каждый модуль предназначен для решения одной или нескольких функциональных задач: модуль может быть сопряжен с одним или несколькими местами в программном обеспечении системы.
       (Предыдущий абзац сам является образцом модульности. Он целиком заимствован из другой книги [14] и, таким образом, выполняет единственную функцию — дает определение модульности. В результате он является модулем информации, который может быть вызван еще несколькими другими книгами. Его полезность как модуля объясняется тем, что он рассматривает ограниченную область знаний в обобщенном виде. Если бы этот модуль содержал материал, относящийся только к конкретному тексту в другой книге, то он здесь утратил бы свое значение.)
       В современной литературе можно встретить и отрицательные высказывания о модульности. Существует направление (оно является частью течения “структурного программирования”), определяющее модуль по числу содержащихся в нем строк. Правила программирования нередко включают такие ограничения, как: “Программные модули должны состоять не более чем из X строк, где X чаще всего имеет значение от 50 до 100. Нам кажется, что определять модуль по числу строк неправильно, так как это уводит внимание программиста от функционального назначения модуля, фиксируя его внимание на размере. Несомненно, что многие удачные модули малы. Но и среди больших модулей есть много удачных. Очевидно, что маленький модуль легче понять, но несложный большой модуль тоже понять легко, в то время как маленький, но запутанный модуль понять трудно. Критерием здесь должен быть не размер, а функциональная принадлежность.
       Преимущество хорошей модульности становится очевидным особенно при сопровождении. Если необходимо изменить функцию модуля, надо изменить только этот модуль. Это почти не окажет влияния на работу остальной части программы. Если возникает необходимость в новой функции, то это можно осуществить созданием соответствующего модуля. Влияние на работу всей программы будет минимальным. (Практически проверка программы, построенной по модульному принципу, будет представлять собой последовательность вызовов существующих модулей. И если будет найдена ошибка, ее устранение пройдет в рамках одного или нескольких модулей. Кроме того, если модуль уже однажды проверен, его можно использовать в дальнейшем в других местах программы. Таким образом, внесенные изменения не только обходятся дешевле, но и повышают вероятность правильного выполнения программы.)
       Пожалуй, лучшим примером модульности является библиотека стандартных подпрограмм. Каждая подпрограмма такой библиотеки выполняет определенную задачу или группу задач. Программисту не приходится самому программировать функцию квадратного корня или что-либо подобное: эта работа уже сделана, и ему остается лишь воспользоваться результатом. Стандартная подпрограмма является самым быстрым средством “затыкания дыр” в лрограммном обеспечении.
       Но модульность следует понимать шире, чем просто библиотеку стандартных подпрограмм. Модули — это составляющие элементы программного обеспечения, однако модуль не обязательно должен быть универсальным. Он может быть элементом, который необходимо использовать только в нескольких местах определенной программы. Наличие нужного модуля может сделать программу легкой для сопровождения^ а его отсутствие, наоборот, такой трудной, что от нее придется отказаться. Например, соображения модульности при программировании обычно требуют, чтобы внешние интерфейсы, такие, как ввод /вывод, были выделены в отдельные модули. Несоблюдение этого требования намного усложняет работу сопровождающего программиста при внесении изменений в интерфейс.
       Модули могут принимать различные формы. Часто внешняя (“отдельно скомпилированная”) подпрограмма считается модулем. В языках типа Алгола внутренняя процедура (определенная в рамках большей программы) также может быть модулем. В Коболе и некоторых других языках модулем может быть секция или любой другой отдельный сегмент программы. А в наиболее современной форме база данных с ее кластером для манипулирования базами данных также может считаться модулем. Подчеркнем еще раз, что суть модуля состоит в присущей ему одному функции. Важное значение имеет связь модуля с данными. Наиболее распространенной формой такой связи является список параметров; в этом случае модулю известны только местные данные и формальные параметры. Эта форма связи хороша тем, что все данные, используемые модулем, идентифицируются и выделяются; при этом модуль не может оказать неожиданного побочного воздействия на вызывающую его программу. Это могло бы произойти, если бы модуль модифицировал глобальную переменную, величина которой была важна для той части программы, где произошел вызов модуля.
       Однако идея полного использования списка параметров не всегда достижима. Модулю может понадобиться доступ к большему числу данных, чем определено в списке параметров в каждой точке вызова. В таком случае связь может осуществляться через глобальные данные, а именно либо через общую область данных, либо через общий пул данных (термин “общая область” употребляется в Фортране, а термин “общий пул” — в языке JOVIAL и его производных для централизации описания данных, необходимых для всех модулей, составляющих программу. Сам по себе блок — общая область, и общий пул становится отдельным модулем или несколькими модулями программы).
       Учитывая эту потребность, в современные языки программирования часто включают понятие “кластер”. База данных и семейство процедур для работы с ней отделяются от остальной программы. Программа, которой нужны некоторые или все кластеры, должна обращаться точно к ним. Кластер сам принимает решение о том, какие данные и процедуры можно использовать, а какие нет. Таким образом снимается вопрос о побочных воздействиях [41, 42].
       Модули, которые связываются с данными только через список параметров, называются полностью замкнутыми. Заметим, что полностью замкнутый модуль легко может быть перенесен в другую программу, так же как библиотечная подпрограмма. Модули, которые связаны и с глобальными данными, называются частично замкнутыми. Частично замкнутые модули имеют такие данные, которые связывают их с телом программы и затрудняют перенос этих модулей в другие программы. Кластер, четко определяющий, какие его части могут быть заимствованы, и поддерживающий необходимую ему базу данных, сам является закрытым модулем.
       Однако о модульности, как нам кажется, сказано достаточно. Пора перейти к примерам удачной и неудачной модульности. Пример модульности. Предположим, что вы получили задание — определить систему управления файлом, которая будет применяться для поддержки одной или более прикладных систем доступа к файлу. После начального периода проектирования вы приходите к выводу, что решение задачи четко распадается на несколько задач по обслуживанию пользователя. Вы должны уметь: 1) создать файл; 2) уничтожить файл; 3) записать файл; 4) считать с файла; 5) найти в файле; 6) добавить к файлу и 7) удалить из файла.
       Для того чтобы выполнить эти задачи, необходимо иметь базу данных, в которой отражаются все изменения информации, содержащиеся в файле.
       Для удовлетворения всем этим требованиям вы создаете модули, каждый из которых будет выполнять перечисленные задачи. Например, процедура CREATE потребует на входе имя файла, который нужно создать, и даст на выходе исправленную и дополненную базу данных, включающую новый файл. Процедура SEARCH потребует на входе имя файла, в котором следует вести поиск, и ключ к той информации, которая должна быть найдена, а на выходе даст местонахождение файла и (или) позицию искомой в нем информации.
       Заметим, что каждая модуль-функция — это хорошо определенный круг задач, а значит, представляет собой достаточно простой участок программы. Таким образом, можно разбить сложную большую задачу на отдельные, легко решаемые задачи.
       На языке программирования Ада определение этих модулей будет выглядеть следующим образом:

Procedure CREATE (FILE_NAME) is 
begin
   -- выполнить все необходимое для создания файла
end CREATE;
function SEARCH (FILE-NAME; KEY_NAME) return
   FILE-JNDEX is begin
   -- выполнить идентификацию файла и поиск для
   -- данного ключа
   -- затем получить индекс искомого файла
end SEARCH;

       По мере написания функций становится ясно, что каждая из них должна обращаться к базе данных. Значит, мы имеем дело с кластер-ситуацией. База данных со своими подпрограммами образует отдельную группу информации со свойственным только ей одной и хорошо определенным набором связей.
       На языке Ада определение кластера, который в Ада называется пакетным модулем, будет выглядеть примерно так:

package FILE_MANAGER is
   -- определение сервисных интерфейсов пользователя
   procedure CREATE (FILE_NAME);
      -- другие функции управления файлом
   function SEARCH(FILE_NAME; KEY_NAME) return FILE_INDEX;
end FILE_MANAGER;
package body FILE_MANAGER is
   -- определение реализации сервисных средств пользователя:
   -- но пользователь не может видеть, как они реализованы
   -- объявление базы данных
   procedure CREATE (FILE_NAME) is
   begin
      -- программа, описанная ранее
   end CREATE;
   -- аналогично для всех других реализаций
   function SEARCH (FILE_NAME; KEY_NAME) return
      FILE_INDEX is
   begin
      -- программа, описанная ранее
   end SEARCH
end FILE_MANAGER;

       Здесь следует отметить два момента: 1) пакет на языке Ада, или: кластер, распадается на две части: внешний интерфейс и реализация: (что является само по себе хорошим примером модульности) и: 2) модульность в нашем примере становится иерархической, т.е. некоторые модули включают в себя другие модули. Это наилучшее проявление модульности: каждая функция четко отделена от всех, других функций, модульные взаимосвязи хорошо определены и исключая эти связи, модули полностью изолированы друг от друга.
       Теперь приведем пример, когда та же задача плохо разбита на модули. Рассмотрим несколько неудачных, но возможных вариантов:
       1. Вся система управления файлом может быть встроена в прикладную программу, которая ее использует.
       2. База данных может быть отделена от прикладной программы, но функции начинают выполняться без предварительной обработки: информации.
       3. Функциональные обслуживающие сервисные процедуры (создать, уничтожить и т.д.) могут иметь общие части, вместо тога чтобы быть оформленными как различные процедуры.
       Отрицательных примеров можно привести бесчисленное множество.

Структура программы
       В печати за последнее десятилетие широко освещалась проблема структурного программирования. Специалисты и неспециалисты в один голос провозглашали структурное программирование величайшим открытием с момента создания цифровых вычислительных машин. Однако это не так.
       Такое категоричное заявление, конечно, потребует объяснений. Прежде всего само понятие “структурное программирование” еще недостаточно четко определено. Хотя существуют попытки дать определение [1, 29, 59], но они не всегда последовательны и совместимы. Однако суть этого понятия довольно известна. Оно включает практику программирования с применением ограниченного, но достаточного набора элементов управления, о которых мы не будем здесь говорить, так как о них столько написано, что читателям они, несомненно, хорошо известны. В понятие структурного программирования включают также множество побочных соображений, таких, как применение бригад программистов, проектирования сверху вниз и библиотеки поддержки программ. Но суть остается прежней и заключается в применении того набора элементов управления, о которых вы уже читали и слышали раньше.
       Итак, что же плохого в структурном программировании? Ничего, кроме излишней рекламы, создаваемой вокруг него. Структурное программирование не является панацеей. Оно является очередным, более прогрессивным этапом развития программирования и в этом смысле достойно применения. Если расположить элементы сопровождения по степени важности, на первом месте будет стоять модульность, а почти сразу же за ней — структура программы.
       В контексте нашей книги под структурой программы мы лонимаем один из атрибутов программы, который позволяет лучше ее понять. Применение элементов структурного программирования делает программу легко читаемой и удовлетворяющей эстетическим требованиям. Операторы begin-end делят программу на законченные по мысли модули, блоки, предложения, что позволяет следить за логикой изложения, а значит, легче понимать ее. Отступы позволяют зрительно установить структуру программы. Это очень полезно, особенно для сложных программ. Но, пожалуй, еще важнее для структуры программы с точки зрения сопровождения последовательная система комментариев. Комментарии поясняют введение каждого нового элемента модульности и (или) структуры программы и тем самым улучшают ее форму и делают более удобной для чтения. О пользе комментариев мы скажем ниже.
       Читателю может показаться неясным различие между модульностью и структурой. Разве модульность не есть подход к структуре? Несомненно. Эти два понятия рассматриваются отдельно по следующим причинам. Исторически сложилось так, что модульность появилась лет на десять раньше структурного программирования. Мы считаем необходимым выделить модульность, чтобы ее не смешивали с нечетко определенным понятием “структурное программирование”. Такое разделение необходимо, так как в некоторых языках понятие “структура” относительно лишено смысла, в то время как понятие “модульность” существует во всех языках. Таким образом, граница между модульностью и структурой может показаться неявной, но она существует. Заметим, что хороший модуль не обязательно имеет хорошую структуру, а у плохого, наоборот, может быть хорошая структура. Значит, понятия “модуль” и “структура” следует различать.
       Где-то на границе между понятием “модуль” и “структура” лежит понятие “межмодульный интерфейс”. Сложные интерфейсы делают программу плохо понимаемой и неудовлетворяющей эстетическим требованиям; поэтому мы включаем в раздел и эту тему. Четкие интерфейсы — это еще элемент структуры программы.
       Здесь также следует привести примеры. В предлагаемом отрезке программы обратите внимание на атрибут структуры программы — элементы управления, группирование операторов begin-end, отступы, комментарии, интерфейсы, — чтобы иметь возможность судить о качестве программы.
       Пример структуры программы. В примере модульности мы рассматривали систему управления файлом. Функция SEARCH иллюстрирует одновременно понятие “хорошая структура программы” и взаимосвязь между “структурой” и “модульностью”. Наш пример является отрезком программы, написанным на языке Ада.

function SEARCH (FILE_NAME; KEY_TYPE) return
   FILE_INDEX is
begin
   -- поиск определяемого ключа записи в определяемом файле
   -- возврат индекса к прежнему значению
   for I in FILE_INDEX'FIRST..FILE_INDEX'LAST loop
      -- поиск всей базы данных
      if FILE_MAP(I) / = NULL
         -- проверка базы данных на нули и совпадения элементов
         and then FILE_MAP(I) = FILE_NAME
         and then FILE_KEY(I) = KEY_TYPE then
            return I;
      end if;
   end loop;
   raise BAD-FILE;
      -- расширить список исключении, если требуемая запись не найдена
end SEARCH;

       Данный пример иллюстрирует два элемента управления — цикл (for... loop ...end loop) и условный переход (if... and then...then... ...end if). Здесь также показано несколько групп (begin-end SEARCH, loop-end loop и if-end if), четыре уровня отступов и небольшие комментарии. (Интерфейсы были показаны раньше, когда речь шла о модульности.)
       Попытайтесь представить себе, что в этой программе отсутствуют отступы и комментарии. Теперь поставьте себя на место тех, кто обязан будет обеспечить сопровождение этой трудночитаемой программы. Впечатление от плохой структуры программы удручающее, не правда ли?

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

STRUCTURED ATA STRUCTURE; BEGIN
   ITEM CHARACTER_STRING 5 CHARACTERS;
   ITEM OTHER_VARIABLE 32 BITS;
END STRUCTURE;

       а затем управлять последовательностью с помощью имени:

OTHER_STRING = CHARACTER_STRING;

       Второй способ включает использование байтуправляющей функции для доступа к нужной последовательности:

OTHER_STRING = BYTE (CHARACTER_STRING,5);

       Предположим теперь, что в программе происходит стократное обращение к последовательности символов, а число символов в последовательности необходимо увеличить с пяти до девяти. Как это ловлияет на сопровождение? Очевидно, что в первом случае для этого надо изменить только одну легкообнаружимую строку программы, в то время как второй случай предполагает изменение уже 100 строк, которые к тому же не так легко найти. Значит, влияние структуры данных на сопровождение может быть очень существенным.
       (Приведенный пример выбран не случайно. Читатели помнят о вышеупомянутых трудностях, вызванных переведением Почтового ведомства США плана почтовых зон на более длинные программы. Совершенно ясно, что, если бы в прошлом уделялось большее внимание структуре данных, этот переход прошел бы почти безболезненно.)
       Из данного примера видно, что структура данных важна сама по себе и имеет влияние на выполнение программы. Этот пример можно считать обобщенным: языки Кобол и ПЛ/1, например, обладают наилучшей структурой данных, так как имеют тонкую, ориентированную на редактирование структуру данных, наиболее удобную для выведения их на печать в нужном формате. (Простая команда пересылки, к примеру, может в результате устранить ведущие нули, вставить десятичную точку, добавить контрольные символы и поместить знак доллара в случае, если цель пересылки соответствует описанию.) А современные формы Фортрана и Бейсика, напротив, не представляют никакой структуры данных, кроме массива. (Это, пожалуй, самый серьезный недостаток Фортрана.)
       Дихотомия структуры данных в отличие от дихотомии структуры программы, как мы уже отмечали, принимает различные формы. Она затрагивает даже область проектирования программного обеспечения. Одна из недавно появившихся методик, которую называют методикой Джексона [22], предлагает проектировать структуру программы, исходя из структуры данных. По Джексону, структура алгоритма тесно связана со структурой данных, и программы, спроектированные с учетом этого, будут наиболее удобными для сопровождения. Очевидно, что в разных областях применения потребность в структурной ориентации данных может быть различной. Не менее очевидно и то, что в тех областях, где структурная ориентация необходима, потребность в ней особенно велика.
       Одной из наиболее характерных черт большой системы является алгоритмическое преобразование одной структуры данных в другую. Этот процесс иногда проистекает из так называемого “конфликта структур” (термин Джексона), а иногда является следствием неопределенного разрастания системы или погрешности проектирования. Во многих больших системах, претерпевших значительное число доработок, данные подчас с небольшими изменениями преобразуются по нескольку раз, пока наконец не получается именно то, что соответствует текущим требованиям. Чтобы остановить развитие этого процесса, необходимо заново проанализировать задачу. При проектировании промежуточных структур данных следует помнить о конечной цели использования информации. Программы, имеющие структурированные данные, требуют в иерархии профилактики сопровождения максимального внимания к их структуре.
       Не менее важно правильно группировать данные. В случае, когда описания набора данных взаимосвязаны функционально или по содержанию, для простоты восприятия и модификации их следует объединять в группы. Например, данные, называемые в Фортране “общими”, а в JOVIAL — “общим пулом”, следует объединить в один большой общий блок или общий пул-блок. Если потребуется изменение, его можно будет внести только в этот блок и провести повторную компиляцию только тех модулей, которые используют этот блок.
       Концепция типизации абстрактных данных, так же как и метод структурирования данных, требует повышенного внимания. Идея этого понятия состоит в том, что все данные должны быть описаны как принадлежащие к определенному типу; типы определены в языке, кроме того, есть типы, дополнительно определенные программистом. Для каждого типа существует набор допустимых величин, значения которых могут принимать данные этого типа, а также набор операций, которые можно проводить с этим типом данных.
       Разделение на типы имеет следующие преимущества: 1) свойства типа определены в его описании, и если они изменяются, то коррекции будет подвергаться только это описание; 2) справочные данные должны содержать только абстрактные свойства типа, а подробности реализации будут содержаться в описании; 3) строгое соответствие между типами может быть обеспечено компилятором, что исключает ошибки, вызванные их несовместимостью, облегчает сопровождение и повышает надежность.
       Однако возможность полного разделения данных на абстрактные типы обеспечивают только самые современные языки, например Ада и Паскаль. Традиционные Кобол, Фортран и Алгол этого не позволяют делать. Причем в тех языках, где эта возможность отсутствует, действенного способа обеспечить ее не существует.
       И наконец, важно эффективно именовать данные. Конечно значимые имена здесь более уместны, нежели произвольные. Как вы думаете, что означает имя EMPLOYEE_NAME? A EMPNAM?
       Здесь, однако, содержится противоречие. Иногда современные стандарты программного обеспечения требуют, чтобы данные были поименованы не в соответствии с их содержанием, а в соответствии с их структурой. Например, элемент данных, который определен в модуле XYZ и является частью таблицы TAB, можно было бы назвать XYZ_TAB_1. Это дало бы определенное удобство: сопровождающему программисту было бы ясно, откуда взяты данные. Однако мы считаем, что это затрудняет чтение программ. В данном случае возможен компромисс, например EMPLOYEE_NAME_XYZ_TAB включает как структуру, так и значение. Правда, такое наименование может оказаться для программиста слишком длинным. (Здесь может помочь хороший редактор текста. Программист мог бы кодировать короткие имена, а затем с помощью команд для каждого имени производить подстановку более длинных имен.) Часто легкочитаемые (длинные) имена употребляются для глобальных данных, а короткие — для менее важных локальных данных. Таким образом, вопрос удобства наименования часто связан с вопросом его длины.
       Пример структуры данных. Предположим, что в рассмотренном ранее примере управления файлами каждый файл имеет свой особый формат. Все записи в файлах имеют (возможно) один и тот же размер, но разные форматы, и, как это обычно бывает, они разнородны по составу, т.е. содержат всевозможные типы данных: последовательность знаков, целые величины, данные с плавающей точкой и т.д.
       Такого рода структуры легко представить в одних языках и невозможно — в других. Ниже мы даем пример структуры данных на языке Ада. Заметим, что такой тип структуры, называемый Ада TYPE, используется в языке Ада для создания нескольких образцов этой структуры.

type RECORD_FORMAT_1 is 
   record
      EMPLOYEE_NAME: array (1..30) of CHARACTER;
      EMPLOYEE_RATE: float;
      EMPLOYEE_HOURS: integer range 0..99;
   end record;
FILE1-RECORD RECORD_FORMAT_1;

       Формат записи для файла 1 определяется подобно типу записи format_1; а он в свою очередь состоит из 30 знаков, числа с плавающей точкой и двухразрядного целого числа. Заметим, что каждое имя выбирают таким образом, чтобы оно давало ясное представле ние о содержании данных (это свойственно для языка Ада, так как в нем даже тип имеет описательное имя в соответствии с данными). Понятие “группирование данных”, возможно, лучше всего пояснить с помощью концепции кластера, о котором говорилось выше. Предположим, что программа управления файлом содержит таблицу описания файла, его текущий индекс, а также таблицу связи файлов с их физическим местоположением. Очевидно, что эта и вся остальная информация о базе данных файла должны быть сгруппированы вместе, так как они тесно взаимосвязаны между собой. Если в процессе сопровождения системы изменяются какие- либо данные, следует пересмотреть все сгруппированные данные с точки зрения влияния на них этого изменения; и еще более важно, чтобы это изменение не повлияло на данные других группировок.
       База данных, управляющая файлом, может быть сгруппирована с дополнительными функциями, которые мы обсуждали ранее (например, СREATE и SEARCH). Это дало бы полное описание содержания базы данных и функции управления файлом.
       Описание этой группы данных на языке Ада будет иметь вид

package FILE_MANAGER is
   -- здесь, как и раньше, определен весь сервис пользователя
end FILE_MANAGER;
package body FILE_MANAGER is
type FILE_DESCRIPTION_TABLE_FORMAT is
   record
      FILE_NAME: array (1..10) of CHARACTER;
      FILE_KIND: integer range 0..4;
   end record;
type FILE_LOCATION is
   record
      FILE_NAME: array (1..10) of CHARACTER;
      FILE_POINTER: integer range 0. .32767;
   end record;
   FILE_TABLE: FILE_DESCRIPTION_TABLE_FORMAT;
   FILE_INDEX: integer rang 0..NUMBER_OF_FILES;
   FILE_LOC: FILE—LOCATION;
   -- далее, как и раньше, следует реализация сервиса пользователя
end FILE_MANAGER:

       После этого структура данных группируется с другой программой, которой она требуется.

Язык высокого уровня
       Мысль о том, что язык высокого уровня лежит в основе решения любой задачи программного обеспечения, почти превратилась в аксиому. Сопровождение программного обеспечения, в том числе и профилактика сопровождения, не является исключением. Что касается вопроса структуры программ, то он так хорошо освещен в литературе, что не имеет смысла повторяться [25, 59]. Говоря о перспективах данного вопроса, интересно отметить, каким образом можно поддержать все имеющиеся методы профилактики сопровождения с помощью языков высокого уровня (ЯВУ).
       Структура данных, описанная в предыдущем разделе, совершенно невозможна без поддержки хорошим ЯВУ (использование массивов для моделирования древовидных структур и упаковка данных без ее описания крайне неудобны). Структура программы в меньшей степени зависит от ЯВУ, но и она на Ассемблере (даже при использовании макрокоманд) в лучшем случае неуклюжа. А вот модульность в равной степени возможна и в ЯВУ, и в Ассемблере. Большинство программ, подвергаемых анализу с точки зрения профилактики сопровождения, не зависит от ЯВУ, в то время как документация выигрывает во многом, так как программы на ЯВУ легко читаются.
       Надо также отметить, что не все ЯВУ одинаковы. Фортран в том виде, в котором он применялся до 1977 г., по числу пользователей является вторым языком после Кобола. Однако для профилактики сопровождения в качестве ЯВУ он плох. Фортран предоставляет возможность составления модулей, но ограничен с точки зрения структуры программы, структуры данных или параметризации (о которой речь пойдет ниже). В языках, созданных на базе Алгола, эти недостатки устранены. Кобол при всех достоинствах его структуры данных имеет ограниченные возможности для модульности структуры программы. Тот факт, что Кобол и Алгол по-прежнему широко применяются в наши дни, указывает на пренебрежение интересами сопровождения при выборе языка.
       Примеры программ, написанных на ЯВУ, можно найти в справочниках по языкам программирования. Примеры самых лучших ЯВУ можно найти в литературе [24, 27].

Параметризация
       Мелочи иногда имеют большое значение. К таким мелочам относится параметризация. Это понятие достаточно простое. Если для обозначения одного и того же множества в нескольких местах программы употребляется константа и ее, возможно, применят еще не раз, ей присваивают имя и употребляют это имя всякий раз, когда ссылаются на эту константу. Это может иметь значение только в том случае, если константа изменится. Тогда необходимо будет изменить только определение имени, не затрагивая ссылок. (Отметим сходство этого приема с подобным же приемом в структуре данных. Обобщая, можно сказать, что любой прием, позволяющий объединять переменные множества в декларации, является полезным для профилактики сопровождения.)
       Не все языки предоставляют такую возможность в равной степени. Как ни странно, чаще она имеется в языках типа Ассемблера, а не в ЯВУ. Например, в ассемблерных языках почти всегда имеется макровозможность “equals” или “synonymous” (ARRAY_SIZE_EQU_10), но в языках высокого уровня она встречается редко. Например, язык военно-воздушных сил США JOVIAL представляет ограниченную макровозможность DEFINE: DEFINE ARRAY'SIZE “10”. Как видим, и в данном случае современный язык не учитывает интересы сопровождения.
       Так же совершенно недостаточна способность языка поддерживать динамические константы (которые могут быть инициированы с помощью оператора присвоения или какого-либо другого способа). Параметризацию нельзя считать полной, если во время компиляции величина поименованной постоянной неизвестна и не используется. Заметим, что динамическая константа не обязательно должна использоваться в операторе описания, в то время как параметр может быть использован в нем, например DECLARE TABLE (TABLE'SIZE). Рассмотрим пример параметризации.
       Пример параметризации. В практике применения ЭВМ существует фундаментальная задача обнаружения ситуаций, когда возможности программы исчерпаны. Слишком часто вместо решения этой проблемы ее просто игнорируют (исчерпав возможности программы, машина выбрасывает ее неожиданно для программиста).
       Чаще, к счастью, получается так, что программа сама выявляет возможную аномальную ситуацию. При этом печатает предупреждение и производит регенерацию (например, деликатно прекращает работу или в случае потенциального переполнения переключает указатель области данных на возможно более безопасное положение).
       Вопрос стоит так: как лучше всего с позиции сопровождения обнаружить подобные ситуации? Легко предположить, что ответ связан с параметризацией.
       Рассмотрим пример на языке Ада:

FILE_TABLE_SIZE: constant integer: = 15;
FILE_TABLE: array (l..FILE_TABLE_SIZE) of FILE_LOCATION;
  -- предположим, что FILE_LOCATION является типом записи, определенной ранее
function ADD(FILE_NAME) return FILE_INDEX is
begin
   -- добавить файл к таблице файла
   for I in 1..FILE_TABLE_SIZE loop
      if FILE_TABLE(I) = FILE_NAME then return I; end if;
      if FILE_TABLE(I) = NULL then
         FILE_TABLE(I):= FILE_NAME; return I; end if;
   end loop;
   raise TABLE_OVERFLOW;
end ADD;

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

Обмен данными
       Программистам доступны три способа обмена данными в рамках внутрипрограммного обмена данными. (Обмен данными мы определяем как средство передачи данных от одного модуля к другим.) Первый способ заключается в использовании списков параметров. Второй — в применении глобальных или общих данных. А третий связан с только нарождающимся понятием “кластер”. В этом случае “полуглобальная” база данных используется совместно с ограниченным числом программ [41, 42].
       Межпрограммный обмен данных также важен. Обычно он осуществляется путем передачи флажков или блоков данных через общую область, обеспечиваемую операционной системой, или путем передачи более значительных объемов информации файлами. Идея так называемого трубопровода, по которому идет обмен файлами между программами, высказанная создателями системы UNIX, весьма привлекательна, но все же она является упрощенным решением задачи [45].
       Выше мы обсуждали вопрос обмена данными в разд. “Модульность”. Здесь мы снова возвращаемся к этой теме просто для того, чтобы подчеркнуть ее самостоятельное значение для профилактики сопровождения. Как уже отмечалось, обмен параметрами является, строго говоря, наиболее предпочтительным способом обмена данными. Но практика часто требует применения глобальных (общих) данных, особенно для больших программ, а кластер нередко выручает там, где появляются затруднения, связанные с неограниченным применением глобальных данных. Здесь мы не приводим соответствующей оценки достоинств межпрограммных связей.
       Далее дается пример обмена данными.
       Пример обмена данными. Давайте вспомним, что процедурам CREATE и SEARCH для работы требуется ряд параметров. Процедуре CREATE необходимо сообщить имя файла, который следует создать, а процедуре SEARCH — имя файла и имя ключа, который надо отыскать в файле. Эта информация передается процедурам наиболее предпочтительным способом — в виде параметров. Допустим, имеем вызовы CREATE(FILE1); SEARCH(FILE1, KEY1); которые требуют определенных услуг для FILE1 и KEY1.
       Если бы в системе был только один файл (что маловероятно согласно постановке задачи, но возможно), то не было бы необходимости именовать его в списке параметров. А если бы там была глобальная переменная, названная KEY1, которая могла бы быть установлена пользователем процедур CREATE и SEARCH и была доступна этим процедурам, мы имели бы

CREATE;
KEY1: = SOMETHING;
SEARCH;

       Программа теперь более лаконична, но менее описательна. И в этом случае есть вероятность того, что на переменную могут повлиять другие части программы. Например, если предположить, что программист собирается использовать значение переменной KEY1, установленное ранее, то

CREATE;
KEY1: = SOMETHING;
SEARCH;

       В этом случае, если пассивная часть программы, обозначенная через “...”, содержит инструкцию присвоения для KEY1 либо в первоначальном варианте программы, либо в ее последующей модификации, то процедура SEARCH будет ошибочна. По этой причине использование глобальных данных нежелательно.
       И наконец, предположим, что имя файла и имя ключа не подлежат передаче в память в качестве параметров. В этом случае нужно осуществить блокировку памяти. Такую возможность предоставляет кластер.

package FILE_MANAGER is
   -- здесь определены услуги пользователю
   -- KEY1 здесь не определен и, таким образом, не доступен извне
end FILE_MANAGER;
package boby FILE_MANAGER is
   KEY1: integer;
   . . .
   -- здесь определено применение услуг пользователю
   . . .
   KEY1: = SOMETHING;
   . . . 
   SEARCH;
   . . . 
end FILE_MANAGER;

       Вероятность того, чю переменная KEY1 попадет ошибочно в память машины в промежутке между присвоением ей значения и вызовом процедуры SEARCH, теперь значительно уменьшилась, так как никакие операторы, лежащие вне кластера, на нее повлиять не смогут. Кластер позволяет воспользоваться глобальными данными, но при этом требуется блокировка памяти.

Защита программы от возможных ошибок
       “Если ошибка может возникнуть, то она непременно возникнет”. Эти слова должны быть написаны большими буквами над рабочим местом программиста. Если учесть, что работа программиста состоит в том, чтобы давать утомительные, подробные указания немой машине, легко понять, что здесь имеются большие возможности для совершения ошибок.
       Приходилось ли вам когда-нибудь кому-нибудь объяснять дорогу к какому-нибудь месту? А теперь представьте, что этот человек слепой. Поразмыслим над разницей. А количество деталей, необходимых машине, еще весьма далеко от количества деталей, необходимых слепому. Опытный программист заранее знает, что будут ошибки, некоторые из которых проявятся только через годы. Он пишет свою программу таким образом, чтобы иметь возможность самому обнаружить ошибки сразу или же чтобы их обнаружил и устранил сопровождающий программист. Это мы назовем здесь обобщающим термином защита программы и проведем аналогию с “безопасным вождением” автомобиля, которое подразумевает повышенное внимание водителя к возможным опасностям.
       Защита программы от потенциальных ошибок — это довольно широкое понятие, которое в значительной степени зависит от конкретного применения. Однако можно выделить несколько общих принципов. Одним из методов защиты программы является метод утверждений [53]. Программа должна содержать все утверждения программиста о желательном поведении программы или данных, и, более того, программа должна обладать способностью проверить правильность этих утверждений. Должно быть также особое “устройство”, которое печатает диагностику ошибок, а также указывает, в каком месте они произошли, и сообщает либо о возобновлении работы программы, либо об исключении из нее ошибочного утверждения.
       Сам термин “утверждение” может вводить в заблуждение. Он возник как один из терминов, употребляемых для доказательства правильности чего-либо. Здесь он употребляется совсем в другом контексте. Речь идет о расширении хорошо известных методов редактирования данных.
       Утверждения могут быть использованы, например, чтобы обнаружить:
       1. Ошибочные и неправдоподобные входные данные.
       2. Ошибочные и неправдоподобные выходные данные.
       3. Ошибочные и неправдоподобные данные вообще.
       4. Ошибки в логике.
       5. Нежелательные побочные эффекты вызова процедур.
       6. Индексы, лежащие вне диапазона.
       7. Использование неинициированных переменных.
       8. Переполнение памяти.
       9. Условия исключения из программы.
       10. Любые другие условия, известные программисту как ошибочные.
       Некоторые из этих пунктов заслуживают более пристального внимания. Основным из них является верификация входных данных. Строгая проверка достоверности входных данных, особенно если они поступают от неопытного сотрудника, может иметь решающее значение для стабильной работы программы (и программиста!). Если ошибки не замечены на входе, на выходе получаются неудовлетворительные результаты.
       Переполнение памяти машины также может иметь серьезные последствия. Если программист говорит: “Откуда я мог знать, что пользователь попытается заложить 101 позицию в массив размерностью 100?” — то это значит, что он не предусмотрел защиту программы от возможных ошибок. Ему следовало ввести простые утверждения в каждой точке, в которой размерности массива присваивается конкретное значение (или в точке, где увеличивается индекс массива). Утверждение обнаружило бы переполнение, диагностировало бы его извне и приняло бы все возможные меры к восстановлению программы. Если этого не сделать, программа может повести себя самым невероятным образом.
       (У авторов был случай, когда написанная ими программа вызвала переполнение таблицы символов компилятора, которое не было обнаружено сразу. Компилятор уничтожил участок памяти, напечатал 512 бессмысленных сообщений, а затем выбросил программу.)
       Открытым остается вопрос о том, сколько утверждений следует оставлять активными в программе на протяжении ее эксплуатации, учитывая, что их использование влияет на время выполнения и размеры программы. Некоторые специалисты говорят, что не могут позволить себе включать утверждения в программу. Другие говорят, что не могут позволить себе этого не делать. Видимо, вопрос о том, включать или нет, и если да, то в каком объеме, должен решаться, исходя из конкретных условий.
       Следует отметить, что существуют определенные способы, позволяющие легко вносить утверждения в программу или извлекать их из нее. Некоторые компиляторы могут распознавать утверждения (при этом они должны быть заранее включены в синтаксис) таким образом, чтобы опция либо игнорировала их, либо обрабатывала во время компиляции. Другие компиляторы обеспечивают возможность “условной компиляции”, при которой программа любого вида (в том числе и с утверждениями) может либо игнорироваться, либо обрабатываться с помощью опции. И если имеется намерение использовать утверждения избирательно (например, для отладки), для удобства работы необходим один из этих способов.
       Защита программы от ошибок, кроме утверждений, предусматривает использование резервов. Необходимо преднамеренно сберечь некоторую часть ресурсов системы, т.е. не использовать сразу, чтобы у сопровождающего программиста была возможность выполнить свою работу должным образом. Будь то магнитная память или память на дисках, если она на 100% использована разработчиком, сопровождающему программисту вряд ли удастся что-либо исправить или дополнить. Если все резервы памяти системы использованы уже на начальной стадии, сопровождающий программист окажется в затруднительном положении.
       На заре развития ЭВМ считалось нормальным, что половина времени сопровождающего программиста уходит на высвобождение ресурсов программы, а вторая половина — на то, чтобы расширить ее возможности и воспользоваться ресурсами, которые удалось высвободить. В наше время это уже непозволительно. Для нужд сопровождающего программиста необходимо иметь резервы.
       Существует еще один способ защиты программы от возможных ошибок, которым является автономный блок записи, соединенный с программой. Это понятие заимствовано из области авиации и заключается в следующем: программа может производить непрерывную запись всех важных событий, которые происходили за время ее работы (например, поступление модулей, сходящиеся итерации, завершение циклов, запись утверждений и т.д.). С помощью такого устройства можно записать все ошибки и сбои программного обеспечения, что позволит определить причину аварии. (Здесь проводится полная аналогия с аварией самолета.)
       Необходимая частота включения записи определяется с помощью дополнительной условной компиляции исходной программы. Например, при нормальном выполнении программы из соображений производительности могут быть записаны только начала основных фаз работы системы; но, если понадобится провести анализ причины сбоя системы, мы будем иметь возможность осуществить пере-компиляцию, увеличив частоту включений записи (например, для того чтобы проследить введение модулей в программу и изменение ключей данных). Если условия оптимизации позволяют, то частота записи может быть такой, что весь этот процесс заменит выполнение программы.
       Еще одним методом профилактического программирования является практика расстановки флажков в программе и составление перечня ненадежных методов работы. Как ни странно, программисты нередко прибегают к рискованным способам программирования. Причины бывают самые разные: от стремления добиться цели <“язык программирования не позволяет решить задачу без небольших уловок”) до стремления к оптимизации (“я не смог бы уложиться в отведенное время, если бы не схитрил”). Такие случаи не являются исключением, а скорее представляют собой повседневную реальность, с которой в большей или меньшей степени сталкиваются все программисты.
       Чтобы убедиться в достоверности вышеупомянутых фактов, рассмотрим примеры, иллюстрирующие применение ненадежных методов программирования во многих языках программирования.

Пример Ненадежные методы программирования
Хэш-алгоритмы программы Нарушение типа — над последовательностью символов, к примеру, может быть выполнено арифметическое действие
Преобразование последовательности символов Нарушение типа — для доступа к массиву знаков в качестве индекса использован знак
Стандартная программа вычисления Подпрограмма, имеющая один вход. Обычно алгоритмы вычисления функций sin и cos программируются как стандартная программа с двумя входами
Исключение возвратов Использование GOTO
Разгрузка памяти, очистка памяти и т.д. Нарушение типа — адресация в памяти может быть представлена, например, как последовательность битов независимо от того, как описан тип в действительности
Сервисное прерывание и т.д. Абсолютная адресация — фрагмент данных может быть получен только по абсолютному адресу

       Правильный подход к ненадежным методам программирования, таким образом, предполагает необходимость: 1) признать за ним право на существование; 2) создать языки программирования и разработать стандарты, позволяющие свести к минимуму количество случаев применения ненадежных методов программирования (это даст программисту возможность выполнить свою работу надежно и четко); 3) снабдить программу средством регистрации, чтобы все случаи применения ненадежных методов заносились в листинг (какой именно ненадежный метод и почему он был использован?) и суммировались в конце программы для отчета программиста руководству.
       В настоящее время такие сведения должен готовить сопровождающий программист. Однако помощь близка! В языке Ада разработана методика применения ненадежных методов программирования, а компиляторы Ада способны автоматически составлять отчеты о их применении в программах.
       Для быстрого обнаружения и устранения ошибок сопровождающему программисту важно знать, какие части программы составлены с применением ненадежных методов.
       Примеры защиты программ от возможных ошибок. В языке Ада заложена некоторая возможность использования утверждения. Например, программист может оговорить область определения переменной в точке ее описания:

ARRAY_SIZE: integer rang 1..100;

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

ARRAY_SIZE: = -5; диагностику проводить во время компиляции
оr
I: = 100;
ARRAY_SIZE: = I + 1; -- исключения из области определяются в ходе выполнения программы

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

ARRAY_SIZE: = XX;
if ARRAY_SIZE< 1 or ARRAY_S1ZE> 100 then
   PRODUCE_DIAGNOSTIC (ARRAY_SIZE);
   ABORT_GRACEFULLY;
end if;

       В некоторых случаях применения утверждений программист явно объявляет область определения, а затем получает диагностику выходов за рамки ее области определения за время выполнения программы:

!RANGE_ARRAY_SIZE (1..10);
. . .
ARRAY_SIZE = 101; -- диагностика будет дана во время выполнения программ

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

!RELATION FIRST_THINGEE < = NEW_THINGEE < = LAST_THINGEE AND NEW_THINGEE < 0;

       Это такие взаимоотношения, при которых процессор, обрабатывающий утверждения, мог бы обнаружить нарушение и произвести их диагностику; и

!CALL(FIRST_VARIABLE; SECOND_VARIABLE);
EXECUTE_PROCEDURE(PARAMETER);

       Это запрос утверждения проверить побочные воздействия процедуры EXECUTE PROCEDURE на переменные FIRST_VARIABLE и SECOND_VARIABLE. Заметим, что, если бы не было такого утверждения, те же проверки были бы запрограммированы в менее удобной форме:

a) if NEW_THINGEE < FIRST_THINGEE or
      NEW_THINGEE > LAST_THINGEE or 
      NEW_THINGEE = 0 then 
      PRODUCE_DIAGNOSTIC(NEW_THINGEE);
      RECOVER;
   end if;
b) SAVE_FIRST: = FIRST_VARIABLE;
   SAVE_SECOND: = SECOND_VARIABLE;
   EXECUTE_PROCEDURE(PARAMETER);
   if FIRST_VARIABLE / = SAVE_FIRST or
      SECOND_VARIABLE / = SAVE_SECOND then
      PRODUCE_DIAGNOSTIC(EXECUTE_PROCEDURE);
      RECOVER;
   end if;

Стандарты программирования
       Стандартами программирования называются требования, предъявляемые к программисту окружением. Они могут быть продиктованы его руководителем, условиями контракта или коллегами-программистами. Эти требования обычно высоки и трудновыполнимы. Они, как правило, бывают навязаны программисту в целях повышения качества программирования и улучшения административного контроля. Выигрывает от соблюдения этих правил в первую очередь сам сопровождающий программист. Программы, написанные в соответствии со стандартами, обычно более понятны, и с ними легче работать. Это способствует профилактике сопровождения [57].
       Стандарты программирования, однако, могут быть неверно использованы. Слишком часто они подменяются набором приемов программирования (а иногда уловок), свойственных данному руководителю, программисту или, что еще хуже, непрограммисту. Иногда они также являются отражением последних веяний в технике программного обеспечения независимо от того, приняты они или нет. В результате получается смесь хороших методов и чепухи.
       Пожалуй, прежде всего следует установить перечень стандартов для лиц, составляющих их:
       1. Не устанавливай новых технических условий, если не уверен, что это даст улучшение результатов.
       2. Делай спецификацию небольшой по объему, чтобы она легче запоминалась.
       3. Не подвергай стандартизации те вещи, которые уже определены языком или системой.
       4. Выделяй основные направления, но не стандарты.
       5. Выделяй содержание, обращая внимание на форму лишь по необходимости.
       6. Определи, каким образом можно обойти стандарты.
       7. Считай программиста профессионалом-практиком, а не дилетантом.
       С этими оговорками стандарты, возможно, смогут помочь сопровождающему программисту.
       Типичные современные стандарты включают в себя соглашения (перечень правил для поименованных или логических сущностей), ограничение сложности (перечень правил, ограничивающих число строк в программном модуле), конструктивные ограничения (перечень правил, оговаривающих допустимые языковые формы, такие, как GOTO или его модификации) и т.д. Как мы уже убедились, соглашения о структурированном присвоении имен и ограничения количества строк в программном модуле имеют отрицательные последствия. Таким образом, становятся очевидными накладываемые конструктивные ограничения, по крайней мере в современных языках.
       Примеры стандартов программирования. На основе одного очень крупного проекта программного обеспечения (который в свою очередь был частью еще более крупного космического проекта) разработан справочник стандартов объемом около 100 страниц! Правила, которые приводятся ниже, являются наилучшими образцами из этого справочника.
       1. Глобальные базы данных следует разбить на блоки, чтобы свести до минимума число модулей, требующих доступа к блоку.
       2. Переменным следует присваивать однозначные имена, а не EQUIVALENCE или OVERLAYED.
       3. Запросы ввода/вывода должны обрабатываться централизованно рабочим модулем с подмодулями по одному в каждом.
       4. Язык проектирования программы следует включать в программу, полученную на этапе проектирования в виде комментариев.
       5. Следует для каждого модуля программы завести папку его разработки и сопровождения. Содержание папки должно включать: а) титульный лист; б) журнал измерений; в) требования к модулю; г) проектирование; д) листинг модуля, находящегося в обращении; е) план проверки; ж) результаты проверки; з) отчеты об ошибках; и) комментарии; к) замечания разработчика.
       6. Названия следует выбирать так, чтобы они раскрывали суть функций и включали в себя структуру программы. Структура программы должна быть выражена путем введения в имя: а) сокращенного названия модуля; б) названия совокупности данных или названия блока, если необходимо, в сокращенном виде.
       7. Следует выделять элементы структурного программирования.
       8. Комментарии должны включать как минимум следующее:
          а) детализацию функций и интерфейсов каждой подпрограммы;
          б) описание применения и области существования каждой переменной;
          в) объяснение каждой подфункции;
          г) объяснение каждой ненадежной или сложной программы.
       9. Глубина вложения должна быть минимальной.
       10. Размеры модулей должны определяться их функцией, однако их средняя величина не должна превышать 100 строк.

Документация
       Многое из того, о чем говорилось выше, имеет первостепенное значение. Самым важным средством профилактики сопровождения является модульность. Следом за ней по степени важности идет структура программы и структура данных. Стандарты программирования, хотя им уделяется большое внимание, вносят значительно меньший вклад (в основном они привносят понятия более высокого уровня).
       О документации мы решили говорить в конце, но не потому, что она менее важна, а потому, что 1) она очень сильно отличается от всего остального и 2) о ней довольно подробно говорится в других разделах книги. Еще раз отметим, что документация детального уровня должна содержаться в виде комментариев в листинге программы, а не в виде отдельной книги.
       В этом разделе мы не станем больше рассуждать, но приведем ряд конкретных предложений по вопросу о том, какие комментарии, где и в каком объеме должны лечь в основу такой документации. Заранее отметим, что хорошая документация (т.е. “комментарий”) не менее важна для профилактики сопровождения, чем все остальные средства.
       По крайней мере следующие места программы должны содержать комментарии:
       1. Начало каждого модуля, включая название модуля; дату назначения модуля, его входы и выходы; перечень имеющихся ограничений на работу модуля, включая основные предположения их возникновения; описание работы отрезка программы, на котором появляется ошибка и имя разработчика. Главные модули должны также включать всю хронологию их модификаций: дату внесения каждого изменения, имя сопровождающего программиста, цель изменения и его место в программе.
       2. Каждая подфункция, будь то прямая последовательность операторов или логический переход, или блок begin-end, должна быть объяснена.
       3. Каждый интерфейс, включая его явное определение и ссылку на информацию о других аспектах его функционирования (где возмо жно).
       4. Каждая группа функционально или каким-либо другим способом связанных описаний, объяснений назначения и состава группы.
       5. Каждое описание, включая объяснение назначения каждого пункта, его возможных величин, если такие имеются.
       6. Каждая труднопонимаемая часть программы, включая объяснение работы программы и обоснование причин использования сложной программы.
       Трудность, однако, состоит не в том, чтобы знать, какие комментарии и где нужно писать, а в том, чтобы описать их. Поэтому хороший руководитель должен настоять на том, чтобы необходимые комментарии включались в программу. Набор программ, данных в обзорах [1,29], указывает сопровождающему программисту на необходимость включения в программу адекватных комментариев. Эти обзоры имеют также ряд других достоинств. Возможно, с них и начнется правильное расположение комментариев. (Если программист ввел язык проектирования в качестве комментариев, ему, возможно, не придется лобавлять множество дополнительных комментариев в дальнейшем.) Вскоре должны появиться звуковые прослушивающие устройства. Либо компилятор, либо автоматизированное устройство прослушивания программы могут относительно легко проверить соответствие программы комментариям и другим стандартам.
       Умение составлять хорошую документацию является своеобразным искусством, сравнимым с искусством писателя. Приведем примеры.
       Пример документации. Хорошо знакомая нам функция SEARCH неоднократно рассматривалась выше, но с точки зрения структуры программы. Ниже мы вновь возвращаемся к ней для иллюстрации правильного комментирования, позволяющего сделать программу самодокументирующейся:

-- FUNCTION SEARCH
-- INPUT = FILE_NAME AND KEY_TYPE,
-- OUTPUT = FILE_INDEX
-- поиск файла, содержащего инструкции, затем
-- осуществляется поиск файла для данного ключа и
-- возврат указателя к файлу FILE_INDEX
-- вызов файла исключений BAD_FILE, если инструкции
-- не содержат ответа на запрос, или
-- вызов файла исключений BAD_KEY, если искомый
-- ключ не содержится в файле, написанном
-- 25 декабря 1981 г. Стадсом Аксвингером
function SEARCH (FILE-NAME; KEY-TYPE)
   return FILE—INDEX is begin
   -- смотри запись данного ключа в данном файле
      for FILE_POINTER in FILE-INDEX'FIRST..FILE_INDEX'LAST loop
         -- первоначальное месторасположение файла
         if FILE_MAP (FILE_POINTER) = FILE_NAME
            then goto FIND-KEY;
         end if;
      end loop;
      raise BAD_FILE; return; -- если файл не найден
      “FIND KEY” RECORD-COUNT: = 0; -- файл получен
      while KEY_TYPE = FILE_KEY loop
         -- затем найден ключ
         READ (FILE_POINTER); читайте запись
         RECORD_COUNT: = RECORD-COUNT + 1;
         -- помните, где находитесь
      end loop;
      FILE_INDEX: = RECORD_COUNT; -- сохраните результат
      return;
      begin -- исключение блока
         exception
         when END_OF_FILE raise BAD_KEY; return;
         -- если ключ не найден
      end
end SEARCH;

3.2.2.2. Трудоемкость

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

3.2.2.3. Улучшение программы

       Следует непрерывно улучшать качество работы программы. Очень часто под улучшением программы понимают включение в нее элементов профилактики сопровождения, которые следовало бы внести еще разработчикам. Методы, приводящие к таким улучшениям, требуют:
       1. Применения модулей: там, где текст программы повторяется, необходимо сделать процедуру, с помощью которой можно вызывать модуль. Это также позволит сократить исходную программу, часто значительно. В дальнейшем необходимо пытаться выделить функциональные области программы для облегчения дальнейших изменений.
       2. Исключения из программы всех тех операторов GOTO и меток, без которых можно обойтись. Хотя чрезмерное стремление к отмене операторов GOTO порождает проблем больше, чем позволяет решить, в наиболее очевидных случаях при необходимости запустить в серию несерийную программу нужно исключить их.
       3. Применения взаимоконтролирующихся модулей. Пусть каждый модуль проверяет состав и соответствие сомнительных данных из других модулей. Это позволит по крайней мере обнаружить ошибки и недочеты раньше, чем они возникнут при работе системы. В тех случаях, когда известны диапазоны изменения величин переменных, необходимо выяснить, нет ли переменных, значения которых лежат вне указанных ограничений. В этом состоит методика контроля утверждений, о которой говорилось выше в этом разделе.

3.2.2.4. Большие системы. Метод подхода

       Большие системы ставят целый ряд оригинальных задач как перед создателями программного обеспечения, так и перед специалистами по его сопровождению. Хотя решения этих задач по-прежнему являются предметом поиска, наиболее общая методика, использующая структурный подход к этим задачам, заключается в дополнительной формализации спецификаций, проектных представлений интерфейсов системы, предполагаемых контрольных выходов и отчетов о результатах контроля.
       Подобная формализация может оказаться полезной для сопровождающего программиста. Такой подход особенно хорошо освещен в литературе [18]; неудачное сопровождение программного обеспечения улучшено с помощью полезного эксперимента, в котором система перестраивается более формально — путем использования исправленных и дополненных требований к техническим условиям программного обеспечения.
       Когда в большой системе возникает ошибка, следует учесть определенные соображения. Метод поиска ошибок в больших системах должен включать, по-видимому, следующие стадии:
       1. Исправление ошибок.
       а) Убедитесь в том, что ошибка существует. Указывает ли постановка задачи и сопроводительная документация на разницу между фактическими и ожидаемыми результатами? Нередко пользователь приходит к сопровождающему программисту или консультанту с теми результатами, в которых он сомневается, но после обсуждения очень часто находит ответ на свой вопрос в справочнике пользователя. Иногда ответ содержится не в справочнике, а в требованиях. Тогда необходимо внести соответствующие изменения в справочник пользователя. Таким образом может быть исключено большое число ошибок.
       б) Выделение ошибки. Выделение ошибки состоит в определении с помощью всей имеющейся информации наиболее вероятного ее местонахождения в программе. На этом этапе сопровождения программы или системы обязательным является тщательный пересмотр системы и всех имеющихся средств сбора информации о ней. Должны быть определены отдельные части системы, которые могут иметь отношение к возникшей ошибке. В некоторых случаях, когда недостает необходимой информации, может оказаться полезным создать средство для ее получения. Вновь созданное средство будет использовано, когда этот тип информации потребуется снова. При наличии таких средств, возможно, потребуется одна или более прогонок программы, содержащей ошибку, для того чтобы получить всю необходимую информацию. (Были ли верны результаты работы программы в точке А? в точке Б?...) В результате такого анализа ошибка программы будет обнаружена.
       в) Воспроизведение ошибки. Предположим, что ошибка существует и выделена. Предположим далее, что интуитивное мгновенное решение не возникло, — тогда требуется помощь. В большой системе важно создать локальный тест, позволяющий выявить признаки ошибки, т.е. воспроизвести ошибку в том виде, в котором ее легче будет устранить. Следует отбросить всю избыточную, ненужную информацию, которая окружает данные, содержащие ошибку, и выделить ее в чистом виде. Если, к примеру, потребуется трассировка или другая отладочная информация, то желательно получить распечатку только той области программы, в которой содержится ошибка, а не целый ворох бумаги, большая часть которой не имеет прямого отношения к решаемой проблеме. Часто такой локальный тест может в дальнейшем пополнить набор тестов для регрессивного тестирования системы.

3.2.2.5. Эволюция против революции

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

3.2.3. ДОКУМЕНТАЦИЯ

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

3.23.1. Историческая документация

       Методика написания документации к программному обеспечению всегда обходила историческое документирование. Историческая документация— это неформально представленная информация, используемая на определенном этапе создания программы. Она включает следующие понятия:
       Заметки на стадии проектирования. Это наиболее важная форма исторической документации, состоящая из записей проектировщика (они подробнее и шире официального описания проекта, представленного на языке разработки программы или в виде структурных схем). Эти записи, обычно сделанные от руки, незаменимы, если сопровождающему программисту необходимо видоизменить отрезок программы и он хочет узнать, почему имеющийся вариант проекта развивался именно этим путем, а не другим. Большинство современной документации не представляет ему таких сведений. Часть этого материала может быть оформлена в виде первоначального или обзорного документа, но мы советуем сохранять ее в самом первозданном виде и хранить в хронологиче ском порядке. Части материала, относящиеся к одному и тому же вопросу, могут быть помечены перекрестными ссылками путем введения нумерации страниц в файл разработки и создания списка ссылок на эти номера. Причина, по которой заметки проектировщи ка обычно не оформляются в виде отчета, состоит в том, что по мере применения программы они часто устаревают. Важно понять, что заметки проектировщика 1) дают полное общее представление о процессе проектирования, но не о деталях, 2) следует постоянно исправлять, чтобы они не устаревали. Первое свойство заметок обычно используется программистами. Что же касается постоянного обновления заметок, то это дело будущего.
       2. Отчет о проблемах. Следует вести хронологическую картотеку возникающих ошибок, пронумерованную для удобства нахождения нужного материала. Ошибки должны храниться в рабочей картотеке вплоть до окончания работы, а затем переноситься в историческую картотеку. (Частично эта работа может быть автоматизирована путем извлечения данных из индивидуальных отчетов и составления сводных списков.) Эти исторические записи также следует сохранять в первоначальном (неперепечатанном) виде, так как при этом может быть потеряна часть информации. Хотя эти картотеки применяются нечасто, но когда они все же понадобятся, от них будет зависеть многое (например, с их помощью можно выделить и исправить повторяющиеся ошибки).
       3. Предложения по усовершенствованию. Еще одним важным аспектом исторической документации является сбор идей по новейшим усовершенствованиям программы. Идеи бывают двух видов: коренные улучшения программы и косметические усовершен ствования. Последние обычно включаются в листинг или в текущий вариант программы, а затем забываются. Но коренные улучшения, которые обычно приходят в голову, когда программист занят совсем другой работой, следует хранить централизованно (в папке или подшивке) и регулярно просматривать. Тогда хорошие идеи не будут теряться, и программисту всегда будет что показать руководителю или заказчику.
       4. Описание версий — сопроводительная документация. Этой важной части документации не всегда уделяется должное внимание. Каждый вариант программы должен иметь полное описание всех внесенных в него изменений. Сюда входит список отчетов об ошибках, которые были устранены, словесное описание изменения и, где возможно, описание влияния этих изменений на пользователя в терминах, понятных пользователю. Здесь также следует дать ссылки на справочник пользователя, справочник документации, изменения в документации.
       Заметим, что ни одна из этих форм ведения документации не является традиционно обязательной, тем не менее все они очень важны. Заметим также, что в большинстве случаев эту документацию лучше всего” вести от руки, так как ясность и свежесть первоначально возникшей мысли легко может быть утеряна в процессе оформления.

ЛИТЕРАТУРА

1. См. [1] к гл. 2.

2. Bladen, Standard Compiler Workshop Final Report, Eglin Air Force Base, 1978.
       Даны результаты семинара, целью которого было определение оборудования для алгоритмического языка Ада. Четыре рабочие группы разрабатывали следующие темы: 1) план работы; 2) оценки спецификаций и верификаций; 3) оптимизация; 4) вспомогательные средства программного обеспечения. Последняя группа предложила специальный набор вспомогательных средств для поддержания языка Ада.

3. Boehm, Software Engineering, IEEE Transactions on Computers (December 1976).
       Даются определения техники программного обеспечения и его составных частей. Обсуждаются затраты и основные направления. В гл. 7 “Сопровождение програм много обеспечения” говорится, что сопровождение поглощает около 70% средств и что ему уделяется мало внимания.

4. Boehm, Brown, Kaspar, Lipow, MacLeod, Merritt, Characteristics of Software Quality, North-Holland, 1978.
       Обсуждается сложность сопровождения как основной элемент надежности программного обеспечения. Показано разделение сопровождения по функциям в виде древовидной структуры составляющих ,его частей. Даны положительные и отрицательные характеристики сопровождения.

5. Carter, Donahoo, Farquhar, Hurt, Software Production Data, RADC-TR-77-177, 1977.
       Описываются исследования, проведенные корпорацией Computer Sciences с целью оценить эффект “практики современного программирования” (ПСП). Вводится понятие “нить”, используемое для трассировки требований в последовательности фаз жизненного цикла программного обеспечения, и дается оценка применению этого понятия в различных проектах. Дается оценка влияния элементов ПСП на жизненный цикл и на различные типы ошибок.

6. См. [6] к гл. 2.

7. Feldman, Make - A Program for Mainteining Computer Programs. Software Practice and Experience (April 1979).
       Описывается средство управления версиями, использованное в операционной системе UNIX.

8. Fox, E-3A Software Maintenance, Proceedings of the AIAA Conference on Computers in Aerospace, 1977.
       Описывается работа по сопровождению, проведенная в рамках проектов командования и управления Министерства обороны США. Обсуждается проект Е-ЗА, структура управления сопровождением программного обеспечения и связанная с этим деятельность. 9. См. [10] к гл. 2.

10. Gannon, A Verification Case Study, Proceedings of the AIAA Computers in Aerospace Conference, 1977
       Описывается автоматическая верификационная система (ABC) JOVIAL и способы ее применения. Обсуждается способность ABC к самопроверке и использование ее как части правительственной приемочной проверки. Делаются выводы об эффективности тестирования.

11. Gay, Evaluation of Maintenance Software in Real-Time Systems, IEEE Transactions on Computers (June 1978).
       Описывается программа реального времени, устанавливающая местонахождение ошибок путем “посева” (преднамеренного введения) заранее известной ошибки для установления числа ошибок в программе.

12. Gelperin, Testing Maintainability, ACM SIGSOFT Software Engineering Notes, April 1979.
       Предлагаются способы оценки степени сложности сопровождения программного обеспечения методом “а что, если...”, ревизия стандартов, предположительный анализ (чтобы предусмотреть предстоящие изменения), оценка структуры (чтобы лучше понять состав и сложность программы), тестирование программы.

13. Gerhart, Yelowitz, Observations of Fallibility in Applications of Modern Programming Methodologies, IEEE Transactions on Software Engineering, 1976.
       Описываются ошибки, содержащиеся в программах, созданных с помощью современной техники и считающихся верными. Рекомендуется: 1) более тщательный анализ полного задания; 2) применение всех возможных средств, гарантирующих надежность; 3) отказ от укоренившихся представлений о “трудной” и “легкой” программе (в легкой может быть больше возможностей для ошибки); 4) различие понятий “хорошая структура программы” и “правильная программа”; 5) понимание того факта, что новая методика не является панацеей.

14. См. [12] к гл. 2.

15. Glass, Of Flat Earths and Flowcharts, The Power of Peonage, Computing Trends, 1979.
       Юмористически описывается случай, происшедший из-за того, что закончилась эра структурных схем.

16. Goodenough, Eanes, MAIDS Study - Program Testing and Diagnosis Technology, Letter Report N/000-6-73, 1973.
       Речь идет о несоответствии тестов и о важности раннего обнаружения ошибок разработки. Включено регрессионное тестирование и общее обсуждение способов тестирования.

17. Hart, The Advanced Interactive Debugging System (AIDS), ACM SIGPLAN Notices, December 1979.
       Описывается символическая, независимая от языка, не нуждающаяся в заранее запланированной программе коммерческая система отладки.

18. Heninger, Specifying Software Requirements for Complex Systems: New Techniques and Their Application, Proceedings of the IEEE Specifications of Reliable Software Con ference, 1979
       Описывается применение формальных требований к техническим условиям в связи с сопровождением программного обеспечения, содержащего ошибки и неполадки. Даются практические подходы к решению проблемы.

19. Howden, An Evaluation of the Effectiveness of Symbolic Testing, Software Practice and Experience (July 1978)
       Дается оценка нескольких методик отладки и их способности обнаруживать ошибки. Автор считает символическое тестирование наиболее перспективным. Рассмотрены также анализаторы эффективности проверки на разных стадиях сбоя системы и другие способы.

20. Huang, An Approach to Program Testing, ACM Computing Surveys, September 1975.
       Включает учебный материал, анализирующий трудности тщательного тестирования. Предлагается в качестве решения проблемы анализатор эффективности проверки. Обсуждаются применение, достоинства и недостатки этого средства.

21. Dolotta, Haight, Mashey, The Programmer's Workbench, The Bell System Technical Jour. (July - August 1978).
       Описывается оборудование средств, которыми располагает операционная система UNIX. Рассматриваются применение системы, проблемы внедрения и практический опыт.

22. Jackson, Principles of Program Design, Academic Press, 1975.
       Дается проблемно-ориентированный подход к идеям разработки. Каждая идея подтверждается примером решения задачи. Задачи взяты из области обработки данных с использованием языка программирования Кобол. Особое внимание уделяется разработке структуры данных.

23. Kernighan, Plauger, Software Tools, Addison Wesley, 1976.
       Описываются средства, их ценность для программиста и разновидности средств, котэрые могут быть применены.

24. См. [14] к гл. 2.

25. Kosy, Air Force Command and Control Information Processing in the 1980s, Trends on Software Technology, R-1012-PR, The Rand Corp., 1974.
       Описывается эволюция и предполагаемое будущее программного обеспечения в технике командования и управления. Речь идет о перспективных методах, включая применение языков высокого уровня и обсуждение их преимуществ.

26. Lauesen, Debugging Techniques, Software Practice and Experience (January 1979).
       Описываются десять способов улучшения отладки: отладка снизу вверх, создание средств контроля выхода, самоконтролирующиеся программы, новый подход к отбору контрольных данных, независимые тесты, приемочная проверка, конфигура ционное управление тестов, запись результатов проверок, моделирование ошибки, введение учега отказов.

27. См. [17] к гл. 2.

28. См. [19] к гл. 2.

29. Linger, Mills, On the Development of Large Reliable Programs, Current Trends in Programming Methodology, Prentice-Hall, 1977.
       Авторы являются сторонниками структурного программирования, считая его средством “безошибочного” кодирования. Даны примеры.

30. Miller., Software Quality Assurance, Computer (August 1979).
       Сборник статей на тему “Опыт работы с разнообразными средствами и методами обеспечения надежности”.

31. См. [21] к гл. 2.

32. См. [22] к гл. 2.

33. См. [23] к гл. 2.

34. Myers, Composite / Structured Design, Van Nostrand Reinhold, 1978.
       Продолжает тему, начатую в [31] (модульность), давая объективные критерии для ее оценки. Речь идет о функционально сильных модулях и спаривании данных.

35. Ng, Young, A 1900 Fortran Post Mortem Dump System, Software Practice and Experience (July 1978).
       Дается описание средства отладки исходного языка, которое выдает дампы на Фортране (выборочно) по окончании программы. В работу включены символически идентифицированные данные и некоторые данные из истории работы программы, а также интерфейсы с компилятором, редактором связей и анализатором дампа.

36. См. [24] к гл. 2.

37. Paige, Software Testing: Principles and Practice Using a Testing Coverage Analyzer, Transactions of the Software 77 Conference, October 1977.
       Автор считает, что проверка “является лучшим способом демонстрации правильности программного обеспечения”. Определяется и иллюстрируется понятие “анализатор тестов”, приводятся результаты работы анализатора. Обсуждаются способы тестирования с применением анализатора.

38. Panzl, Automatic Software Test Drivers, Computer (April 1978).
       Обсуждается регрессионное тестирование в связи с автоматической системой, предназначенной для создания и сохранения процедур тестирования (“при современных методах эффективное регрессионное тестирование редко возможно”).

39. См. [25] к гл. 2.

40. См. [26] к гл. 2.

41. Department of Defense Requirements for the Programming Environment for the Common High Order language, Junuary 1979.
       См. [51], где дается более поздний вариант этого документа.

42. См. [28] к гл. 2.

43. Reaser, Priesman, Gill, A Production Environment Evaluation of Interactive Programming, U.S. Army Computer Systems Command Technical Documentary Report USA CSC-AT-74-03, 1974.
       Дается сравнительная оценка создания программного обеспечения в интерактивном режиме и в режиме разделения времени. Выясняется, что при работе в режиме разделения времени эффективность возрастает, а затраты снижаются.

44. Reifer, Trattner, A Glossary of Software Tools and Techniques, Computer (IEEE) (July 1977).
       Средства создания программного обеспечения подразделяются на шесть категорий: моделирование, разработка, тестирование и оценка, операции и сопровождение, оценка работы программы, вспомогательные средства программирования. Дается список из 70 средств и их классификация в соответствии с вышеупомянутыми категориями.

45. Ritchie, Thompson, The UNIX Time-Sharing System, The Bell System Technical Jour. (July - August 1978).
       Содержит описание операционной системы UNIX интерфейса пользователя и ее использования. Раскрывается понятие “трубопровода” — механизма межпроцессной передачи данных, а также описываются другие уникальные свойства системы.

46. См. [29] к гл. 2.

47. Sackman, Timesharing vs. Batch Processing, the Experimental Evidence, Proceedings of the 1968 Spring Joint Computer Conference.
       Суммированы все “за” и “против” при работе в режиме разделения времени. Приводятся результаты пяти экспериментальных разработок. Результаты показывают, что 1) уменьшаются человеческие затраты; 2) возрастают затраты ЭВМ; 3) программисты предпочитают работать в режиме разделения времени.

48. Share Ad-Hoc Committee on Universal Languages, The Problem of Programming Commu nication with Changing Machines - A Proposed Solution, Communications of the ACM, August 1958.
       Содержит самое первое из известных определений Универсального машинно-ориентированного языка (UNCOL). Универсальный машинно-ориентированный язык должен был быть промежуточным языком, на который предполагалось переводить языки высокого уровня (они тогда назывались проблемно-ориентированными языками). На основе UNCOL собирались создать все машинные языки.

49. Sites, Programming Tools: Statement Counts and Procedure Timings, SIGPLAN Notices, December 1978.
       Автор призывает использовать средство анализа программного обеспечения для достижения наглядности и повышения эффективности программ.

50. Stanfield, Skrukrud, Software Acquisition Management Cuidebook, Software Maintenance Volume, System Development Corp.TM-5772 / 004 / 02, November 1977.
       Дается описание методов профилактики сопровождения на всех стадиях жизненного цикла программного обеспечения. Речь идет о конкретном обеспечении в рамках работ Министерства обороны США, но те же методы применимы для любого другого программного обеспечения. Даются идеи и набор тестов для проверки программы в процессе сопровождения. Суммированы требования, указания и спецификации Министерства обороны США к сопровождению программного обеспечения.

51. Requirements for Ada Programming Support Environments, Stoneman (February 1980).
       Приводятся рассуждения, следствием которых является перечень общих средств для языка Ада Министерства обороны США.

52. Stucki, Automatic Generation of Self-Metric Software, IEEE Symposium on Computer Software Reliability, 1973.
       Собраны методы использования программы — монитор, описано средство оценки и проверки программы (PET) для сбора операционных данных во время работы программы, а также для подсчета частоты использования операторов, максимальных и минимальных значений данных и т.д.

53. Stucki, New Directions in Automated Tools for Improving Software Quality, Current Trends in Programming Methodology, Prentice-Hall, 1977.
       Описывается проверка утверждений и методы отладки, с помощью которых программа может обнаружить собственные ошибки.

54. Swanson, The Dimensions of Maintenance, Proceedings of the 2nd International Conf. on Software Engineering, 1976.
       Предлагается определить теоретическую базу сопровождения программного обеспечения. Даются определения сопровождения с целью коррекции, адаптации и улучшения программного обеспечения. Предлагается состав базы данных. Рекомендуется вести дальнейшие исследования по данному вопросу.

55. Tanenbaum, Klint, Bohm, Guidelines for Software Portability, Software Practice and Experience (November 1978).
       Описываются трудности производства портативного программного обеспечения. Они подразделены на проблемы: 1) языков программирования, 2) плавающих точек, 3) файлов, 4) средств обмена данными, 5) интерактивного терминала, 6) операционных систем, 7) архитектуры ЭВМ, 8) документации. Обсуждаются конкретные проблемы.

56. UNIX Time-sharing System, The Bell System Technical Jour. (July 1978).
       Дается более десятка статей, посвященных возможностям, созданию и сопровождению операционной системы UNIX, которая считается наиболее перспективным образцом операционной системы. Включается обсуждение набора средств создания и сопровождения программного обеспечения, известных под названием “рабочее место сопровождающего программиста”.

57. White, Program Standards Help Software Maintainability, Proceedings of the Annual Reliability and Maintainability Symposium, 1978.
       Дается оценка влияния стандартов программного обеспечения на сопровождение. Делается вывод, что модульность, структурное программирование и построчное комментирование имеют свои преимущества.

58. Winograd, Beyond Programming Languages, Communications of the ACM (July 1979).
       Автор считает, что средства современных языков программирования не соответст вуют сложным задачам. Предлагается концепция языка высокого уровня, который выделяет описание известных фактов в большей степени, чем повелительные высказывания.

59. Wulf, Languages and Structured Programs, Gurrent Trends in Programming Methodology, Preitice-Hall, 1977.
       Обсуждается “кризис” программного обеспечения, необходимость структурных программ и роль языков, обеспечивающих их. Особое внимание уделяется новым идеям в области языков.

60. Wegner, Programming with Ada: An Introduction by Means of Graduated Examples, Prentice-Hall, 1980.
       Дается описание языка Ада Министерства обороны США. Обсуждается его содержание с точки зрения программиста. Даются примеры.




<<< Пред. Оглавление
Начало раздела
След. >>>

Дата последнего изменения:
Thursday, 21-Aug-2014 09:10:55 MSK


Постоянный адрес статьи:
http://az-design.ru/Support/SoftWare/l/GlassRob/02h003.shtml