Назад Оглавление Вперед
На головную страницу М.М.Горбунов-Посадов
 
РАСШИРЯЕМЫЕ ПРОГРАММЫ
 

 Г л а в а  2
МОДУЛЯРИЗАЦИЯ
 
2.4. Фундаментальные мотивы модуляризации
 

 

2.4. Фундаментальные мотивы модуляризации

      2.4.1. Функциональная самостоятельность модуля. Отдельные мотивы модуляризации могут, вообще говоря, конфликтовать друг с другом. Так, при сегментировании разработчик минимизирует число подкачек сегментов, а для этого он вынужден систематически превращать в модули отдельные этапы алгоритма, т. е. отталкиваться от хронологии выполнения программы. Однако при модуляризации по другим мотивам хронологические соображения обычно практически не учитываются. Например, проектируя набор методов, связывающих объекты класса с внешним миром, чаще всего вовсе не принимают во внимание ожидаемую последовательность выполнения этих методов. Если далее такую программу подвергнуть сегментированию, то границы сегментов могут самым причудливым образом разлучить семейство методов класса, что внесет в структуру программы некоторую дозу хаоса.
      К счастью, сегментирование является одним из немногих досадных исключений. Для подавляющего большинства мотивов модуляризации проведение границ модулей подчиняется лишь одному, общему для всех закону: модуль должен представлять собой функционально самостоятельную часть программы, воплощать квант программистского знания. Такая общность происхождения приводит к тому, что границы модулей, вычленяемых по различным мотивам, легко согласуются между собой, в результате чего формируется гармоничная структура программы.
      Желанием подчеркнуть функциональную самостоятельность модуля объясняется используемая в книге лексика: модуль «вычленяется», программа «расчленяется» на модули. В таких терминах слышится некоторая физиологичность, и это, конечно, немного мешает. Но зато ясно, что выделяются («вычленяются») функционально самостоятельные части, а не что попало, как, скажем, в случае термина «разбиение», где модуль ассоциируется, скорее, с осколком разбитой чашки. В англоязычной литературе популярен термин «decomposition», однако его русская транскрипция («декомпозиция»), к сожалению, не самоочевидна и пока не появилась ни в одном словаре.
      Как работает закон функциональной самостоятельности вычленяемого модуля при модуляризации по тем или иным из технических мотивов, разбиравшихся в предыдущем разделе?
      Удобство редактирования и сокращение времени трансляции достигаются за счет того, что изменения программы, касающиеся определенного понятия, локализуются, вообще говоря, в одном модуле, функциональное назначение которого охватывает данное понятие. Поэтому только один модуль приходится редактировать и перетранслировать при внесении таких изменений.
      Функционально самостоятельный модуль нагляден, поскольку легко сформулировать его назначение. В частности, краткая формулировка решаемой задачи может служить прекрасным комментарием-заголовком модуля. Функциональная самостоятельность сокращает число межмодульных связей, облегчая взаимодействие исполнителей, реализующих отдельные модули.
      Однако наиболее весомые доводы в пользу вычленения функционально самостоятельных частей выдвигаются при рассмотрении таких фундаментальных мотивов модуляризации, как систематизация и упрощение разработки, многократное использование и изменяемость программы.

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

      2.4.3. Многократное использование. В англоязычной литературе многократное использование обозначается термином reuse. К сожалению, пока не удалось подобрать столь же емкого и лаконичного русского термина, и потому до сих пор употребляется несколько более или менее подходящих оборотов: «множественное использование», «повторное использование», «переиспользование» и др.
      «Вычленение независимых единиц программистского знания» [Цейтин, 1990], пригодных к многократному использованию, — один важнейших мотивов модуляризации. В самом деле, если вы хотите, чтобы отдельные части вашего алгоритма можно было применять в дальнейшем при построении новых программ, то единственный реальный путь к этому — вычленить претендующие на многократное использование функциональные компоненты вашей программы и оформить их в виде модулей.
      Допустима и более сильная формулировка. Многократно используемые модули могут возникать не только как побочный продукт создания некоторой программы, но и как плод целенаправленной деятельности, не связанной с программированием конкретной задачи. В результате такой деятельности обычно формируется одна из простейших общих программных конфигураций — библиотека модулей.
      Задача модуляризации при построении библиотеки приобретает несколько иное звучание. При определении состава библиотеки требуется не расчленить некоторую конкретную программу, а сформировать общеупотребительный набор модулей (например, подпрограмм или классов), позволяющих решать характерные типовые подзадачи из некоторой предметной области.
      Интересно, что пополнение библиотеки — пример редко встречающейся в программировании ситуации, когда без специальных усилий со стороны разработчика развитие программного фонда идет безболезненно (см. разд. 1.2, 2.2.3), не затрагивая написанных ранее программ. Собиратель библиотеки «коллекционирует» модули, уделяя внимание их тестированию, единообразному описанию возможностей и т. д., но, как правило, совершенно не беспокоясь о сохранении работоспособности прежнего состава библиотеки при поступлении новых модулей. Причины для беспокойства действительно нет, поскольку вновь поступающие модули обычно настолько слабо связаны с остальным содержащимся в библиотеке программным материалом, что их появление не может как-либо повредить соседям по библиотеке.
      Многократно используемые и в том числе библиотечные модули часто играют только подчиненную роль: разработчик новой программы самостоятельно пишет некоторую основную, ведущую часть, которая время от времени обращается к таким модулям для решения частных подзадач. Однако не менее полезными могут оказаться и многократно используемые ведущие части, т. е. каркасы программ. Здесь при создании своей конкретной программы разработчик заново пишет лишь некоторые из вложенных модулей, заполняющих гнезда готового каркаса (рис. 2.1).


 
