Машины для компилятора и ассемблера. По тем же причинам требуются
компиляторы и ассемблеры, работающие на надежных машинах, но компилирующие
объектный код для целевой системы. Затем можно начать его отладку на
эмуляторе.
При программировании на языках высокого уровня значительную часть
отладки можно произвести при компиляции для вспомогательной машины и
тестировании результирующей программы, прежде чем отлаживать программу для
целевой машины. Этим достигается производительность непосредственного
исполнения, а не эмуляции, в сочетании с надежностью стабильной машины.
Библиотеки программ и учет. Очень успешным и важным применением
вспомогательной машины в программе разработки OS/360 была поддержка
библиотек программ. Система, разработанная под руководством У. Р. Кроули (W.
R. Crowley), состояла из двух соединенных вместе машин 7010 и общей дисковой
базой данных. На 7010 поддерживался также ассемблер для S/360. В этой
библиотеке хранился весь протестированный или находящийся в процессе
тестирования код, как исходный, так и ассемблированные загрузочные модули.
На практике библиотека была разбита на подбиблиотеки с различными правами
доступа.
Прежде всего, у каждой группы или программиста была область для
хранения экземпляров программ, контрольных примеров и окружения, которое
требовалось для тестирования компонентов. На этой площадке для игр не было
никаких ограничений на действия с собственными программами.
Когда компонент программиста был готов к включению в более крупную
часть, его экземпляр передавался менеджеру этой более крупной системы,
который помещал его в подбиблиотеку системной интеграции. Теперь автор не
мог его изменить без разрешения менеджера интеграции. Когда система
собиралась воедино, этот менеджер проводил все виды системного тестирования,
выявляя ошибки и получая исправления.
Через некоторое время системная версия была готова для более широкого
использования. Тогда она перемещалась в подбиблиотеку текущей версии. Этот
экземпляр был священным, и доступ к нему разрешался только для исправления
разрушительных ошибок. Его можно было использовать для интегрирования и
тестирования всех новых версий модулей. Программный каталог на машине 7010
отслеживал все версии каждого модуля, его состояние, местонахождение и
изменения.
Здесь важны два обстоятельства. Первое - это контроль, означающий, что
экземпляры программ принадлежат менеджерам, и только они могут
санкционировать их изменение. Второе - формальное разделение и перемещение с
площадки для игр к интеграции и выпуску новой версии.
По моему мнению, это было одним из лучших решений в программе OS/360.
Эта часть технологии управления была независимо разработана для нескольких
крупных программных проектов, в том числе в Bell Labs, ICL и Кембриджском
университете.2 Она применима как к программам, так и к документации. Это -
неоценимая технология.
Программные инструменты. По мере появления новых технологий отладки
старые теряют значение, но не исчезают. По-прежнему необходимы дампы памяти,
редакторы исходного текста, дампы мгновенного состояния, даже трассировки.
Аналогичным образом, требуется полный набор утилит для загрузки колод
перфокарт на диски, копирования магнитных лент, печати файлов, изменения
каталогов. Если инструментальщика проекта назначить на достаточно ранней
стадии, то все это может быть сделано сразу и находиться в готовности к
моменту надобности.
Система документации. Из всех инструментов больше всего труда может
сберечь компьютеризированная система редактирования текста, действующая на
надежной машине. Наша система, разработанная Дж. У. Франклином (J. W.
Franklin), была очень удобна. Я думаю, без нее руководства по OS/360
появились бы значительно позднее и оказались бы более запутанными. Есть
люди, которые станут утверждать, что двухметровая полка руководств по OS/360
является следствием недержания речи, и сама ее объемистость являет собой
новый тип непостижимости. И доля правды в этом есть.
Но у меня есть два возражения. Во-первых, хотя документация по OS/360 и
ошеломляет размерами, план ее изучения тщательно изложен. Если использовать
его избирательно, то чаще всего можно не обращать внимания на большую часть
всей массы. Документацию по OS/360 нужно рассматривать как библиотеку или
энциклопедию, а не материал для обязательного чтения.
Во-вторых, это гораздо лучше, чем крайняя недостаточность документации,
характерная для большинства систем программирования. Я охотно соглашусь, тем
не менее, что в некоторых местах текст можно было значительно улучшить, и
результатом лучшего описания стал бы меньший объем. Некоторые части
(например, "Концепции и средства") сейчас очень хорошо написаны.
Эмулятор производительности. Лучше его иметь. Разработайте его "снаружи
внутрь", как описано в следующей главе. Используйте одинаковое
проектирование сверху вниз для эмулятора производительности, эмулятора
логики и самого продукта. Начните работу с ним как можно раньше.
Прислушайтесь к тому, что он вам скажет.
Языки высокого уровня и интерактивное программирование
Сегодня два важнейших инструмента системного программирования - это
те, которые не использовались при разработке OS/360 почти десятилетие назад.
Они до сих пор не очень широко используются, но все указывает на их мощь и
применимость. Это: а) языки высокого уровня и б) интерактивное
программирование. Я убежден, что только инертность и лень препятствует
повсеместному принятию этих инструментов, технические трудности более не
являются извинениями.
Языки высокого уровня. Главные основания для использования языков
высокого уровня - это производительность и скорость отладки.
Производительность мы обсуждали раньше (глава 8). Имеющиеся данные, хотя и
немногочисленные, указывают на многократный рост, а не на увеличение на
несколько процентов.
Улучшение отладки происходит благодаря тому, что ошибок становится
меньше, а находить их легче. Их меньше, поскольку устраняется целый уровень
образования ошибок, уровень, на котором делаются не только синтаксические,
но и семантические ошибки, такие как неправильное использование регистров.
Их легче находить, поскольку в этом помогает диагностика компилятора и, что
еще важнее, очень легко вставлять получение отладочных моментальных снимков.
Меня эти возможности производительности и отладки ошеломляют. Мне
трудно представить себе систему программирования, которую я стал бы
создавать на языке ассемблера.
Ну, а как с классическими возражениями против этого инструмента? Их
три: я не могу сделать то, что хочу; результирующая программа слишком
велика; результирующая программа слишком медленна.
Что касается возможностей, возражение, я думаю, больше не состоятельно.
Все свидетельствует в пользу того, что можно делать то, что хочется,
потрудившись найти способ, но иногда для этого приходится изловчиться.3, 4
Что касается памяти, то новые оптимизирующие компиляторы начинают
показывать весьма удовлетворительные результаты, и их усовершенствование
продолжается.
Что касается скорости, то оптимизирующие компиляторы иногда порождают
код, который зачастую выполняется быстрее, чем написанный вручную. Более
того, проблемы скорости можно обычно решить, заменив от 1 до 5 процентов
скомпилированной программы кодом, написанным вручную, после ее полной
отладки.5
Какой язык высокого уровня следует использовать для системного
программирования? Сегодня единственный достойный кандидат - PL/I.6 У него
очень полный набор операторов; он соответствует окружению операционной
среды; имеется целый ряд компиляторов с разными особенностями -
интерактивных, быстрых, с улучшенной диагностикой, с высокой степенью
оптимизации. Лично я быстрее разрабатываю алгоритмы с помощью APL; затем я
перевожу их в PL/I для соответствия системному окружению.
Интерактивное программирование. Одним из оправданий проекта МТИ MULTICS
была его польза для создания систем программирования. MULTICS (и вслед за
тем TSS IBM) концептуально отличается от других интерактивных компьютерных
систем именно в тех отношениях, которые необходимы для системного
программирования: многоуровневая система разделения доступа и защиты данных
и программ, интенсивное управление библиотеками и средства для совместной
работы пользователей терминалов. Я убежден, что во многих приложениях
интерактивные системы никогда не заменят системы с обработкой пакетных
заданий. Но я думаю, что создатели MULTICS привели самые убедительные доводы
в ее пользу именно в применении к системному программированию.
Пока есть не много свидетельств действительной плодотворности этих
очевидно мощных инструментов. Существует широко распространенное признание
того, что отладка является трудной и медленной частью системного
программирования, и медленная оборачиваемость - проклятие отладки. Поэтому
логика интерактивного программирования кажется неумолимой.7



