4.12. Материалы отката
4.12.1. Поколения и поставки. Материалы отката отражают события, происходящие с текстом модуля или программы. За время своего существования отдельные модули и программа в целом обычно претерпевают многочисленные реорганизации. Каждая реорганизация или модификация порождает очередной экземпляр текста модуля, который мы будем называть поколением модуля.
Разумеется, модификация модуля означает и модификацию программы. Далеко не все промежуточные состояния программы представляют интерес большинство из них нет никакой необходимости как-либо явно отражать в программном фонде. Однако отдельные состояния все же фиксируются, и для этого есть весомые причины.
В частности, когда текущая версия программы передается во внешнюю организацию, имеет смысл на время эксплуатации ее в данной организации сохранить в программном фонде переданную версию. Это существенно облегчит разбор рекламаций, которые будут поступать от организации-пользователя. Анализ полученных ошибок можно будет вести, откатившись к той среде, в которой эти ошибки первоначально были зафиксированы.
Ведь за время, прошедшее от момента передачи версии до прихода рекламации, в программу могут быть внесены многочисленные изменения. Поэтому если, скажем, на новой (текущей) версии обнаруженная пользователем ошибка не проявилась, то довольно трудно будет определить, связано ли исчезновение ошибки с выполненными улучшениями программы или, к примеру, с различиями операционных сред пользователя и разработчика.
Иногда приходится фиксировать версию программы, на которой проведен какой-либо ответственный расчет. Фиксируется также относительно надежно функционирующая версия перед тем, как начать радикальную реконструкцию программы. Но первая из названных причин фиксации состояния передача во внешнюю организацию встречается существенно чаще и в какой-то мере охватывает две последующие. Поэтому именно от нее мы оттолкнемся при введении соответствующего термина: будем называть зафиксированные в программном фонде промежуточные состояния разработки программы поставками.
Поставки одна из причин появления в программном фонде поколений модулей. Действительно, если предпринимается попытка модифицировать модуль, включенный в поставку, то прежде всего надо создать новый экземпляр текста, т. е. новое поколение модуля, и уже над ним выполнять редактирующие действия, сохраняя нетронутым модуль из поставки. Какие еще причины ведут к появлению поколений?
Наиболее весомая причина постоянный страх перед выполнением редактирования, испытываемый программистом из-за возможности случайного уничтожения крупного куска отлаженного текста. Вероятно, каждый практикующий программист не раз попадал в ситуацию, когда, закончив сеанс редактирования и тем самым зафиксировав в программном фонде выполненные изменения текста модуля, ему приходилось хвататься за голову, восклицая: «Что я наделал!». И затем благодарить Бога за предусмотрительность разработчиков операционной среды, позаботившихся о сохранении в программном фонде прежней версии (поколения) модуля, откат к которой позволяет восстановить казавшийся утраченным текст.
В крупных проектах поколения нередко становятся одной из наиболее действенных форм документирования и управления ходом разработки. Вечный враг крупного проекта многочисленные хаотические неуправляемые изменения. Чтобы привести их в порядок, вводится дисциплина, не позволяющая вносить изменения без разрешения руководства. Тогда каждое изменение фиксируется в программном фонде в виде поколения, снабженного записями о том, кто вносил изменение, для чего оно вносилось и кто его санкционировал [Липаев, 1998]. В то же время спецификации изменений отнюдь не заменяют, а лишь дополняют основные спецификации; последние должны систематически корректироваться, чтобы отражать текущее состояние программы.
Некоторый аналог поколений возникает в ситуации, где один и тот же текст модуля параллельно независимо реформируется в различных направлениях и/или различными разработчиками. Каждое направление реформирования заводит себе свой автономно редактируемый экземпляр текста (обычно называемый версией). Когда все заканчивают редактирование своих экземпляров, производится пренеприятная операция слияния изменений, выполненных по каждому из направлений.
Несмотря на целый ряд известных системных средств, поддерживающих подобную технику развития программы, массового признания она не получила. Хотя системные средства создают иллюзию легитимности и надежности параллельного редактирования, однако ни для кого не секрет, что у семи нянек дитя в конце концов оказывается без глазу. Еще точнее здесь аналогия с лебедем, раком и щукой, тянущими воз программного проекта в разные стороны.
Потребность в применении этой техники возникает, по-видимому, из-за невысокого качества модульного анализа, когда не всем потенциальным направлениям изменений были поставлены в соответствие самостоятельные модули. Прежде чем ступать на полную опасностей тропу параллельного редактирования, имеет смысл попытаться найти иной путь, перепроектируя сложившуюся неудачную модульную структуру. Тут бывает полезно не замыкаться на представлении о модуле как автономно транслируемой единице, а посмотреть на проблему шире, привлекая нетрадиционные формы модулей.
Называют и некоторые другие причины сохранения в программном фонде поколений модулей. Например, сопоставляя последовательные поколения, можно проанализировать (и попытаться улучшить) стиль разработки и отладки программ. Однако на практике до такого анализа, как правило, не доходят руки. Кроме того, за него пришлось бы заплатить излишне высокую цену, расходуя для хранения многочисленных поколений далеко не беспредельные ресурсы внешней памяти. (Впрочем, запоминать полные тексты поколений, отличающихся небольшими изменениями, вовсе не обязательно. Известна техника дифференциальных файлов, где помимо текста одного, обычно первого или последнего, поколения хранится лишь компактная информация об отличиях каждого последующего поколения от предыдущего.)
Остальные упоминаемые иногда причины появления поколений также не столь существенны. Таким образом, главную роль играют необходимость фиксации поставок программ и желательность организации отката отдельных модулей.
Сколько поколений модуля следует хранить в программном фонде? Ясно, что по крайней мере все отличающиеся друг от друга поколения, участвующие в зафиксированных поставках. Кроме того, разумеется, всегда хранится текущее, и, как правило, предпоследнее поколение модуля. Чаще всего дело этим и ограничивается, но иногда встречаются операционные среды, где по умолчанию в программном фонде остаются все когда-либо существовавшие поколения, которые уничтожаются только по явному указанию пользователя.
Сравнительно несложно реализуется и промежуточная схема, где число хранящихся поколений составляет логарифм от числа когда-либо существовавших, причем разности между номерами текущего (последнего) и остальных хранимых поколений составляют возрастающую геометрическую прогрессию со знаменателем, равным основанию упомянутого логарифма. Тем самым достигаются сгущение свежих и разрежение уходящих в прошлое поколений.
4.12.2. Варианты и поколения. Так или иначе, в программном фонде накапливаются многочисленные поколения и поставки. Попытаемся разобраться, какое участие они могут принимать в формировании конкретных конфигураций многовариантных программ.
Ничто не мешает соединить многовариантную среду с аппаратом поставок. Если указано, что расчет должен быть выполнен на базе определенной поставки, то это означает, что и среда подготовки расчета, и формируемая программа должны строиться исключительно из материалов программного фонда, зафиксированных при поставке. Серьезных трудностей такие построения не вызывают.
Сложнее обстоит дело с поколениями отдельных модулей. Первое решение, которое тут приходит в голову, предоставить пользователю максимальную свободу, т. е. разрешить ему формировать выполняемую программу из произвольной комбинации различных поколений составляющих ее модулей. Однако оказывается, что это решение таит в себе определенные противоречия.
Если свободное манипулирование поколениями освящено системной поддержкой, у пользователя подсознательно возникает ощущение априорной добротности формируемых таким образом программ. Но произвольная комбинация поколений вполне может оказаться недееспособной из-за рассогласованности интерфейсов. Как уже упоминалось, не все модификации многовариантной программы удается уложить в русло комбинирования старых или подключения новых сменных модулей. Неожиданные революционные идеи подчас требуют радикальной реконструкции программы, которая не может не отразиться, в частности, на некоторых интерфейсах. В результате поколения модулей, разделенные подобной реконструкцией, не всегда удается состыковать в одной выполняемой программе.
Итак, произвольное комбинирование различных поколений, вообще говоря, некорректно. Тут поколениям неявно приписывается тот же смысл, что и вариантам. Это серьезное заблуждение, к сожалению, еще не до конца изжитое в существующих инструментальных средах (version control systems). Поколения и варианты имеют мало общего не только в происхождении и назначении, но и в технике их использования. Более того, эти понятия во многих отношениях диаметрально противоположны.
На практике вариант довольно легко отличить от поколения. Варианты равноправны между собой, а среди поколений имеется отношение порядка, отражающее хронологию их появления [Крюков, 1994]. Варианты сосуществуют в программном фонде, пребывая в нем, вообще говоря, произвольно долго. Поколения же отмирают по мере появления своих новых преемников.
Поколения обслуживают редактирование текста программы при устранении очевидных ошибок или при внесении безусловных улучшений. Если же имеются какие-либо сомнения в отношении качества вносимых в программу изменений, то текст лучше не редактировать: осознанно оформляйте вариант, а не рассчитывайте на возможную помощь со стороны аппарата поколений.
Варианты и поколения полярны с точки зрения их эмоционального восприятия. Расширение числа вариантов приносит разработчику радостное ощущение крепнущей мощи его программного продукта. Безудержный рост числа хранимых поколений характерный симптом неблагополучия в программном хозяйстве, источник постоянных огорчений и головной боли. Если поколения начали стремительно размножаться, то скорее всего это означает, что большинство из них представляет собой хлам, от которого разработчику в свое время не хватило решимости избавиться; теперь он захлебывается в окружающем его океане поколений и уже не в силах отличить нужный материал от бесполезного.
С точки зрения технологии варианты и поколения также находятся на противоположных полюсах.
Вариантные фрагменты, и в первую очередь сменные модули, оформленные с помощью специализированных средств, по существу однородны, все они обязаны иметь единый интерфейс с окружением вариантного гнезда. При использовании специализированной поддержки появление нового варианта влечет за собой только подключение к программному фонду нового сменного модуля, которое производится безболезненно, без редактирования текстов существующих программ. Средства поддержки регулярного вариантного программирования позволяют наглядно, рельефно выделить варьируемые фрагменты текста.
Напротив, родство между поколениями по определению чисто хронологическое. Здесь речь не идет, да и не может идти о какой-либо консервации интерфейсов, поскольку даже функциональное назначение редактируемого модуля с течением времени вполне может трансформироваться. Поколения неразрывно связаны с болезненной процедурой редактирования существующих программ: новое поколение модуля появляется именно в результате завершения сеанса редактирования его текста. Отличия между поколениями, т. е. несовпадающие участки их текстов, нередко трудно обнаружить (хотя в некоторых операционных средах имеются специальные средства выявления отличий в произвольных текстах, которые обычно применяют для выяснения отношений между поколениями).
Отмеченное несходство вариантов и поколений должно быть достаточно рельефно отражено в операционной среде. Проектировать системные средства поддержки вариантов и поколений лучше всего совместно: это позволит в полной мере позаботиться об их противопоставлении. Каждый раз при образовании соответствующего объекта программного фонда программист должен быть поставлен перед необходимостью сознательного выбора между спецификацией его как варианта или же как поколения. Путаница, возникающая из-за смешения этих понятий, настолько частое явление, что усилия по ее предупреждению непременно многократно окупятся.
* * *
Заканчивая разговор о многовариантности, вновь вернемся к сфере применения рассмотренных построений. Безусловно, главный и наиболее заинтересованный заказчик технологии многовариантности и средств ее системной поддержки вычислительный эксперимент. Однако и во многих других областях эти механизмы расширяемого программирования оказываются весьма удобными и полезными.
Характерный пример обеспечение мобильности крупных программ. В крупной программе, как правило, не удается избежать появления частей, которые необходимо заменять при переходе к другой операционной среде. Их оформление вызывает определенные трудности. Альтернативные немобильные части обычно не могут сосуществовать в одной выполняемой программе, их нельзя, скажем, собрать в одном операторе выбора. Средства поддержки многовариантности существенно облегчили бы жизнь разработчику подобных программ.
Вместе с тем, если даже ограничиться областью вычислительного эксперимента, можно заметить, что многовариантность не охватывает всех возникающих здесь проблем обеспечения расширяемости программного фонда. Многовариантность предполагает, что вновь появляющиеся модули заменяют существующие части в выполняемых программах, а иногда требуется, чтобы они дополняли их.
Например, при исследовании некоторой установки, состоящей из четырех узлов (каждому из которых соответствует моделирующий его поведение фрагмент алгоритма), может быть принято решение добавить еще один, пятый узел. Конечно же, модель пятого узла дополнит программу, а не заменит собой какую-либо ее часть.
И все-таки для вычислительного эксперимента характерны прежде всего именно заменяемые, вариантные компоненты. Но для других областей программирования не менее (а нередко и более) важны дополняемые, наборные конструкции, о которых речь пойдет в следующей главе.
|