Рис. 2.1.  Место многократно используемых модулей в программе.
а — многократно используемые модули в роли подчиненных,
б — многократно используемый каркас;
 
, — многократно используемые модули,
— модули, написанные для данной конкретной программы

      Многократно используемые каркасы (шаблоны) широко применяются и при подготовке текстов на естественном языке. Шаблон представляет собой повторяющуюся часть делового письма или типового документа. В гнезда шаблона вставляются, скажем, реквизиты адресата или компоненты основного содержания документа.
      Апофеозом многократного использования считается сборочное программирование. Имеющиеся программистские знания, представленные в виде совокупности модулей, хочется собрать в общедоступных хранилищах, откуда строящиеся программы черпали бы готовые блоки. В самом деле, практически каждый создаваемый сегодня фрагмент алгоритма уже сотни и тысячи раз реализовывался кем-либо в прошлом, и чрезвычайно досадно вновь и вновь повторять этот давно пройденный предшественниками путь. Основные усилия по созданию программы хотелось бы направлять не на непосредственное кодирование, а на подбор, настройку и организацию совместного существования совокупности позаимствованных (возможно, из различных источников) многократно используемых модулей. Многократно используемых подчиненных модулей и каркаса для сборочного программирования уже недостаточно, тут требуются более изысканные конструкции, заполняющие срединные области программы и потому более тесно переплетающиеся между собой.
      Не прекращается поиск новых форм многократно используемого модуля. На ситуацию здесь заметно повлияло массовое признание объектно-ориентированного подхода. Библиотеки подпрограмм постепенно вытесняются библиотеками классов. Форму библиотек классов все чаще принимают и многократно используемые каркасы [Фаяд, 1997].
      Нередко конкретная форма многократно используемого модуля проектируется для решения какой-либо специальной задачи. Так, чтобы пользователь мог работать с составными документами, включающими в себя одновременно, например, текст и электронные таблицы, фирма Микрософт подчинила свои программы обработки текста и электронных таблиц соглашениям OLE (Object Linking and Embedding — связывание и внедрение объектов). Тем самым эти программы превратились в многократно используемые модули, в произвольных комбинациях сочетающиеся между собой и с другими подобными программами обработки документов. Далее соглашения OLE развивались, породив новые представления (COM, ActiveX и др. [Чеппел, 1997; Роджерсон, 1997]) о способах взаимодействия подобных многократно используемых компонентов и, в частности, об их применении в Интернет.
      Как уже упоминалось в разд. 2.3.7, имеется опыт многократного использования модулей распределенного выполнения, написанных в рамках соглашений CORBA [КОРБА, 1999]. Наконец, относительно недавно были предложены JavaBeans [Гарольд, 1999] — модули многократного использования для языка Java, ориентированные в первую очередь на построение Интернет-приложений. Создатели JavaBeans особое внимание уделили организации настройки и встраивания модуля в создаваемую программу: эти весьма непростые действия теперь могут выполняться быстро, легко и надежно, в стиле визуального программирования.
      Все перечисленные построения имеют впечатляющие результаты на ниве многократного использования. Однако ни одно из них не ставило в явном виде цели создания универсальной формы для представления независимой единицы программистского знания. И, к сожалению, ни в одном из построений эта цель не была достигнута.
      Работы по многократной используемости пока могут похвастать успехами лишь в ряде конкретных направлений, в общей же постановке задача далеко не решена. Так, с одной стороны, трудно теперь встретить математика-программиста, которому довелось хотя бы однажды вручную кодировать алгоритм вычисления значения синуса, поскольку в любой общематематической библиотеке всегда найдется соответствующая подпрограмма. Но, с другой стороны, еще реже встречаются системные программисты, которым не требовалось бы периодически самостоятельно кодировать какой-либо из банальнейших алгоритмов сортировки, поскольку в операционной среде для таких алгоритмов часто отсутствует удобное, рассчитанное на широкую область применения библиотечное представление.
      По-видимому, именно отсутствие удобных форм представления сдерживает формирование широкомасштабных хранилищ программистских знаний. Хочется верить, что трудности эти удастся преодолеть и будущий программист, получив доступ к таким хранилищам, сможет радикально повысить производительность своего труда. Определенные надежды связаны здесь, в частности, с достижениями в области исследования расширяемых программ.

      2.4.4. Изменяемость. Как уже не раз отмечалось, любая программа, активно функционирующая на протяжении достаточно длительного времени, неизбежно будет изменяться. Причин для изменений существует великое множество: обнаружение и устранение неточностей, повышение эффективности и улучшение других потребительских качеств, отслеживание изменений внешней среды, в частности реакция на появление новых аппаратных и программных средств, попытки применить программу для решения смежной или более общей задачи и т. п.
      Программистское сообщество единодушно признает изменяемость важнейшим качеством программного обеспечения. Примечательно, что самый распространенный англоязычный программистский термин «software» замешан на слове soft — мягкий, податливый, пластичный. Первоначально термин «software» всего лишь подчеркивал противопоставление грубо-материальной, консервативной, «жесткой» аппаратной части компьютера — hardware — и его неосязаемой, «мягкой» части — программ, постоянно сменяющих друг друга в оперативной памяти. Но невероятная частота употребления этого термина объясняется, похоже, не столько общеизвестным первоначальным смыслом, сколько другим его допустимым толкованием, ассоциирующимся со словом soft: software — программы, которые можно изменять.
      Изменять, расширять, действительно, можно любую программу. Однако, если не предпринять специальных усилий, внесение изменений нередко оказывается чрезвычайно трудоемким и болезненным процессом, чреватым длительной утратой работоспособности отлаженного ранее материала. Что же может сделать разработчик программы для того, чтобы последующие ее изменения выполнялись относительно легко? Главный рецепт облегчения внесения изменений — тщательно продуманная модульная структура программы.
      Первопричиной любого изменения является пересмотр какого-либо из решений, выбранных при формировании прежней версии программы. Поэтому реализацию каждого решения, принимаемого в ходе разработки, Парнас [Парнас, 1972] предлагает оформлять в виде модуля. Тем самым последствия решения скрываются от остальных частей программы. Если данное решение когда-либо потребуется пересмотреть, то изменяемая область программы будет четко очерчена: изменения теперь не должны выйти за пределы оформленного таким образом модуля.
      Можно несколько перефразировать Парнаса, усмотрев первопричину изменений программы не в пересмотре реализационных решений, а в изменении значений факторов, влияющих на программу. Среди этих факторов целесообразно выделить базисные — первичные, элементарные, несводимые к комбинации других, но в совокупности охватывающие всю сферу деятельности программы. Каждому базисному фактору сопоставляется базисный модуль, функциональное назначение которого заключается в отражении значения данного фактора. Тогда причина отдельного изменения программы «раскладывается» на изменения значений базисных факторов, затрагивая обычно лишь относительно небольшое их число, и потому последствия изменения строго локализуются в ограниченном подмножестве базисных модулей.
      К локализации в модуле будущих изменений можно подойти и иначе, не акцентируя внимания на их причинах. Тут на первый план выдвигается ортогональность модуля, означающая, что он реализует самостоятельную, независимую функцию. Ортогональность подразумевает однозначное, исключающее дублирование распределение функций между модулем и остальной программой: для любого элементарного фрагмента программы должно существовать четкое и убедительное объяснение того, почему он оказался в ортогональном модуле или же вне его. Изменение реализации ортогонального модуля (при сохранении его интерфейса), вообще говоря, никак не отражается на остальной программе.
      Обеспечение повальной базисности и ортогональности для всех и каждого из составляющих программу модулей обычно оказывается недостижимым идеалом. Однако практически любые шаги, предпринимаемые разработчиком в направлении улучшения этих характеристик, не пропадают даром, поскольку они способствуют не только изменяемости программы, но и таким ее полезным качествам, как наглядность, надежность и др.
      Модуляризацию «по Парнасу» не всегда удается последовательно провести в жизнь. Действительно, опытный программист ни про одно из своих программных решений не может с полной уверенностью сказать, что оно окончательно. И все же часто многие из них не получают надлежащего модульного оформления. В глубине души разработчик надеется, что число пересматриваемых решений будет невелико и программа пойдет в виде, близком к первоначально задуманному. Поэтому определенные усилия, необходимые для оформления в виде модуля каждого из решений, могут показаться чрезмерными.
      Иное положение складывается в случае, когда из исходной постановки решаемой задачи следует, что программа будет подвергаться многочисленным регулярным изменениям. Тут необходимость надлежащего оформления изменяемых решений сомнений не вызывает, поскольку затраты на организацию соответствующей модульной конфигурации безусловно окупятся. В частности, в подобных задачах могут оказаться полезными специализированные средства поддержки оформления варианта, представленные в разд. 1.8.
      Существует тесная связь между многократной используемостью и изменяемостью. Чтобы ее почувствовать, достаточно заметить, что в новой, измененной версии программы части, не затронутые модификациями, используются повторно. Так что можно воспринимать указанные два мотива как определяющие движение к одному и тому же результату (функциональному расчленению программы), но с противоположных сторон: изменяемость последовательно вычленяет варьируемые части, а многократная используемость — консервативные, переходящие от программы к программе.
      Более того, в повседневной практике вообще, по-видимому, не стоит противопоставлять указанные мотивы. Выделяйте каждую функционально самостоятельную часть, и пусть время рассудит, станет ли она отброшенной (измененной) за непригодностью однодневкой или, перекочевав в сотни и тысячи программ, будет признана нетленным творением, пополнившим сокровищницу программистских знаний. Главное — что и первую, и вторую роль выделенная часть сумеет достойно сыграть, только будучи оформленной в виде модуля.

Далее

Рейтинг@Mail.ru