Рис. 12.2 Сравнительная производительность при пакетном и диалоговом
Программировании
Помимо того, есть хорошие отзывы тех, кто разработал таким способом
небольшие системы или части систем. Единственные доступные мне данные
относительно влияния на программирование больших систем исходят от Джона
Харра из Bell Labs. Они представлены на рисунке 12.2. Эти цифры охватывают
написание, ассемблирование и отладку программ. Первая программа является, в
основном, управляющей. Остальные три - языковые трансляторы, редакторы и
т.п. Данные Харра позволяют предположить, что средства интерактивной работы,
по крайней мере, удваивают производительности системного программирования.8
Эффективное использование большинства интерактивных средств требует,
чтобы работа производилась на языке высокого уровня, поскольку телетайп и
пишущую машинку нельзя использовать для получения дампа памяти. С
использованием языка высокого уровня легко редактировать исходный текст и
делать отдельные распечатки. Вместе они действительно составляют пару
отточенных инструментов.

    Глава 13. Целое и части


Я духов вызывать могу из бездны.
И я могу, и каждый может,
Вопрос лишь, явятся ль на зов они?
ШЕКСПИР, КОРОЛЬ ГЕНРИХ IV

Среди современных кудесников, как и встарь, встречаются хвастуны: "Я
могу писать программы, которые управляют воздушным движением, перехватывают
баллистические ракеты, делают переводы по банковским счетам, управляют
производственными линиями". На что есть ответ: "И я могу, и каждый может, но
будет ли работать то, что ты напишешь?"
Как написать программу, которая будет работать? Как протестировать
программу? И как объединить набор протестированных программ-компонентов в
протестированную и надежную систему? Несколько раз мы уже касались
соответствующих приемов, давайте теперь рассмотрим их более систематически.
Проектирование без ошибок
Защита определений от ошибок.
Самые пагубные и неуловимые системные
ошибки возникают из-за несоответствия допущений, сделанных авторами
различных компонентов. Подход к концептуальной целостности, изложенных выше
в главах 4, 5 и 6, непосредственно обращается к этим проблемам. Кратко
говоря, концептуальная целостность продукта не только упрощает его
использование, но также облегчает разработку и делает менее подверженным
ошибкам.
Такую же роль выполняет детализированная трудоемкая работа по
разработке архитектуры, подразумеваемая этим подходом. В. А. Высоцкий из
проекта Safeguard, выполнявшегося в Bell Telephone Laboratories, говорит
так: "Решающая задача - дать определение для продукта. Очень многие неудачи
связаны именно с теми аспектами, которые не были вполне специфицированы".1
Тщательное определение функций, тщательная спецификация и старательное
избегание всех украшательств функций и полетов технической мысли - все это
снижает количество системных ошибок, которые будут обнаружены.
Проверка спецификации. Задолго до написания всякого кода спецификация
должна быть передана сторонней группе тестирования для тщательного
рассмотрения полноты и ясности. Как считает Высоцкий, сами разработчики
сделать это не могут: "Они не могут признаться, что не понимают ее, они
будут счастливо прокладывать свой путь через пропущенные и темные места".
Нисходящее проектирование. В очень четкой статье 1971 года Никлаус Вирт
формализовал процедуру разработки, годами использовавшуюся лучшими
программистами.2 Более того, его замечания, сделанные в отношении разработки
программ, полностью применимы к разработке сложных программных систем.
Воплощением этих замечаний является разделение создания систем на
проектирование архитектуры, разработку и реализацию. Более того, каждая из
задач проектирования архитектуры, разработки и реализации лучше всего может
быть решена нисходящими методами.
Вкратце, метод Вирта определяет разработку как последовательность
уточняющих шагов. Набрасывается примерное описание задачи и грубый метод
решения, позволяющий получить основной результат. Затем определение
изучается более пристально, чтобы увидеть, в чем отличие полученного
результата от требуемого, и крупные этапы решения разбиваются на более
мелкие. Каждое уточнение в определении задачи становится уточнением
алгоритма решения и может сопровождаться уточнением представления данных.
В этом процессе выявляются модули решения или данных, дальнейшее
уточнение которых может быть продолжено независимо от основной работы.
Степень такой модульности определяет гибкость и изменяемость программы.
Вирт считает необходимым использование на каждом шаге нотации как можно
более высокого уровня, чтобы выделить понятия и скрыть детали, пока не
станет необходимым дальнейшее уточнение.
Правильно осуществляемое нисходящее проектирование позволяет избегать
ошибок по нескольким причинам. Во-первых, прозрачность структуры и
представления облегчает точную формулировку требований к модулям и их
функций. Во-вторых, расчленение и независимость модулей помогают избежать
системных ошибок. В- третьих, проект можно тестировать на каждом уточняющем
шаге, поэтому тестирование моно начать раньше и на каждом шаге
сосредоточиться на подходящем уровне детализации.
Процесс пошагового уточнения не означает, что в случае столкновения с
какой- нибудь неожиданно затруднительной деталью не приходится возвращаться
назад, отбрасывать самый верхний уровень и начинать все сначала. На практике
это часто случается. Но становится значительно легче точно увидеть, когда и
почему нужно отбросить весь проект и начать сначала. Многие слабые системы
появляются в результате попыток сохранить скверный первоначальный проект
путем разного рода косметических заплаток. Нисходящее проектирование
уменьшает такой соблазн. Я убежден, что нисходящее проектирование является
важнейшей новой формализацией программирования за десятилетие.
Структурное программирование. Другой важный круг идей для разработки,
сокращающих число ошибок в программе, исходит то Дейкстры (Dijkstra)3 и
построен на теоретической структуре Бема (Boehm) и Джакопини (Jacopini).4
В своей основе подход заключается в разработке программ, управляющие
структуры которых состоят только из циклов, определяемых такими операторами,
как DO WHILE и группами условно выполняемых операторов, ограниченных
скобками с использованием операторов условия IF...THEN...ELSE. Бем и
Джакопини показывают теоретическую достаточность таких структур. Дейкстра
доказывает, что альтернативное неограниченное применение ветвление с помощью
GO TO образует структуры, располагающие к появлению логических ошибок.
В основе, несомненно, лежат здравые мысли. При обсуждении сделано много
критических замечаний - в частности, большое удобство представляют
дополнительные управляющие структуры, такие как n-вариантный переход (так
называемый оператор CASE) для различения среди нескольких случаев и
аварийный выход (GO TO ABNORMAL END). Кроме того, некоторые догматически
избегают всех GO TO , что представляется чрезмерным.
Важной и существенной для создания программ, не содержащих ошибок,
является необходимость рассматривать управляющие структуры системы как
управляющие структуры, а не как отдельные операторы перехода. Такой образ
мысли является большим шагом вперед.
Отладка компонентов
За последние двадцать лет процедуры отладки программ прошли большой
круг и в некоторых отношениях вернулись к начальной точке. Цикл прошел
четыре этапа и любопытно проследить их, отметив мотивацию перехода.
Отладка в активном режиме. У первых машин было сравнительно слабое
оборудование ввода-вывода, обусловливавшее большие задержки. Обычно машина
использовала для чтения и записи бумажные и магнитные ленты, а для
подготовки лент и печати использовались автономные средства. Из-за этого
ввод-вывод на ленту был невыносимо неудобен для отладки, и для нее
использовалась консоль. Поэтому отладка организовывалась таким образом,
чтобы обеспечить за сеанс работы с машиной возможно большее число проверок.
Программист тщательно разрабатывал свои процедуры отладки, планируя
места остановки, адреса памяти для просмотра, их возможное содержимое и
дальнейшие действия в зависимости от содержимого. Это дотошное
программирование самого себя в качестве отладчика вполне могло занять
половину времени написания отлаживаемой программы.
Главным грехом было смело нажать кнопку START, не разбив предварительно
программу на отлаживаемые секции с запланированными остановками.
Дампы памяти. Отладка в активном режиме была очень эффективной. За
двухчасовую отладку можно было запустить программу раз десять. Но компьютеры
были малочисленны и очень дороги, и мысль о такой напрасной трате машинного
времени ужасала.
Поэтому, когда появились скоростные принтеры, подключаемые в активном
режиме, технология изменилась. Программа запускалась и работала до
возникновения ошибки, после чего распечатывался дамп памяти. Тогда начинался
кропотливый труд за столом по изучению содержимого каждого адреса. Времени
уходило примерно столько же, сколько и при отладке на машине, но это было
уже после контрольного прогона, и работа состояла в расшифровке данных, а не
в планировании, как прежде. Для каждого отдельного пользователя отладка
занимала значительно больший срок, поскольку тестовые запуски зависели от
оборачиваемости пакетной обработки. Однако процедура в целом была
предназначена для сокращения времени использования компьютера и обслуживания
возможно большего числа программистов.
Снимки моментального состояния. Машины, для которых были разработаны
дампы памяти, имели память размером 2000-4000 слов, или 8-16 Кбайт. Однако
размер памяти рос огромными темпами, и делать дамп памяти стало нереальным.
Поэтому разработали методы выборочного дампа, выборочной трассировки и
вставки в программы команд для моментальных снимков. Вершиной развития этого
направления стал TESTRAN в OS/360, позволявший вставлять в программу
моментальные снимки без повторной сборки и компиляции.
Интерактивная отладка. В 1959 году Кодд (Codd) с коллегами5 и Стрейчи
(Strachey)6 сообщили о работе, целью которой была отладка в режиме
разделения времени, позволяющая одновременно достичь мгновенной
оборачиваемости отладки в активном режиме и эффективно использовать машинное
время, как при пакетной обработке заданий. Компьютер должен был иметь в
памяти несколько программ, готовых к запуску. Терминал, управляемый только
программой, должен был быть связан с каждой из отлаживаемых программ.
Отладка должна была проходить под управлением программы-супервизора. Когда
программист за терминалом останавливал свою программу, чтобы изучить ее
выполнение или внести изменения, супервизор запускал другую программу,
занимая таким образом машину.
Мультипрограммная система Кодда была разработана, но акцент был сделан
на увеличение производительности благодаря эффективному использованию ввода-
вывода, и интерактивная отладка не была осуществлена. Идеи Стрейчи были
улучшены и в 1963 году воплощены Корбато с коллегами в МТИ в
экспериментальной системе 7090. Это разработке привела к MULTICS, TSS и
другим сегодняшним системам разделения времени.
Главными ощущаемыми пользователем различиями между отладкой в активном
режиме, как она осуществлялась ранее, и сегодняшней интерактивной отладкой
являются возможности, полученные в результате присутствия программы-
супервизора и связанных с ней интерпретаторов языков программирования. Можно
программировать и производить отладку на языках высокого уровня. Эффективные
средства редактирования позволяют легко делать изменения и моментальные
снимки.
Возврат к мгновенной оборачиваемости отладки в активном режиме пока не
привел к возвращению предварительного планирования отладочных сеансов. В
сущности, такое предварительное планирование не столь необходимо, как
раньше, поскольку машинное время теперь не тратится впустую, пока человек
сидит и думает.
Тем не менее интересные экспериментальные данные Голда (Gold)
показывают, что во время первого диалога каждого сеанса достигается втрое
больший прогресс в интерактивной отладке, чем при последующих диалогах.8 Это
убедительно говорит о том, что из-за отсутствия планирования мы не полностью
реализуем потенциал диалоговой работы. Пора стряхнуть пыль со старых методов
работы в интерактивном режиме.
Я считаю, что для правильного использования хорошей терминальной
системы на каждые два часа работы за терминалом должно приходиться два часа
работы за столом. Половина этого времени уходит на подчистки после первого
сеанса: внесение изменений в журнал отладки, подшивку новых листингов в
системный журнал, объяснение непонятных явлений. Вторая часть уходит на
подготовку: планирование изменений и усовершенствований и разработку
детальных тестов для очередного сеанса. Без такого планирования трудно
поддерживать продуктивность на протяжении всех двух часов. Без подчистки
после сеанса трудно сделать последовательность сеансов систематичной и
продвигающей работу вперед.
Контрольные примеры. Что касается разработки фактических процедур
отладки и контрольных примеров, особенно удачное изложение предлагает
Грюнбергер (Gruenberger),9 есть и более короткие описания в других известных
учебниках.10, 11
Системная отладка
Неожиданно трудным этапом создания системы программирования
оказывается тестирование системы. Я уже обсуждал некоторые причины как его
трудности, так и непредсказуемости. Можно не сомневаться в двух вещах:
системная отладка займет больше времени, чем предполагается, а ее сложность
оправдывает досконально систематичный и плановый подход. Рассмотрим, что
включает в себя такой подход.12
Используйте отлаженные компоненты. Обычный здравый смысл, если не
обычная практика, подсказывают, что системную отладку нужно начинать, когда
работает каждая составляющая часть.
Далее общепринятая практика следует двумя путями. Первый подход -
"свинти и попробуй". Видимо, он основывается на том, что кроме ошибок в
компонентах найдутся и ошибки в системе (т.е. в интерфейсах). Чем скорее
части будут соединены вместе, тем скорее всплывут системные ошибки. Легко
также представить, что, используя компоненты для тестирования друг друга,
можно в значительной мере избежать создания окружения для тестирования. И
то, и другое, очевидно, является правдой, но, как показывает опыт, не всей
правдой: значительно больше времени сберегается при тестировании системы с
использованием чистых отлаженных компонентов, чем его тратится на создание
окружения и доскональной проверки компонентов.
Несколько более тонким является подход "документированной ошибки". Он
означает, что компонент готов к использованию в системной проверке, когда
все его ошибки найдены, но необязательно уже исправлены. Тогда,
теоретически, при системном тестировании возможные эффекты этих ошибок
известны и могут быть проигнорированы, а сосредоточиться можно на новых
явлениях.
Все это означает принимать желаемое за действительное и происходит от
стремления объяснить провал графика работ. Никто не знает всех возможных
последствий известных ошибок. Если бы все было просто, системное
тестирование не вызывало бы затруднений. Кроме того, исправление
документированных ошибок, несомненно, приведет к внесению новых ошибок, и
системный тест окажется испорченным.
Создайте больше окружений. Под "окружением" я понимаю все программы и
данные, созданные для целей отладки, но не предназначенные для использования
в конечном продукте. В окружении нет смысла иметь и половины того кода,
который входит в продукт.
Один из видов окружения - фиктивный компонент, который может состоять