Написано в 2000 г. | English |
Как растет программа Будем называть изменение программы безболезненным, если оно не требует редактирования ее исходного текста, т. е. сводится исключительно к расширению набора составляющих программу модулей. В работе предпринята попытка показать, что все эволюционные изменения программы могут быть выполнены безболезненно. Точнее говоря, справедливы два постулата. (1) Любая точка роста (hot spot) программы представима в виде горизонтального слоя — расширяемого набора однородных модулей. (2) Любое эволюционное изменение программы представимо в форме вертикального слоя — совокупности модулей, предназначенных для безболезненного пополнения одного или нескольких горизонтальных слоев.
Любая крупная программа на своем жизненном пути претерпевает революционные и эволюционные изменения. Революционные изменения радикально преобразуют программу, однако потребность в них возникает нечасто. Основной объем программистских усилий приходится, как правило, на эволюционные изменения. Эволюция программы идет относительно небольшими шагами (транзакциями). На каждом шаге имеющийся код — результат предшествующей транзакции — почти не меняется, но к нему добавляется компонент, реализующий новую функциональную возможность (use case). Как согласовать между собой структуры программы и транзакции, чтобы новый компонент органично вписался в сложившийся коллектив программных модулей и чтобы эволюционное изменение проходило безболезненно, не угрожая работоспособности ранее написанных частей программы? 1. Горизонтальные слои Еще более двадцати лет назад А.Л.Фуксман [1] обратил внимание на то, что добавляемый компонент обычно распадается на модули, которые относятся к нескольким уже сформировавшимся образованиям, так называемым горизонтальным слоям принимающей программы. В каждом из горизонтальных слоев локализуется какой-либо аспект реализации программы, и если добавляемый компонент достаточно нетривиален, он, скорее всего, должен оказать влияние сразу на несколько слоев. Сейчас представления о горизонтальных слоях становятся популярными и на Западе. Все чаще [2, 3] общая структура программы изображается в виде "квадратно-гнездовой" схемы (рис. 1), где горизонтальными овалами представлены горизонтальные слои, пунктирными прямоугольниками — компоненты, а на их пересечении обычно записывается название модуля (модулей). На рис. 1 названия модулей не приведены, а лишь отмечены звездочками те составляющие слоев, которые действительно присутствуют в программе. Дело в том, что компонент, разумеется, вовсе не обязан отметиться во всех горизонтальных слоях, и для сложной программы матрица модулей, подобная рис. 1, оказывается довольно-таки разреженной. Рис. 1. "Квадратно-гнездовая" схема программы Приведем два примера. Пусть вы пишите статью, и добавляемый к ней компонент — новый раздел. Тогда, наряду с размещением текста этого раздела среди уже имеющихся (горизонтальный слой "Основной текст"), требуется, вообще говоря, пополнить содержимое еще нескольких слоев. Возможно, добавится несколько новых позиций в библиографическом списке (горизонтальный слой "Литература"), появятся новые строчки в глоссарии и т. д. Второй пример. Вы разрабатываете текстовый редактор. И добавляете к нему компонент, реализующий еще одну возможность — скажем, контекстный поиск. Конечно же, основной объем нового исходного текста попадет в горизонтальный слой "Реализация основных операций". Однако кроме того придется внести дополнения в такие горизонтальные слои как "Меню операций" и, возможно, "Таблица горячих клавиш". При поверхностном взгляде на прямоугольные очертания рис. 1 может показаться, что горизонтальные и вертикальные направления симметричны, взаимозаменяемы. Но это неверно — направления существенно отличаются по своему содержанию. Горизонтальные слои — жизненно необходимая, неотторгаемая часть программы. Программа, лишенная каких-либо из своих горизонтальных слоев, скорее всего окажется непригодной ни к отладке, ни тем более к эксплуатации. В самом деле, изолированный список литературы, без собственно текста, нельзя назвать даже полуфабрикатом статьи. Изолированная таблица горячих клавиш не может быть запущена на выполнение, она почти ничего не способна рассказать о содержащей ее программе. И как эксплуатировать или хотя бы отлаживать текстовый редактор, если в нем отсутствует меню операций? Напротив, компоненты ("вертикальные слои", по терминологии А.Л.Фуксмана) лишь расширяют возможности программы, она, пусть в несколько усеченном виде, может функционировать и без них. Так, статью с несколькими отсутствующими пока разделами вы вполне можете показать коллеге — он, вероятно, легко поймет, о чем там идет речь, и сделает полезные замечания. Текстовый редактор, не имеющий пока возможности контекстного поиска, можно не только отлаживать, но и даже эксплуатировать. Тем самым вертикальные слои представляют собой материал, реализацию которого при создании крупной программы удобно отложить на потом. Эта особенность вертикального слоя впервые была подмечена А.Л.Фуксманом [1] и послужила отправной точкой предложенной им стратегии поэтапной разработки. Согласно А.Л.Фуксману, на первом этапе создается "основа" — предельно упрощенная версия программы, остающаяся после удаления из нее всех вертикальных слоев. Затем, на последующих этапах (транзакциях) реализуются и добавляются к расширяемой таким образом программе все новые и новые вертикальные слои. С технологической точки зрения заметное преимущество данной стратегии состоит в том, что при отладке любой промежуточной (без некоторых вертикальных слоев) версии программы тут не требуются "заглушки" — имитаторы недостающих частей, без которых не могут обойтись известные стратегии "сверху вниз" или "снизу вверх". Несколько иным путем приходят к той же самой структуре расширяющего программу компонента в работах [2, 3]. Здесь компонент рассматривается с позиций многократного использования (reuse). Отмечается, что сложившийся стереотип представления о многократно используемом компоненте как об обособленной односвязной подпрограмме или классе неоправданно обедняет механизмы взаимодействия компонента и принимающей программы. Существенно более плодотворным является представление о программе как о совокупности горизонтальных слоев, в каждом из которых добавляемый многосвязный компонент вправе оставить свой след. Так или иначе, мы имеем веские основания для того, чтобы любую транзакцию, обслуживающую эволюционное развитие программы, оформлять как добавление нового компонента (вертикального слоя), который распадается на модули, предназначенные, вообще говоря, для нескольких горизонтальных слоев. Математик тут, вероятно, сказал бы: "Любое регулярное изменение программы разложимо по базису горизонтальных слоев". Это разложение равно применимо как к частям программы, разрабатываемым специально для нее, так и к заимствуемым многократно используемым компонентам. Далее нас редко будет интересовать происхождение материалов эволюционной транзакции, а термины "вертикальный слой" и "компонент" будут употребляться в основном как синонимы. 2. Двумерная структура программы Реализация техники горизонтальных и вертикальных слоев сталкивается с определенным противоречием. Структура программы здесь по существу двумерна, а все получившие распространение алгоритмические языки предполагают одномерное представление исходного текста. В результате разработчик оказывается перед выбором: если спроектировать горизонтальные слои в виде непрерывных участков исходного текста, то модули, составляющие вертикальный слой, окажутся далеко отнесенными друг от друга. Если же непрерывными сделать вертикальные слои, то это повлечет разобщенность модулей горизонтального слоя. Остроту данного противоречия хорошо иллюстрируют технологические решения, принятые в упомянутых выше работах. А.Л.Фуксман [1] отдавал предпочтение непрерывности горизонтальных слоев. В результате вертикальный слой оказывался рассредоточенным по тексту программы и потребовалась специальная весьма тяжеловесная конструкция — "сосредоточенное описание рассредоточенного вертикального слоя". Ведь просто растворить безвозвратно модули вертикального слоя в тексте программы было бы, по меньшей мере, нетехнологично. Во-первых, вертикальный слой — важный структурный элемент, увидеть контуры которого чрезвычайно полезно при последующем изучении исходного текста. Во-вторых, нередко требуется отменить подключение одного из вертикальных слоев, и без сосредоточенного описания сделать это очень нелегко. В работах [2, 3], напротив, непрерывными оказались компоненты, а рассредоточенными — горизонтальные слои. Компоненты оформлены как классы (объекты), которые содержат в себе составляющие горизонтальных слоев. Здесь требуются заметные усилия для организации работы горизонтального слоя как единого целого. Не все благополучно и с наглядностью исходного текста: непросто бывает увидеть текущий состав горизонтального слоя программы. Наконец, рассредоточение горизонтального слоя по нескольким классам нередко приводит к заметным потерям эффективности кода: таблицы приходится превращать в списки и т. д. Выход из противоречия, порождаемого двумерностью слоев, достаточно очевиден: требуются инструментальные средства, поддерживающие эту двумерность. Если понятия "горизонтальный и вертикальный слои" отразить в инструментальной среде, то в обиход разработчика войдут простые и надежные базовые операции включения/отключения вертикального слоя, а при изучении текста программы появится возможность просмотра и горизонтальных, и вертикальных конструкций. Проделать все это можно, например, не покидая используемого одномерного языка: на вход имеющегося транслятора будем подавать сгенерированный инструментальный средой одномерный текст. 3. Объявление модуля Чтобы инструментальная среда сумела поддержать двумерность, эволюционная транзакция должна обрести определенную структуру. Требуется, во-первых, добавляемый компонент (вертикальный слой) каким-то образом расчленить на модули, относящиеся к различным горизонтальным слоям, и во-вторых, предложить эффективную схему подключения этих модулей к принимающей программе. Начнем с расчленения добавляемого компонента. Нам нужно, чтобы компонент так или иначе задавал совокупность объявлений составляющих его модулей, предназначенных для указанных горизонтальных слоев. Можно предложить следующую конструкцию объявления модуля:
В первой строке указывается имя_горизонтального_слоя, для которого предназначен объявляемый модуль. Фигурные скобки, окаймляющие вторую строку, означают, что записанный в них элемент должен быть воспроизведен в одном или в нескольких экземплярах. Тем самым вводится возможность объявления составного модуля: каждый экземпляр элемента объявляет имя и значение одного из полей. Для чего могут понадобиться поля модуля? Вновь обратимся к нашим примерам. В модуле раздела статьи, предназначенном для горизонтального слоя "Основной текст", имеет смысл вычленить поля "Заголовок" и "Текст" — тогда появится возможность автоматически единообразно оформлять все заголовки и формировать оглавление. В библиографической ссылке можно вычленить поле "Автор" — тогда в списке литературы можно будет, например, выделить фамилии авторов курсивом. Структуру (совокупность констант), задающую горячую клавишу, лучше представить не в виде сплошного текста описания на языке программирования, а как совокупность значений отдельных ее полей — это и короче, и гибче. Далее в объявлении модуля следует элемент #APPLY. Окаймляющие его квадратные скобки говорят о том, что этот элемент может быть опущен. Элемент #APPLY используется только во вложенных объявлениях. Он задает применение — то, что должно остаться в исходном тексте объемлющего модуля после обработки конструкции #INSTALL_IN препроцессором. Применение представляет собой произвольный текст на исходном языке, среди которого могут размещаться значения полей объявляемого модуля, задаваемые вставками вида #имя_горизонтального_слоя.имя_поля. Вложенные объявления могут потребоваться, когда составляющие компонент модули тесно связаны между собой. Проиллюстрируем взаимозависимость модулей на рассмотренном выше примере раздела статьи, содержащего библиографические ссылки. Где должно размещаться объявление модуля библиографической ссылки? Лучше всего погрузить его непосредственно в текст раздела, поскольку в этом случае при удалении содержащего ссылку текстового фрагмента автоматически скорректируется и состав библиографического списка. Однако, как мы недавно заметили, текст раздела представляет собой самостоятельный модуль, точнее, поле "Текст" модуля, предназначенного для горизонтального слоя "Основной текст". Таким образом, здесь требуется объявить модуль библиографической ссылки внутри объявления другого модуля. Пусть, например, в текст раздела требуется включить ссылку на книгу Э.Дейкстры "Дисциплина программирования", причем при окончательной публикации ссылка должна там принять вид "[Дей78]". Тогда на месте ссылки в модуле основного текста записывается конструкция
Тем самым при размещении раздела в базе данных проекта горизонтальный слой "Литература" пополнится модулем с именем "Дей78" и еще двумя полями, "Автор" и "Текст". Элемент #APPLY обеспечит появление в публикуемом тексте раздела требующейся ссылки вида "[Дей78]". 4. Однородность модулей Предложенная в предыдущем разделе конструкция позволяет объявить в составе добавляемого к программе компонента несколько модулей, предназначенных для различных горизонтальных слоев. Далее возникает вопрос: каким образом определить точки текста программы, где следует разместить новые модули? Задать эти точки оказывается достаточно легко, если принять во внимание одну важнейшую особенность горизонтального слоя: составляющие его модули однородны. Под однородностью здесь понимается не только общность назначения (семантики) всех модулей слоя, но и полное синтаксическое их единообразие. Модули состоят из полей, и однородность означает, что состав полей будет одним и тем же для всех модулей горизонтального слоя, а значения одноименных полей в свою очередь однородны. Проиллюстрируем однородность модулей, вернувшись к рассмотренным примерам горизонтальных слоев. Первый пример: к готовящейся статье добавляется компонент — новый раздел. Горизонтальный слой "Основной текст" образуется из единообразно оформленных модулей — разделов, горизонтальный слой "Литература" — из единообразно оформленных библиографических ссылок и т. д. В каждом модуле слоя "Основной текст" присутствует поле "Заголовок", и все эти поля единообразно оформлены. Второй пример: разрабатываемый текстовый редактор пополняется новой операцией. Горизонтальный слой "Реализация основных операций" состоит, по-видимому, из однородных процедур, "Меню операций" — массив однородных структур (констант), "Таблица горячих клавиш" — тоже массив однородных структур, но, разумеется, другого типа. Любая транзакция, расширяющая программу, имеет вполне определенную структуру: она представима в форме множества расширений имеющихся горизонтальных слоев. Некоторые горизонтальные слои транзакция может вовсе не затрагивать, в некоторые слои добавлять один модуль, в некоторые — несколько модулей. Например, очередной раздел статьи может содержать произвольное число библиографических ссылок, в частности, вообще не содержать ссылок. Для одной операции текстового редактора может быть не предусмотрено горячих клавиш, для другой — отводится одна клавиша, для третьей — несколько клавиш (скажем, Ctrl+F и F11). Каждый горизонтальный слой фиксирует формат своих модулей, и пополнять его можно только однородными элементами этого формата. На рис. 2, как и на рис. 1, показана структура программы с горизонтальными слоями, но здесь иллюстрируется однородность модулей слоя и многообразие форматов составляющих модулей различных слоев. Рис. 2. Однородные модули горизонтальных слоев программы. Заштрихованы модули, добавленные в результате выполнения очередной транзакции 5. Представление горизонтального слоя Итак, горизонтальный слой вобрал в себя все однородные модули, направленные туда из включенных в программу компонентов. Далее этот однородный набор модулей должен каким-то образом превратиться в пригодный для трансляции текст на языке программирования. Какой же вид имеет горизонтальный слой в программе? Оказывается, горизонтальный слой может принимать форму любой однородной языковой конструкции. Конструкций таких в существующих языках имеется достаточно много. Однородны, например, константы в массиве констант, ветви в операторе выбора и др. Даже самая массовая языковая конструкция — группа параллельно или последовательно выполняемых действий — составлена из однородных частей-операторов. В исходном тексте горизонтальный слой оформляется в виде цикла периода компиляции [4]. Цикл повторяется столько раз, сколько модулей данного слоя заявлено во включенных в программу компонентах, а переменная цикла в это время последовательно пробегает все модули слоя. Цикл записывается непосредственно в тексте программы и имеет следующий вид
Предложения #HORIZON и #END_OF_HORIZON очерчивают границы цикла, а имя_горизонтального_слоя задает горизонтальный слой и одновременно служит в качестве переменной цикла, пробегающей по всем модулям слоя. В цикле будет многократно воспроизводиться тело_цикла, представляющее собой произвольный текст на исходном языке. В этом тексте располагаются значения полей модуля слоя, задаваемые вставками вида #имя_горизонтального_слоя.имя_поля. Как уже говорилось, число повторений цикла определяется числом подставляемых однородных модулей. Каждое повторение приводит к добавлению в формируемый текст программы еще одного экземпляра текста тела цикла, где на место вставок помещены тексты соответствующих полей очередного модуля. Конструкций #HORIZON, относящихся к одному и тому же горизонтальному слою, в исходном тексте может присутствовать несколько. Так, горизонтальный слой "Основной текст" статьи будет использован по крайней мере дважды: для составления собственно основного текста с заголовками разделов и для составления оглавления, куда из него войдут только заголовки разделов. Вернемся к разобранному выше примеру оформления библиографических ссылок. Для определенности будем полагать, что статья готовится для публикации в Интернете и потому пишется на языке HTML. (Автор сознательно выбрал для иллюстраций "неалгоритмический" язык HTML, поскольку сейчас с этим языком знакомо, по-видимому, подавляющее большинство программистов. Любители Си, Паскаля и других популярных языков найдут десятки "более программистских" примеров в книге [4].) Все объявленные в тексте статьи ссылки можно собрать в единый список литературы посредством конструкции
Все ссылки выстроятся одна за другой, и каждой из них будет посвящено пять строк на языке HTML, определяющих формат списка. Если вспомнить ссылку на книгу Э.Дейкстры из разобранного ранее примера, то на экране дисплея в результате обработки библиографического списка браузером она примет вид [Дей78] Дейкстра Э. Дисциплина ... Разумеется, для реальных применений предложенная конструкция, представляющая горизонтальный слой, должна быть тщательно отшлифована и несколько усовершенствована. Например, имеет смысл добавить средства, позволяющие упорядочивать формируемый библиографический список по именам ссылок или в последовательности появления в тексте статьи. Потребуются усовершенствования и для предложенной выше конструкции объявления модуля. Однако ничего принципиально нового эти усовершенствования не привнесут: и идеология, и реализация описанной схемы эволюции программы оказываются достаточно компактными и эффективными. 6. Безболезненность эволюции Мы убедились, что реализация техники горизонтальных слоев не только возможна, но и относительно проста. Подытожим теперь, что же дает программисту применение этой техники. Прежде всего, двумерные конструкции позволяют наглядно отобразить наиболее существенное, глубинное содержание текста программы. Вертикальные слои (компоненты) характеризуют многообразие внешних проявлений программы, а горизонтальные — цементируют ее, обеспечивают единство алгоритмических решений и пользовательского интерфейса. Новые возможности открываются при разработке многократно используемых компонентов. Описание взаимодействия крупного компонента с принимающей программой больше не выглядит как громоздкий хаотический набор разнородных интерфейсных соглашений. Теперь оно наглядно и технологично разложено на унифицированные интерфейсы модулей, размещаемых в отдельных горизонтальных слоях. Есть и еще одно, менее очевидное, но весьма существенное преимущество техники горизонтальных слоев. Подключение нового вертикального слоя (компонента) происходит здесь безболезненно, не требуя какого бы то ни было редактирования написанных ранее исходных текстов. В самом деле, любая транзакция, обслуживающая эволюцию программы, распадается на ряд подключений новых однородных модулей к существующим горизонтальным слоям. А каждое такое подключение происходит безболезненно: модуль просто помещается в базу данных программного проекта и снабжается атрибутом принадлежности к определенному горизонтальному слою. И не нужно редактировать исходные тексты принимающей программы, да и вообще ничего больше делать не надо: при компоновке программы из базы данных проекта будут ассоциативно извлечены и помещены во вновь формируемый горизонтальный слой все модули, снабженные данным атрибутом. Немаловажно и то, что легко, в стиле "Plug and play" происходит не только подключение компонента, но и его исключение ("Unplug and play") из программы. Ведь в базе данных программного проекта компонент всегда доступен не только как совокупность составляющих модулей, но и как единое целое. Поэтому исключение компонента сводится здесь к уничтожению его как объекта базы данных и не требует никакой коррекции окружающих участков исходного кода. При других структурах эволюционных транзакций исключение внедренного ранее компонента сопряжено обычно с серьезными трудностями — достаточно вспомнить известную проблему деинсталляции приложения в MS Windows. Наконец, массовое признание двумерного программирования означало бы торжество чрезвычайно продуктивной идеи: любая точка роста (hot spot) программы может быть представлена в виде горизонтального слоя — расширяемого набора однородных модулей. 7. Перспективы Сейчас, казалось бы, самое время перейти к рассказу о фирмах-изготовителях аналогов описанной инструментальной среды или хотя бы указать сроки появления на рынке средств поддержки двумерных программных конструкций. К сожалению, придется разочаровать заинтересовавшегося читателя: несмотря на растущее число посвященных данной проблематике публикаций, в массовом программировании эти инструменты появятся, похоже, еще нескоро. Хотя реализация двумерности относительно проста, однако она затрагивает многие деликатные аспекты (горизонтальные слои) инструментальной среды. Достаточно упомянуть символьную отладку, где в любой точке выполнения программы пользователь должен теперь иметь возможность увидеть все три ипостаси ее исходного текста: вертикальную, горизонтальную и окончательную, т. е. попавшую на вход традиционного компилятора. Из-за этого не удастся, по-видимому, реализовать поддержку двумерности в форме надстройки (plug-in), не вторгаясь в код инструментальной среды. Образовался порочный круг. Без участия фирмы-изготовителя безболезненно надстроить поддержку двумерности не удается, поскольку код современной инструментальной среды недостаточно открыт для расширений. Но открытости коду недостает прежде всего потому, что в свое время у его создателей не было в распоряжении все тех же двумерных инструментов. В результате код среды получился по сути не двумерным — там не нашли надлежащего оформления, остались непроявленными расширяемые горизонтальные слои. Выбраться из этого порочного круга мы сумеем, вероятно, лишь тогда, когда фирмы-изготовители инструментальных сред сочтут двумерные программные конструкции достойными внимания и сами включат их поддержку в свои продукты. Литература
Рекомендуемая форма библиографической ссылки на данную статью Горбунов-Посадов М.М. Как растет программа. — Препринты ИПМ им.М.В.Келдыша. — 2000. — № 50. — 16 с. — URL: http://www.keldysh.ru/gorbunov/grow.htm или Горбунов-Посадов М.М. Эволюция программы: структура транзакции // Открытые системы. — 2000. — № 10. — С. 43‑47. — URL: http://www.osp.ru/os/2000/10/178257/ Другие статьи на данную тему Горбунов-Посадов М.М. Конфигурационные ориентиры на пути к многократному использованию. — Препринты ИПМ им.М.В.Келдыша. — 1997. — № 37. — 16 с. — http://www.keldysh.ru/gorbunov/reuse.htm Горбунов-Посадов М.М. Система открыта, но что-то мешает // Открытые системы. — 1996. — № 6. — С. 36‑39. — http://www.keldysh.ru/gorbunov/open.htm Горбунов-Посадов М.М. Безболезненное развитие программы // Открытые системы. — 1996. — № 4. — С. 65‑70. — http://www.keldysh.ru/gorbunov/evolution.htm |