XIII. Целое из частей

"Я духов вызывать могу уз бездны".
"И я могу и каждый это может.
Вопрос лишь, явятся ль они на зов".
(Ш е к с п и р, "Генрих IV" )

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

Как создается работоспособная программа? Как отлаживается программа? И как объединяется множество отлаженных программ-компонентов в проверенную и надежную систему? Мы эпизодически касались этих методов; теперь настала пора рассмотреть их более систематично.

Проект без ошибок

Точное задание.

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

Подобную же роль играет подробная и тщательная разработка архитектуры, присущая этому подходу. В. А. Выссотски руководитель проекта Safeguard в фирме Bell Telephone Laboratories, утверждает: "Гвоздь проблемы состоит в том, чтобы описать продукт. Многие и многие неудачи вызваны теми его аспектами, которые не были как следует специфицированы"'). Аккуратное описание функций, тщательная спецификация и последовательное изгнание всяческих украшательств как в функциях, так и в методиках позволяет сократить число системных ошибок, которые предстоит отыскать в дальнейшем.

Проверка спецификаций.

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

Нисходящее проектирование.

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

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

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

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

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

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

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

Структурированное программирование.

Другой важный подход к проблеме программирования без ошибок принадлежит Дейкстре 3) и основывается на теоретических построениях Боема и Якопппи 4).

Это метод написания программ, в которых структуры управления состоят только из циклов, определяемых оператором DO WHILE, и условных операторов, составленных из взятых в скобки групп операторов, которым предшествует условие IF... THEN... ELSE. Боем и Якопини показали теоретическую достаточность этих структур. Дейкстра убедил, что произвольное употребление перехода по оператору GO TO дает структуры, ведущие к логическим ошибкам.

Обоснованность основной идеи очевидна. Она широко обсуждалась и совершенствовалась; в частности, такие управляющие структуры, как га-значный переход (так называемый оператор CASE), и "аварийный прыжок с парашютом" (GO TO ABNORMAL END) оказались очень удобны. В то же время призыв некоторых рьяных приверженцев этого метода к отказу от всех операторов GO TO кажется чрезмерным.

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

Автономная отладка

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

Машинная отладка.

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

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

Самым страшным грехом считалось тогда нажатие па кнопку ПУСК без предварительного разбиения программы на части с планируемыми остановами.

Выдача памяти.

Машинная отладка была очень эффективна: при выходе на машину, длившемся два часа, можно было получить до дюжины выдач. Но вычислен-тельных машин было мало, они были очень дорогие, и одна мысль о том, что почти все машинное время тратится впустую, приводила в ужас.

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

Выборочная выдача.

Машины, в которых при-менялась выдача памяти, имели сначала 2000-4000 слов, или от 8 до 16 тыс. байтов памяти. Но размеры памяти стремительно росли, и вскоре производить выдачу всей памяти стало непрактично. Поэтому появились методы для выборочной выдачи, для избирательной трассировки и для вставки в программу команд выдачи. Test ran в OS/360 может считаться последним словом в этом направлении; он позволяет вводить в программу команды выдачи без повторного ассемблирования пли рекомпиляции.

Диалоговая отладка.

В 1959 г. Кодд со своими коллегами 3) и Стрейчи 6) независимо опубликовали работы, посвященные отладке в режиме разделения времени, методу, позволяющему сочетать преимущества быстрой оборачиваемости в случае машинной отладки и эффективного использования машины при отладке " в пакетном режиме.

В обеих системах в памяти вычислительной машины хранилось несколько программ, готовых к выполнению. Каждая отлаживаемая программа имела в своем распоряжении программно-управляемый терминал. Управляла отладкой программа-супервизор. Когда программист за терминалом останавливал свою программу, чтобы посмотреть результаты или внести исправления, супервизор пропускал другую программу, так что машина постоянно оставалась занятой.

Мультипрограммная система Кодда была создана, но основное внимание ее разработчики направили на увеличение производительности путем улучшения эффективности ввода/вывода, и диалоговая отладка в ней не была реализована. Идеи Стрейчи были усовершенствованы Корбато п его коллегами и реализованы в 1963 г. на экспериментальной системе для IBM 7090 в MIT7). Эта разработка непосредственно привела к проектам Multics, TSS и другим сегодняшним системам разделения времени.

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

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

Тем не менее, известны интересные экспериментальные данные Гоулда8), показывающие, что при диалоговой отладке наиболее успешным бывает первый диалог 'каждого сеанса. Это позволяет с уверенностью утверждать, что мы не реализуем всех потенциальных возможностей диалога именно из-за отсутствия плана отладки. Настало время вернуть к жизни старые методы машинной отладки.

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

Тесты.

Что касается разработки реальных процедур отладки и тестов, то все это очень хорошо изложено в работе Груенбергера9), хотя есть и менее подробные изложения в некоторых других учебниках 10'11).

Системная отладка

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

Использование отлаженных компонент

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

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

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

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

Оснастка.

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

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

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

Предельным случаем мини-файла является фиктивный файл, которого в действительности вовсе нет. Язык управления задачами (ЯУЗА) в OS/360 обеспечивает такое средство, крайне полезное при отладке компонент.

Еще одна сторона оснастки - это вспомогательные программы. Генераторы тестовых данных, специальные анализирующие распечатки, анализаторы таблиц перекрестных ссылок - все это примеры той специальной "арматуры", которую вы можете подготавливать по своему желанию 13).

Контроль за изменениями.

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

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

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

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

Не более одной компоненты за раз!

Эта заповедь вполне очевидна, но оптимизм и леность заставляют нас пренебрегать ею. Чтобы ей следовать, нужны фиктивные программы и другие вспомогательные средства, а это требует дополнительного труда. И, в конце концов, может быть эта работа вовсе не нужна? Может быть, отпибок-то и нет?

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

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

Квантуйте изменения!

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

Каждая бригада, разрабатывающая новый вариант своей компоненты, использует последнюю отлаженную версию объединенной системы как основание для отладки своего куска. Их работа усложнится, если это основание вдруг окажется шатким - начнет изменяться. Конечно, изменения необходимы, но их следует квантовать. Тогда в распоряжении каждого пользователя будут периоды продуктивной стабильности, прерываемые точками - внесением изменений в систему. Но. они гораздо менее разрушительны, чем постоянное встряхивание. Биледи и Леман 4) утверждают очевидное: кванты должны быть очень большими и редкими во времени пли, напротив, очень маленькими и частыми. Последняя стратегия в соответствии с их моделью ближе к неустойчивости. Мой опыт подтверждает это: я никогда не рискнул бы применить эту стратегию на практике.

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

Next