Проектировщик соглашается на компромисс между стоимостью и скоростью, между простотой запросов данных и скоростью их поиска, между объемом занимаемой памяти, схемой упаковки и временем ответа, между средствами распределения ресурсов и богатством функций, между средствами управления окружением и выполняемыми функциями, а также между многими другими характеристиками системы.
Заниматься проектированием может не каждый. Дейкстра в статье «Структура мультипрограммной системы „THE“»[14] отмечает два момента:
Попытки избежать разного рода «патологий» отняли гораздо больше сил, чем мы ожидали, причем некоторые наши беды были прямо связаны с нашими изобретениями на первых этапах работ… Если бы мы больше уделяли внимания патологии на ранних стадиях проектирования, мы бы выработали гораздо менее утонченные правила управления работой
И
… по крайней мере в моей стране (имеются в виду Нидерланды) интеллектуальный уровень, необходимый для проектирования систем, чаще всего сильно недооценивается. Этот вид работ чрезвычайно труден, и любые попытки вести проектирование иначе как с помощью наилучших специалистов, обречены либо на провал, либо на весьма умеренный успех за огромную плату
Обратите внимание на слова: «наилучшие специалисты».
Выбирая проектировщика, прежде всего обращайтесь к тем, кто уже ранее выполнял такую работу! К тому, кто уже вел систему от самого начала до самого конца! Постарайтесь договориться с ним. Если в вашем распоряжении нет людей, проектировавших ранее программное обеспечение
Тот
Если проектированием занимается слишком много людей, если при проектировании слишком увлеклись демократией или если
Зрительно представить себе программное обеспечение очень трудно. Статические представления его весьма бледны, но зачастую это лучшее, что нам удается сделать. Рассмотрим пример программного обеспечения системы наземного контроля проекта «Аполлон», это обеспечение работало в Хьюстоне много лет, и оно поможет нам увидеть все, что мы намеревались создать.
У нас была большая (более 1 млн. команд) программная система, хорошо документированная и оттестированная. Все эти команды были распределены по программам, подпрограммам и модулям. Подразделы строились отдельно и включались во внешние структуры.
Наше программное обеспечение «приняло» несколько форм. У нас имелась распечатка (напечатанный на машине текст) всех команд по порядку. Имелись колода перфокарт и магнитная лента, на которой была записана программа. Ни одно из этих воплощений программы человеку увидеть не легко — даже распечатку. Ни одно из них не давало возможность увидеть структуру или последовательность выполнения команд программы. Чтобы понять идею системы, нам требовались такие схемы, как изображенные на рис. 5.19 и 5.20. Вся программная система была разделена на восемь основных частей, что и показано на рис. 5.19.
Назначение каждой подсистемы совершенно ясно из их наименований. Общее управление системой осуществлялось операционной системой реального времени, которая определяла, какую программу надо исполнять в данное время.
Теперь перейдем к более подробному изучению подсистемы запуска (рис. 5.20). Каждый квадратик обозначает очень большую программу, которая при необходимости будет выполнена. Операционная система определяет,
Когда мы смотрим на статическое представление программы, подобное дереву на рис. 5.21, мы должны помнить, что каждый квадрат — это программа, которая может быть выполнена в любое время и в самых разных комбинациях с другими программами.
Для наземного контроля системы «Аполлон» было создано более 400 программных модулей. Но все же «познать» программу мы не могли. Диаграмма давала нам лишь безжизненную иллюстрацию. Работающая программа — это нечто совсем другое.
Это все равно что сравнить художественный фильм с пленкой, на которой снят этот фильм. Увидеть пленку без использования кинопроектора не означает еще увидеть фильм! Мы имеем здесь два совершенно разных явления! Слово «пленка» обозначает понятие носителя, на котором заснят фильм; а слово «фильм» связано с самим результатом — объектами, о которых идет речь в фильме. К счастью, в области программного обеспечения такого рода путаница не возникает.
В проектировании можно выделить три части. Принятие решения, структуризацию и представление (описание) (см. рис. 5.22).
ПРИНЯТИЕ РЕШЕНИЯ
Собираемся ли мы организовывать поиск последовательным просмотра файла или хотим применить метод двоичного поиска — настраиваясь сразу на середину файла, определяя нужную нам половину, верхнюю или нижнюю, и затем переходя в середину выбранной части, многократно повторяя данный процесс?
Это и есть принятие решения, выбор алгоритма, процесса. Обычно это творческий акт, в результате которого возникает новый метод, особенно в новых, сложных или очень крупномасштабных разработках.
Выбрав некоторый метод, мы «детализируем» его, превращая в некоторую «структуру». В любой значительной работе к одному и тому же результату могут привести десятки различных структур. Различия в структурах могут быть значительными, результат же будет одинаковым.
Например, выбрав метод двоичного поиска, я могу писать программу либо на Фортране, либо на Коболе. В Коболе затруднено индексирование, а в Фортране очень неудобны манипуляции с данными. Программы на этих языках будут сильно отличаться друг от друга; в результате две разные программы будут осуществлять один и тот же двоичный поиск.
Оценивать структуру можно разными способами. Двумя мерами оценки могут служить модульность и четкость интерфейса.
Описание, или представление, программы может принимать различные формы. Это могут быть блок-схемы, снабженные некоторыми примечаниями; это могут быть иерархические схемы ввода, обработки, вывода (HIPO), схемы Вернера — Орра и т. д.
Целью описания является объяснение новичкам и продолжателям разработки назначения программы и методов, которыми она решает задачу. Следовательно, оценивать описание нужно по тому, насколько хорошо оно отражает эти сведения. «Хорошо» означает понятно, однозначно и немногословно.
В книге Лоуренса Хэлприна «Творческие процессы в жизни людей»[15] исследуются процессы, с помощью которых люди пытаются проиллюстрировать, документировать в удобочитаемом виде формы в различных областях искусства или связанную с ними деятельность. Слово «партитура» используется автором для обозначения представления, «синтезирования процесса, его развития во времени. Наиболее известны музыкальные партитуры, но я расширил это понятие и включил в него все области человеческой деятельности».
Далее Хэлприн переходит к обсуждению танцев и партитур для них. Вслед за этим он исследует городское планирование, астрономию, архитектуру, игру, посадку на Луну — и десятки других видов деятельности. Он показывает, какой замечательной изобретательностью отличаются люди при создании методов изображения развивающихся процессов.
Он также указывает, что, хотя партитура и сама деятельность неразрывно связаны, они сильно отличаются друг от друга. Скрипач-виртуоз исполняет Брамса намного лучше, чем студент, пользующийся теми же нотами. Разработчики программного обеспечения знают, что всякое описание большой программы статично и что в процессе ее работы может случиться множество вещей, не отраженных в описании! Мы не можем описывать выполняющуюся программу. На с.96 мы уже отмечали тот факт, что при выполнении различных вариантов последовательностей команд их даже невозможно оттестировать. Когда начинает выполняться наша большая система программ, реализуется какой-нибудь один из вариантов.
Многие из новейших «методов проектирования», предлагаемых в настоящее время, имеют чисто описательный характер. Однако это не должно их опорочить. Мы отчаянно нуждаемся в лучших способах обмена сложными идеями.
В большинстве книг, в том числе и в этой, мало пишут о первой части проектирования или создания. Мы просто еще не готовы много говорить об этом. Книги, посвященные подобным вопросам, имеют гораздо более широкую область приложения, чем вычислительная техника и программное обеспечение. Обычно в них идет речь о свободных ассоциациях, или о «мозговом штурме», или об изучении процесса решения реальных проблем.
Процесс, ход проектирования понимается недостаточно хорошо даже самыми опытными программистами. Многие выполняют его инстинктивно, пропуская некоторые шаги, необходимые для хорошей разработки. Многие выполняют этот процесс очень плохо.
Показанный на рис. 5.23 процесс проектирования начинается после определения требований к системе и развивается, охватывая все более мелкие его этапы.
Функции процесса определения требований рассматривались нами в предыдущем разделе книги. На этой схеме мы снова показываем этот процесс, причем документы, получающиеся в результате, являются первой, исходной точкой проектирования. Заметьте, что мы отделяем действие от продукта, получающегося в результате, помечая и то и другое. Делаем мы это потому, что при разработке программного обеспечения и систем часто путают некоторые из этих терминов.
Второй шаг, проектирование, связан с рассмотрением различных подходов к решению проблемы и с выяснением, могут ли они привести нас к этому решению. Этот шаг часто связан с применением методов проб и ошибок, нередки возвраты к предыдущему шагу. По мере продвижения вперед, нам часто бывает необходимо развить наш проект еще дальше, чтобы проверить, действительно ли мы можем завершить его так, как нам бы этого хотелось. После того как это будет проделано достаточное число раз и с достаточной степенью детализации, мы получим некий подход — и очень часто он оказывается искомым подходом, в большинстве случаев он более или менее работоспособен.
К сожалению, в подавляющем большинстве случаев проектирование на этом и заканчивается, а все дальнейшие работы по созданию системы связаны с воплощением в жизнь только что выбранных вариантов.
Заметим, однако, что в результате второго шага создаются многие тома документов и отчетов. Ведь мы получили какое-то решение. В некоторых случаях кипа документов достигает двух и более метров в высоту.
Может ли это называться проектом? Конечно, но только не вполне законченным, причем, по всей вероятности, он не очень-то нам понятен и его трудно кому-нибудь объяснить! Даже если он правилен!
И надо проделать еще третий шаг, чтобы сделать полученный проект более компактным, четким, легким для понимания и объяснения и, следовательно, для реализации.
Чаще всего третий шаг, связанный с синтезацией и уменьшением объема проекта,
Преимущества этого шага, вносящего в проект ясность и краткость, сразу бросятся в глаза после его выполнения. Проект станет легко описываемым и вполне понятным. Лежащие в основе принципы понять просто. Результаты проведенного ранее эвристического (методом проб и ошибок) проектирования (шаг 2) слишком объемисты и неясны. Проект может быть правильным, но пользование им будет связано с большими затратами труда и времени. Обычно он бывает понятен одному или, в более счастливом случае, нескольким лицам. Все остальные участники работ понимают до конца лишь части системы, но не систему как целое.
Переработанный после шага 3 проект имеет то преимущество, что его можно показать всем, тогда проектировщики и программисты подсистем смогут работать над своими частями с большей пользой для общего дела.
На четвертом шаге создается проект верхнего уровня. Положения, сформулированные в нем, служат исходными требованиями для работ по созданию других проектов, более низких уровней.
Повторным применением шага 4 ко всем более низким уровням проекта мы создаем целую серию проектов. Это длится до тех пор, пока не будет создан проект самого нижнего уровня. На каждом уровне создаются как описания требований к более низкому уровню, так и проект модуля; этот процесс продолжается до тех пор, пока не будет достигнут уровень реализации (написания) или уровень тривиальности. Чаще возникает эта последняя ситуация.
Рассмотрим различные варианты третьего шага: шага критической оценки и очистки. На рис. 5.24 направление процесса проектирования указывается стрелкой — слева направо. С левой стороны, в начале проектирования, обозначен процесс
Повторяю, что подобная очистка часто не производится. Производить ее трудно, для этого требуется особый талант и очень много труда. Но проводить ее все же надо обязательно.
Расширяющееся пространство на рисунке справа от шага очистки служит для обозначения всевозрастающего объема проектирования и документирования по мере продолжения работ. На базе сформулированных нами фундаментальных основ проектирования мы приступаем к наращиванию уровней детализации, двигаясь прямо к реализации проекта.
Джон Ливингстон Лоувс (1867–1945) в книге «The Road to Xanadu» (1927 г.) пишет о творчестве и приводит примеры великих открытий. Он устанавливает в процессе творчества, открытия, три части, которые называет: ИСТОЧНИК, ВИДЕНИЕ и ВОЛЯ. В источнике находятся все факты, которые ожидают, чтобы их использовали…
Где, действительно, в каждый момент находятся все несчетные известные нам факты и миллионы пережитых нами эпизодов? Где бы это таинственное место ни было… ИСТОЧНИК — это только подходящее обозначение для тайны. И там, поглощенные некими неведомыми глубинами, для всех намерений и целей как бы не существовавшие — они покоились в глубоком сне. Некоторым было суждено дремать неделями, некоторым месяцами, а некоторым целые годы. И вдруг все сразу они пробудились..
Картина пришла в движение и начала разворачиваться помимо моей воли. На мгновение я просто позволил течь потоку образов. Затем я осторожно стал управлять им…
Без ВИДЕНИЯ хаос элементов остается хаосом. А ФОРМА спит вечным сном в просторном хранилище не родившихся замыслов. И в том хаосе лишь творческое ВИДЕНИЕ может различить эту ФОРМУ. Точно так же без содействия ВОЛИ, подчиненной этому ВИДЕНИЮ, воспринятая в сонме других картина не могла бы стать объективной реальностью.
То, что Лоувс называет тремя основными составляющими, очень напоминает описанные нами три части процесса проектирования. Его «воля» хотя и не является полным эквивалентом очистки, но близка ей.
Проектирование автомобилей связано с взаимодействием многих процессов. На рис. 5.25 изображен нисходящий поуровневый метод проектирования автомобиля. На самом верхнем уровне мы пытаемся представить себе автомобиль как целое. На последующих уровнях составляются требования на двигатель, рулевое управление, приборную доску, и, конечно же, на этом уровне может быть еще очень много других составных частей.
В последующем мы начинаем делить на части все эти подсистемы. Перед нами начинает вырисовываться древовидная структура, в которую включается все больше подробностей. Это длится до тех пор, пока мы наконец не достигнем уровня тривиальности, на котором добавление дополнительных подробностей ничего нового не дает. Когда мы начинаем проектировать оконное стекло, или фары, или дверную ручку, мы очень скоро достигаем уровня, на котором все, что мы можем сказать, это — «мы получаем то, что нам было нужно», будучи полностью
Выбор подсистем, на которые надо делить наш объект, это и есть акт проектирования, он оказывает глубокое влияние на нашу работу по созданию системы и на ее дальнейшее функционирование.
Такое поуровневое проектирование может быть обнаружено практически во всех областях, причем чем более отработана технология, тем более стабильна и надежна та информация, которая передается с одного уровня на другой.
В программном обеспечении ни один из уровневых механизмов не достиг нужной стабильности. Содержимое каждого уровня запутанно, а механизмы передачи информации с уровня на уровень ненадежны, или их вовсе нет. Тем не менее проектировщики нижних уровней часто соглашаются на фрагментарные или невразумительные требования/проекты, потому что
Отсутствие «технологической инфраструктуры» приводит к тому, что процесс (или система) производства программного обеспечения не застрахован от ошибок.
Программа управления, созданная для посадки на Луну, состоит примерно из 400 модулей. В операционной системе IBM — ОС для системы 370 в 1975 г. было более 3000 модулей.
Очевидно, что число различных способов их взаимодействия очень велико. В самом начале процесса отработки взаимосвязей наши возможности имеют поистине астрономические масштабы.
Мы делим функции больших программных систем на все меньшие единицы до тех пор, пока размеры модулей не достигнут порядка 30–50 операторов на языке высокого уровня. После этого начинаем связывать модули друг с другом. Затем их объединяют в подсистемы.
Дойдя до этого места, разработчики вычислительной аппаратуры могут сообразить, что все, о чем мы ведем речь, уже сформулировано в одном из принципов аппаратных систем, который гласит, что чем чище и определеннее связи между различными частями системы, тем легче будет в дальнейшем произвести разделение работ и получить работающие и проверочные подсистемы. Чем более замкнутой является каждая отдельная часть, тем легче будет выполнить всю работу. То же самое мы делаем и с программами, называя результат этого процесса «упрятыванием информации». Скоро мы расскажем об этом подробнее.
При разработке программного обеспечения замкнутость приобретает еще большую важность, поскольку программисту бывает очень легко перешагнуть «границы» программных модулей и забраться в модули, принадлежащие кому-то другому. Нам нужно планировать взаимодействие между компонентами и определять то, что нужно выполнять с помощью подпрограмм.
Связывание и разделение мы проводим на основе расслоения. Все связи между подсистемами должны быть точными и ясными. Это значительно облегчит тестирование, поскольку мы получим возможность сначала протестировать отдельные подсистемы и лишь затем, приступив к их совместному тестированию, сосредоточить внимание на взаимосвязях.
Одной из основ всех сложных предприятий является принцип «разделяй и властвуй», это оказывается верным и для программного обеспечения. Расслоение облегчает задачу повторной сборки построенных блоков после внесения в некоторые из них исправлений Этот принцип очень стар. Он применяется во многих областях человеческой деятельности, в том числе — в технике. Это главнейший принцип, используемый при настоящем техническом подходе к делу.
Хотя обычно мы не рассматриваем операционную систему или систему управления базой данных в качестве отдельного слоя общей системы, по сути это на самом деле так. С каждой из этих подсистем мы связываем определенные функции, причем эти функции сосредоточиваются в ограниченной части системы.