Часть III Реализация

14 Языки программирования: С или не С?

Границы моего языка — границы моего мира.

Логико-философский трактат

(Tractatus Logico-Philosophicus 5.6, 1918)

—Людвиг Виттгенштейн (Ludwig Wittgenstein).

14.1. Многообразие языков в Unix

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

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

Понятие "язык сценариев" (scripting language), вероятно, происходит от термина "сценарий" (script), который применялся к заранее подготовленному вводу для программы, обычно работающей в интерактивном режиме, в частности, sh или ed — гораздо более уместный термин, чем "runcom", унаследованный Unix от предка, операционной системы CTSS. Слово "script" появляется в руководстве для системы V7 (1979). Я не помню, кто именно придумал это название.

Дуг Макилрой.

В действительности термин "язык сценариев" несколько неудобен. Многие из основных языков, обычно описываемых как языки сценариев (Perl, Tcl, Python и другие), уже переросли первоначальные задачи создания сценариев и в настоящее время являются самостоятельными универсальными языками программирования значительной мощности. Данный термин склонен срывать сильное сходство в стиле с другими языками, которые обычно не причисляются к этой группе, особенно с Lisp и Java. Единственным аргументом, оправдывающим нынешнее использование данного понятия, является тот факт, что лучшего термина еще никто не придумал.

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

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

В данной главе рассматривается язык С и его наиболее важные альтернативы, обсуждаются их сильные и слабые стороны, а также виды задач, которым они наилучшим образом соответствуют. Ниже описываются языки С, С++, shell, Perl, Tcl, Python, Java и Emacs Lisp. Каждый обзорный раздел включает в себя учебные примеры приложений, написанных с использованием данных языков, а также ссылки на другие примеры и учебные материалы. Высококачественные реализации всех данных языков доступны в Internet в виде открытого исходного кода.

Внимание: выбор языка прикладного программирования является одним из основных идеологических вопросов в сообществе Internet/Unix. Люди сильно привязываются к данным средствам и иногда защищают их вопреки здравому смыслу. Если глава достигнет своей цели, то вполне может оскорбить фанатичных приверженцев всех языков, однако все остальные разработчики почерпнут из нее немало полезного.

14.2. Доводы против С

С — естественный язык операционной системы Unix. С начала 1980-х годов он стал доминировать в системном программировании почти повсеместно в компьютерной индустрии. За пределами сокращающейся ниши Fortran в научных и инженерных вычислениях, а также исключая невидимую массу финансовых приложений на языке COBOL в банках и страховых компаниях, С и его потомок С++ уже более десяти лет доминируют в прикладном программировании.

Поэтому утверждение о том, что С и С++ почти всегда являются неподходящим связующим материалом для начала разработки новых приложений, может показаться неверным. Тем не менее, оно справедливо. С и С++ оптимизируют машинную эффективность ценой увеличения времени реализации и (особенно) отладки. Несмотря на то, что писать на С или С++ системные программы и чувствительные ко времени выполнения ядра приложений все еще имеет смысл, мир значительно изменился со времен возвышения этих языков в 1980-х годах. Сегодня процессоры в тысячи раз быстрее, модули памяти в тысячи раз больше, а диски в десять тысяч раз больше, причем примерно по тем же ценам[118].

Падение цен фундаментально изменило экономику программирования. В большинстве обстоятельств уже не имеет смысла так экономить аппаратные ресурсы, как это позволяет С. Напротив, экономически оптимальный выбор состоит в минимизации времени отладки и максимизации возможности долгосрочного сопровождения кода. Следовательно, большинство видов реализации (включая создание прототипов приложений) лучше обслуживаются более новым поколением интерпретируемых языков и языков сценариев. Эта трансформация точно соответствует тем условиям, которые на предыдущем витке исторической спирали привели к восхождению C/C++ и снизили важность программирования на ассемблере.

Центральной проблемой С и С++ является то, что они требуют от программистов самостоятельно осуществлять управление памятью — объявлять переменные, явно управлять связными списками, определять размеры буферов, обнаруживать или предотвращать переполнение буферов, а также распределять и высвобождать динамическую память. Некоторые из указанных задач могут быть автоматизированы путем искусственных действий, таких как дополнение С программой сборки мусора, например, реализация Boehm-Weiser, однако конструкция С такова, что подобное решение не может быть совершенным.

Управление памятью в С — серьезный источник трудностей и ошибок. По оценкам одного исследования (цитата из [9]), 30 или 40% времени разработки отводится на управление памятью в программах, которые манипулируют сложными структурами данных. В это число даже не включаются затраты на отладку. Несмотря на отсутствие точных данных, многие опытные программисты уверены, что ошибки управления памятью являются единственным крупнейшим источником постоянных ошибок в реальном коде[119]. Переполнение буфера является обычной причиной аварий и брешей в системе безопасности. Управление динамической памятью особенно чревато порождением коварных и трудно отслеживаемых ошибок, таких как утечки памяти и проблемы недействительного указателя.

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

В недавней статье [63] собран впечатляющий массив статистических данных в пользу заявления, которое опытные программисты сочтут весьма правдоподобным: продуктивность программистов при работе с языками сценариев почти в два раза больше продуктивности при работе с С или С++. Данное утверждение хорошо согласуется с приведенной выше оценкой затрат времени (30-40%), а ведь еще следует учесть издержки отладки. Потери производительности при использовании какого-либо языка сценариев очень часто незначительны для реальных программ, поскольку такие программы склонны ограничиваться ожиданием I/O-событий, сетевой задержки и заполнением кэша, а не эффективностью, с которой они используют сам процессор.

В действительности, сообщество Unix медленно приближается к данной точке зрения, особенно с 1990 года, это видно по возрастающей популярности Perl и других языков сценариев. Однако развитие практики еще (к середине 2003 года) не привело к крупномасштабным переменам. Многие Unix-программисты до сих пор осмысливают урок, преподаваемый языками Perl и Python.

Та же тенденция, хотя и выраженная не так ярко, наблюдается за пределами мира Unix, например, в продолжающемся переходе от С++ к Visual BASIC, который заметно проявляется в разработке приложений для Microsoft Windows и NT, а также в движении к Java в мире мэйнфреймов.

Аргументы против С и С++ в равной степени применимы к другим традиционным компилируемым языкам, таким как Pascal, Algol, PL/I, FORTRAN и компилируемые диалекты BASIC. Несмотря на отдельные "героические усилия", такие как Ada, отличия между традиционными языками остаются внешними при сопоставлении их основных конструктивных решений, оставляющих управление памятью программисту. В Unix большинство из когда-либо созданных языков доступны в виде высококачественных реализаций с открытым исходным кодом. Несмотря на это, в широком использовании в Unix или Windows не осталось других традиционных языков. Разработчики отказались от них в пользу С или С++. Соответственно, в данной главе они не рассматриваются.

14.3. Интерпретируемые языки и смешанные стратегии

Языки с автоматическим управлением памятью осуществляют его с помощью диспетчера памяти, встроенного в их динамически исполняемые модули. Как правило, среды выполнения в таких языках разделены на программную часть (собственно выполняющийся сценарий) и часть интерпретатора с управляемой им динамической памятью. В Unix-системах (а также в других современных операционных системах) память интерпретатора может совместно использоваться несколькими программными частями, что сокращает фактические издержки для каждой из них.

Использование сценариев — нисколько не новая идея в мире Unix. В 1970-х годах, в эпоху гораздо меньших машин, Unix shell (интерпретатор для команд, вводимых в Unix-консоль) был спроектирован как полностью интерпретируемый язык программирования. Даже тогда было распространено написание программ полностью на shell или использование shell для написания связующей логики, объединяющей встроенные утилиты и нестандартные программы на С в целостные системы, эффективность которых была больше, чем сумма эффективности составляющих частей. В классических вводных книгах по Unix-среде (таких как "The Unix Programming Environment" [39]) подробно рассматривается данная тактика, и это вполне обосновано: она была одной из важнейших нововведений операционной системы Unix.

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

14.4. Сравнение языков программирования

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

14.4.1. С

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

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

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

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

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

Еще одна весомая причина, по которой следует изучать С (даже если требования к языку программирования разработчика удовлетворяются языком более высокого уровня), состоит в том, что это способствует развитию мышления на уровне аппаратной архитектуры. Лучшим справочником и учебным пособием по С для программистов является книга "The С Programming Language" [42].

Перенос C-кода из одного вида операционных систем Unix в другой почти всегда возможен и обычно прост, но в некоторых областях, таких как сигналы и контроль над процессами, могут возникать определенные сложности. Некоторые из этих проблем рассматриваются в главе 17. Различие C-привязок в других операционных системах, несомненно, может вызвать проблемы переносимости С, хотя операционная система Windows NT, по крайней мере, теоретически должна поддерживать ANSI/POSIX-совместимый стандарт С API.

Высококачественные компиляторы С доступны в Internet в виде программ с открытым кодом; наиболее известным и широко применяемым является компилятор С Фонда Свободного программного обеспечения (Free Software Foundation — FSF), входящий в коллекцию компиляторов GNU (GNU Compiler Collection — GCC). Данный компилятор стал базовым для всех Unix-систем с открытым кодом, а также для некоторых систем с закрытым кодом. Версии GCC доступны даже для операционных систем семейства Microsoft. Исходные коды GCC доступны на сайте FSF .

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

14.4.1.1. Учебный пример: fetchmail

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

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

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

Наконец, fetchmail нуждается в возможности анализировать довольно сложный синтаксис спецификации для управляющей информации по каждому почтовому серверу. В Unix такая проблема классически решается с помощью генераторов C-кода, вырабатывающих исходный код для лексического и грамматического анализатора из декларативных спецификаций. Существование yacc и lex было доводом в пользу С.

fetchmail можно было бы на приемлемом уровне реализовать на Python, хотя и со значительной потерей производительности. Размер и сложность структуры данных fetchmail полностью исключили бы shell и Tcl и строго указывали бы на недопустимость использования Perl. При этом предметная область программы находится за пределами естественных возможностей Emacs Lisp. Реализация на языке Java не была бы лишена смысла, но объектно-ориентированный стиль и уборка мусора дали бы небольшой выигрыш в специфических проблемах fetchmail по сравнению с теми, которые уже решались с помощью С. С++ так же не смог бы значительно упростить сравнительно простую внутреннюю логику fetchmail.

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

Интерактивный конфигуратор fetchmail, лишенный традиционной проблемы С, написан на Python; соответствующий учебный пример рассматривается далее.

14.4.2. С++

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

Этого не случилось. Отчасти причинами могут быть проблемы в самом С++; требование обратной совместимости с С привело ко многим компромиссам в конструкции. Кроме всего прочего, данное требование помешало перевести С++ на полностью автоматическое управление динамической памятью и решить самую серьезную проблему С. Позже "гонка вооружений" между различными разработчиками компиляторов, которую не сдерживала слабая и преждевременная попытка стандартизации, привела к тому, что С++ стал "витиеватым" и чрезвычайно сложным языком.

Причиной послужило также и то, что самой ОО-технологии не удалось оправдать ожидания. Проблема рассматривалась в главе 4 — ОО-методы склонны приводить к созданию больших связующих уровней и проблемам сопровождения. На сегодняшний день, изучение архивов открытого исходного кода (где выбор языка скорее отражает суждения разработчика, чем корпоративные указания) показывает, что использование С++ до сих пор в основном характерно для GUI, мультимедийного инструментария и игр (основных областей успеха ОО-конструкций), и гораздо реже встречается в других областях.

Возможно, что реализация ОО-технологии на С++ особенно склонна к созданию проблем. Существуют данные, которые говорят о том, что расходы на жизненный цикл программ на С++ больше, чем у эквивалентов, написанных на С, FORTRAN или Ada. Является ли данная проблема проблемой ОО, проблемой С++, или это комплексная проблема — пока не ясно, хотя есть смысл подозревать последнее [34].

В последние годы С++ включил в себя некоторые важные не-ОО-идеи. В языке существуют исключения, подобные исключениям в Lisp, т.е. можно добавлять объект или значение в стек вызовов до тех пор, пока его не перехватит обработчик. STL (Standard Template Library, стандартная библиотека шаблонов) обеспечивает типовое программирование, т.е. возможность писать код алгоритмов, независимых от сигнатур их данных, и компилировать их для выполнения необходимых функций во время выполнения. (Это необходимо только для языков, выполняющих статический контроль типов в процессе компиляции; более динамические языки просто передают ссылки на переменные без типа и поддерживают идентификацию типов во время выполнения.)

Эффективный компилируемый язык, совместимость снизу вверх с языком С, объектно-ориентированная платформа, связующий материал для самых современных методик, таких как STL и типовое программирование, — С++ претендует на роль универсального средства, но ценой этого является сложность, гораздо большая, чем та, с которой может справиться мозг отдельного программиста. Как было отмечено в главе 4, главный конструктор языка признал, что он и не ждет, что какой-либо один программист сможет постичь язык полностью. Unix-хакеры не слишком хорошо отреагировали на сложность С++. Подтверждением этого служит одно из анонимных, но известных определений: "С++ — это спрут, полученный путем пришивания лишних ног собаке".

Однако фундаментальная проблема заключается в том, что по существу С++ является просто еще одним традиционным языком. Он лучше сдерживает проблему управления памятью, чем до создания стандартной библиотеки шаблонов (STL), и значительно лучше, чем С. Однако это довольно сомнительное решение проблемы, и оно не будет надежно работать, если в коде не используются объекты и только объекты. Возможности ОО для многих типов приложений незначительны и просто добавляют сложности в С, не достигая весомых преимуществ. Доступны компиляторы С++ с открытым исходным кодом; если бы С++ определенно превосходил С, то в настоящее время он бы доминировал.

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

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

Классическим справочником по С++ является книга Страуструпа (Stroustrup) "The С++ Programming Language" [82]. Отличными учебным пособием по С++ и основным ОО-методам для начинающих является книга "С++: A Dialog" [36]. Статья "С++ Annotations" [6] представляет собой сжатый вводный курс по С++ для опытных программистов на С.

Компилятор С++ входит в состав коллекции компиляторов GNU. Поэтому, язык доступен как для Unix, так и для операционных систем Microsoft; в данном случае также применимы комментарии, касающиеся С. Доступна обширная коллекция вспомогательных библиотек с открытым исходным кодом . Однако переносимость языка ограничена тем, что (в середине 2003 года) в действующих реализациях С++ реализованы значительно отличающиеся подмножества проектного ISO-стандарта, который в настоящий момент находится в процессе подготовки[120].

14.4.2.1. С++ учебный пример: инструментарий Qt

Интерфейсный инструментарий Qt представляет собой замечательный пример успеха С++ в современном мире программ с открытым исходным кодом. Инструментарий предоставляет комплект элементов управления, а также API для написания графических пользовательских интерфейсов для системы X Window, который был сознательно (и довольно эффективно) разработан таким образом, чтобы имитировать вид и восприятие интерфейсов Motif, MacOS Platinum или Microsoft Windows. Фактически Qt предоставляет гораздо больше, чем просто GUI-службы, — он предоставляет переносимый прикладной уровень с классами для XML, доступа к файлам, сокетов, параллельных процессов, таймеров, обработки даты и времени, доступа к базам данных, различных абстрактных типов данных и Unicode.

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

Реализация Qt на С++ демонстрирует преимущества ОО-языка программирования для инкапсуляции компонентов пользовательского интерфейса. В языке, поддерживающем объекты, видимая иерархия элементов управления интерфейса может быть четко выражена с помощью иерархии экземпляров классов в коде программы. Несмотря на то, что то же самое можно сымитировать в С с помощью явного преобразования через созданную вручную таблицу методов, код, написанный на С++, будет гораздо чище. Полезно сравнить его с известным своей причудливостью С API в Motif.

Исходный код и справочная документация по Qt доступна на сайте Trolltech .

14.4.3. Shell

Первым (и долгое время единственным) переносимым интерпретируемым языком был 'Bourne shell' (sh) 7 версии Unix. В настоящий момент родоначальный Bourne shell в значительной степени вытеснили разновидности совместимого снизу вверх Korn Shell (ksh). Единственным наиболее важным из них является Bourne Again Shell (bash).

Существуют и согласованно используются несколько других видов shell, но как языки программирования они не столь значительны. Наиболее известным из них, вероятно, является С shell (csh), печально известный своей непригодностью для написания сценариев[121].

Элементарные shell-программы чрезвычайно просты и естественны в написании. Язык shell послужил началом Unix-традиции быстрого создания прототипов на интерпретируемых языках.

Я написал самую первую версию netnews в виде shell-сценария в 150 строк. Она имела несколько групп новостей и перекрестную рассылку. Группы новостей представляли собой каталоги, а перекрестная рассылка была реализована как множественные ссылки на статью. Программа была слишком медленной для реального использования, но ее гибкость позволяла бесконечно экспериментировать с конструкцией протокола.

Стивен М. Белловин.

Однако по мере увеличения своего размера, shell-программы часто становятся скорее узкоспециальными. Некоторые моменты синтаксиса shell (особенно правила использования кавычек и синтаксис операторов) могут сбить с толку. Данные недостатки, как правило, обусловлены компромиссами в той части конструкции shell, которая связана с языком программирования. Эти компромиссы были внесены, для того чтобы сохранить эффективность shell как интерактивного интерпретатора командной строки.

Программы описывают как "shell-программы" даже если они не написаны исключительно на shell, но в них интенсивно используются С-фильтры, такие как sort(1), и стандартные мини-языки обработки текста, такие как sed(1) или awk(1). Однако данный вид программирования в течение нескольких лет идет на убыль. В наши дни подобная сложная связующая логика обычно пишется на Perl или Python, a shell резервируется для простейших упаковщиков (для которых данные языки были бы чрезмерно сложными) и сценариев инициализации системы (которая не предполагает, что они доступны).

Базовое shell-программирование достаточно описано в любой вводной книге по Unix. "The Unix Programming Environment" [39] остается одним из лучших источников для программистов среднего и высокого класса. Реализации или клоны Korn shell представлены в каждой Unix-системе.

Сложные shell-сценарии часто имеют проблемы переносимости не столько из-за самой оболочки, а ввиду сделанных в них предположений о доступности той или иной программы в качестве компонента. Несмотря на то, что в отдельных случаях клоны bash и ksh доступны в операционных системах, отличных от Unix, shell-программы (практически) вообще невозможно перенести за пределы Unix.

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

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

14.4.3.1. Учебный пример:
xmlto

xmlto — управляющий сценарий, который вызывает все команды, необходимые для представления какого-либо XML-DocBook-документа в виде HTML, PostScript, простого текста или любого другого доступного формата (более подробно DocBook рассматривается в главе 18). Сценарий

xmlto
написан в
bash
.

Детали вызова XSLT-процессора обрабатываются в xmlto с помощью соответствующей таблицы стилей, затем результаты передаются постпроцессору. Для HTML и XHTML вся обработка сводится к трансформации. Для получения простого текста XML также трансформируется в HTML, а затем передается постпроцессору — lynx(1) в режиме

-dump
, который преобразовывает HTML в простой текст. Для получения PostScript XML трансформируется в XML FO (Formatting Objects — объекты форматирования), которые постпроцессор затем преобразовывает в TEX-макрос, DVI-формат посредством tex(1), а затем, наконец, в PostScript с помощью широко известного инструмента dvi2ps(1).

xmlto
состоит из одного интерфейсного shell-сценария. Он вызывает одну из нескольких подключаемых подпрограмм, каждая из которых названа по имени целевого формата. Каждая подключаемая подпрограмма представляет собой shell-сценарий. В зависимости от того как она вызвана, подключаемая подпрограмма либо предоставляет клиентской части таблицу стилей, либо вызывает соответствующий постпроцессор (или постпроцессоры) с различными заранее заданными аргументами.

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

Программа

xmlto
является хорошим примером shell-приложения среднего размера. Использование С или С++ не имело бы смысла, поскольку они неудобны для написания сценариев. Для такой программы можно было бы использовать любой из языков сценариев, описанных в данной главе; однако программа состоит только из простых команд управления без внутренних структур данных или сложной логики, поэтому достаточно использовать shell, так как это дает значительное преимущество — повсеместное распространение на целевых системах.

Теоретически данный сценарий можно было бы выполнить на любой системе, поддерживающей

bash
. Единственным ограничением является необходимость присутствия в системе одного из XSLT-процессоров, а также всех постпроцессоров. На практике маловероятна работа данного сценария во всех системах, кроме современных Unix-систем с открытым исходным кодом.

14.4.3.2. Учебный пример: Sorcery Linux

Sorcerer GNU/Linux — дистрибутив Linux, устанавливаемый как небольшая, загрузочная опорная система, мощность которой достаточна для работы bash(1) и нескольких утилит загрузки данных. Имея этот код, можно вызвать Sorcery, систему пакетов Sorcerer.

Sorcery обеспечивает установку, удаление и проверку целостности пакетов программ. Когда пользователь "ввел заклинания", Sorcery загружает исходный код, компилирует, инсталлирует его, а затем сохраняет список установленных файлов (наряду с протоколом компиляции и контрольными суммами для всех файлов). Установленные пакеты можно "отклонить" или удалить. Списки пакетов и проверка целостности также доступны. Более подробно данный дистрибутив описан на сайте проекта .

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

14.4.4. Perl

Perl — shell на стероидах. Данный язык был специально предназначен для замены awk(1) и расширен, чтобы заменить shell в качестве уровня, связывающего сценарии, написанные на нескольких языках. Первая версия Perl вышла в 1987 году.

Самым сильным качеством Perl являются его чрезвычайно мощные встроенные средства для шаблонной обработки текстовых, строковых форматов данных, и в этой области Perl является непревзойденным. Данный язык также включает в себя гораздо более прочные структуры данных, чем shell, включая динамические массивы элементов различных типов, а также тип "hash" или "dictionary", который поддерживает удобный и быстрый поиск пар имя-значение.

Дополнительно Perl включает в себя довольно полную и хорошо продуманную привязку практически всего API-интерфейса Unix, что радикально уменьшает потребность в С и делает Perl пригодным для таких задач, как простые TCP/IP-клиенты и даже серверы. Другим серьезным преимуществом Perl является то, что вокруг него сформировалось крупное и жизнеспособное сообщество открытого исходного кода. Центром данного сообщества в сети является ресурс Comprehensive Perl Archive Network (обширны архив Perl-программ) . Преданные Perl-хакеры создали сотни свободно используемых Perl-модулей для множества различных задач программирования. В число данных модулей входит структурный обход дерева каталогов, X-инструментарий для создания GUI-интерфейсов, превосходные встроенные средства для поддержки HTTP-роботов и CGI-программирования.

Главным недостатком Perl является то, что его части неисправимо уродливы, сложны и для их использования требуется особая осторожность и стереотипные способы (соглашения о передаче аргументов функциям являются хорошим примером всех трех проблем). Начать работать с Perl сложнее, чем с shell. Хотя мелкие программы на Perl могут быть чрезвычайно мощными, необходима четкая дисциплина для поддержки модульности и сохранения конструкции под контролем по мере роста размера программы. Поскольку некоторые ограничивающие конструкторские решения в ранней истории Perl невозможно было переделать, многие из более сложных функций имеют довольно хрупкую конструкцию.

Наиболее полным справочником по Perl является книга "Programming Perl" [88]. В ней содержится почти вся необходимая информация по данному языку, однако книга печально известна своей плохой организацией; чтобы найти необходимые сведения, читателю приходится перерабатывать множество материала. Более упорядоченным источником является книга "Learning Perl" [76].

Язык Perl универсален в Unix-системах. Perl-сценарии с одинаковым основным номером версии часто легко переносимы между Unix-системами (при условии, что в них не используются модули расширения). Реализации Perl доступны (и даже хорошо документированы) для операционных систем семейства Microsoft, а также MacOS. Perl/Tk обеспечивает кроссплатформенную GUI-совместимость.

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

14.4.4.1. Небольшой учебный пример по Perl:
blq

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

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

Сценарий

blq
, как Perl-код, является исключительно четким и организованным, рекомендуется изучить его как пример хорошего стиля (хорошими примерами также являются другие Perl-инструменты, упомянутые на сайте проекта
blq
). Однако некоторые части кода покажутся нечитабельными, если не знать весьма специфических идиом Perl — в качестве примера можно привести первую строку кода
$0=~s!.*/!!;
. Хотя все языки характеризуются некоторой непрозрачностью, непрозрачность Perl — худшая из всех.

Для написания небольших сценариев данного типа хорошо подходят языки Tcl и Python, однако оба они испытывают недостаток удобных функций Perl для поиска регулярных выражений, которые интенсивно используются в

blq
. Реализации рассматриваемой программы на Tcl или Python были бы приемлемыми, но, вероятно, менее компактными и выразительными. Написание подобной программы на Emacs Lisp отняло бы даже меньше времени, и программа была бы более компактной, но, вероятно, чрезвычайно медленной в работе.

14.4.4.2. Большой учебный пример по Perl:
keeper

Программа

keeper
— инструмент для создания картотеки поступающих пакетов и поддержки FTP- и index-файлов WWW для крупных архивов программного обеспечения Linux на сайте проекта ibiblio. Исходный код и документацию можно найти в подкаталоге поисковых инструментов архива ibiblio .

Программа

keeper
— хороший пример интерактивного Perl-приложения более чем среднего размера. Интерфейс командной строки является строчным и создан по образцу специализированной оболочки или редактора каталогов; необходимо отметить встроенные справочные возможности. Рабочие части интенсивно используют обработку файлов и каталогов, поиск шаблонов и шаблонное редактирование. Следует отметить легкость, с которой keeper генерирует Web-страницы и email-уведомления из программных шаблонов. Кроме того, примечательно использование заготовленного Perl-модуля для автоматизации обхода различных функций в дереве каталогов.

Данное приложение, состоящее примерно из 3300 строк кода, вероятно, превышает рекомендуемый при создании одной Perl-программы предел размера и сложности. Тем не менее, большая часть

keeper
была написана в течение шести дней. Для написания такой программы на С, С++ или Java потребовалось бы шесть недель, а программа была бы крайне трудной в отладке или модификации. Для чистого Tcl данный путь слишком сложен. Версия на Python, вероятно, была бы структурно более чистой, читаемой и удобной в обслуживании, но также и более хаотичной (особенно в части поиска шаблонов). Задачи данной программы можно было бы выполнять с помощью какого-либо режима Emacs Lisp, но Emacs не подходит для использования на telnet-канале, который часто затормаживается из-за перегрузок сервера.

14.4.5. Tcl

Tcl (Tool Command Language — язык инструментальных команд) — небольшой языковой интерпретатор, предназначенный для связи с компилируемыми C-библиотеками и обеспечивающий управление C-кодом с помощью сценариев (extended scripts — расширенные сценарии). Первоначально он применялся для управления библиотеками для электронных симуляторов (SPICE-подобных приложений). Tcl также подходит для встроенных сценариев (embedded scripts), т.е. сценариев, которые вызываются из C-программ и возвращают им значения. Первая общедоступная версия Tcl вышла в 1990 году.

Некоторые средства, созданные на основе Tcl, широко используются за пределами Tcl-сообщества. Ниже описываются два наиболее важных из них.

• Инструментарий Tk, более податливый и легкий X-интерфейс, который упрощает быстрое создание кнопок, диалоговых окон, меню и полос прокрутки текста, а также собирает входные данные от них.

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

Инструментарий Tk настолько важен, что язык часто называется Tcl/Tk. Tk также часто используется с языками Perl или Python.

Главным преимуществом языка Tcl является то, что он чрезвычайно гибок и в своей основе прост. Его синтаксис необычен (основывается на позиционном анализаторе), но полностью последователен. В языке отсутствуют зарезервированные слова, а также синтаксическое различие между вызовом функции и "встроенным" синтаксисом. Таким образом, можно из Tcl эффективно переопределить собственно языковой интерпретатор (что делает рациональными проекты, подобные

Expect
).

Основным недостатком Tcl является то, что чистый язык имеет только слабые средства для управления пространством имен и модульностью, а два из них (

upvar
и
uplevel
) довольно опасны, если использовать их неосторожно. Также не существует структур данных, кроме ассоциативных списков. Поэтому Tcl расширяется очень слабо — трудно организовывать и отлаживать чистую Tcl-программу даже умеренного размера (более нескольких сотен строк), так как можно запутаться в собственном коде. На практике почти во всех Tcl-программах используется одно из нескольких ОО-расширений для языка.

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

Чистый Tcl предоставляет доступ только к сравнительно небольшой и широко используемой части Unix API (по существу, только обработка файлов, создание подпроцессов и сокетов). Действительно, Tcl интересен как демонстрация того, каким небольшим и вместе с тем полезным может быть язык сценариев. Tcl-расширения (подобно модулям Perl) обеспечивают более развитый набор возможностей, однако (как и в случае CPAN-модулей) нет гарантии, что они установлены везде.

Оригинальным справочником по Tcl является книга "Tcl and the Tk Toolkit" [58], но ее уже почти "затмила" книга "Practical Programming in Tcl and Tk" [89]. Брайан Керниган создал описание реального Tcl-проекта [38], в котором показаны преимущества и недостатки Tcl как инструмента быстрого создания прототипов и реальных программ. Его противопоставление Microsoft Visual BASIC особенно взвешенно и поучительно.

Сообщество Tcl не имеет одного центрального репозитария, поддерживаемого основной группой, аналогичной группам поклонников языка Perl или Python, но несколько выдающихся Web-сайтов ссылаются друг на друга и описывают большую часть разработки Tcl-инструментов и расширений. Прежде всего, стоит обратить внимание на проект Tcl Developer Xchange ; среди прочего он предоставляет Tcl-код интерактивного учебного пособия по Tcl. Кроме того, существует лаборатория Tcl на сайте SourceForge .

Tcl-сценарии имеют проблемы переносимости, аналогичные проблемам сценариев shell. Сам язык хорошо переносим на другие платформы, но компоненты, которые он вызывает, могут не быть таковыми. Существуют реализации Tcl для операционных систем семейства Microsoft, MacOS и многих других платформ. Tcl/Tk-сценарии будут работать на любой платформе, обладающей возможностями поддержки GUI-интерфейса.

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

14.4.5.1. Учебный пример: TkMan

TkMan — браузер для man-страниц Unix и документов Texinfo. Исходный код состоит примерно из 1200 строк, что довольно много для программы, написанной на чистом Tcl, но код необычайно хорошо организован и разделен на модули. Для создания GUI-интерфейса, несколько лучшего, чем интерфейс, поддерживаемый стандартными утилитами man(1) или xman(1), используется инструментарий Tk.

TkMan представляет собой хороший учебный пример, поскольку он демонстрирует почти полную гамму технических приемов Tcl. В число наиболее ярких входит Tk-интеграция, управление другими Unix-приложениями (такими как поисковая машина Glimpse) с помощью сценариев, а также использование Tcl для синтаксического анализа Texinfo-разметки.

Любой другой язык не предоставил бы настолько прямой интерфейс к Tk GUI, который составляет большую часть данного кода.

Исходный код и документацию можно найти в Web с помощью ключевого слова "TkMan".

14.4.5.2. Moodss: большой учебный пример по Tcl

Система Moodss представляет собой графическое мониторинговое приложения для системных администраторов. Оно способно следить за журналами и накапливать статистические данные по работе MySQL, Linux, SNMP-сетей и Apache, а также предоставляет их систематизированный обзор посредством GUI-панелей, которые имеют вид электронных таблиц и называются инструментальными панелями (dashboards). Модули мониторинга могут быть написаны на Python или Perl, а также на Tcl. Код безукоризненно написан, организован и считается примером для подражания в Tcl-сообществе. Существует Web-сайт проекта — .

Ядро программы состоит из 18 000 строк Tcl-кода. В нем используется несколько Tcl-расширений, включая специальную объектную систему. Автор Moodss признает, что без них "написание такого большого приложения было бы невозможным".

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

14.4.6. Python

Python является языком написания сценариев, предназначенным для тесной интеграции с С. Он способен как импортировать, так и экспортировать данные в динамически загружаемые С-библиотеки, его также можно вызывать из С как встроенный язык сценариев. Синтаксис Python очень напоминает гибрид синтаксиса С и семейства Modula, но имеет ту необычную особенность, что структура блока фактически определяется отступами (в языке нет аналогов явных директив begin/end или фигурных скобок С). Первая публичная версия Python вышла в 1991 году.

Язык Python представляет собой очень четкую, элегантную конструкцию с превосходными свойствами модульности. Он предоставляет разработчикам возможность писать программы в объектно-ориентированном стиле, но не вынуждает делать этого (возможно программирование более традиционным, процедурным способом, подобным С). Язык имеет систему типов, сравнимую по выразительности с системой типов Perl, включая динамические контейнеры и ассоциативные списки, но менее характерную (документально подтвержден тот факт, что объектная система Perl была создана как подражание системе Python). Данный язык устраивает даже Lisp-хакеров, предоставляя безымянные лямбда-операторы (задаваемые функциями объекты, которые могут распространяться и использоваться итераторами). Python поставляется с Tk-инструментарием, который можно использовать для простого создания GUI-интерфейсов.

Стандартный дистрибутив Python включает в себя клиентские классы для большинства важных Internet-протоколов (SMTP, FTP, POP3, IMAP, HTTP) и порождающие классы для HTML. Поэтому рассматриваемый язык хорошо подходит для протокольных роботов и каналов сетевого администрирования. Он также превосходно подходит для работы в Web CGI и успешно конкурирует с Perl на участке высокой сложности в данной прикладной области.

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

Python не может конкурировать с С или С++ по скорости выполнения (хотя использование стратегии смешанных языков на сегодняшних быстрых процессорах, возможно, делает этот недостаток сравнительно второстепенным). Фактически Python, как правило, считается наименее эффективным и самым медленным из основных языков сценариев — это цена, которую приходится платить за полиморфизм типов во время выполнения. Однако не стоит по этой причине отказываться от использования данного языка. Большинству приложений фактически не требуется производительность, большая, чем предоставляется Python. Приложения, в которых такая производительность действительно необходима, обычно ограничены внешними задержками, такими как задержки сети или дисков. Кроме того, в виде компенсации, Python исключительно просто комбинируется с С, поэтому для достижения существенного прироста скорости чувствительные к производительности Python-модули можно легко преобразовывать в С.

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

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

Наиболее полным справочником по языку Python является книга "Programming Python" [51]. Обширная документация по Python-расширениям также доступна на Web-сайте проекта Python.

Python-программы в большинстве случаев хорошо переносятся между Unix-системами и даже на другие операционные системы. Стандартная библиотека достаточно мощная для того, чтобы значительно сократить использование непереносимых вспомогательных программ. Реализации Python доступны для операционных систем Microsoft и MacOS. Кроссплатформенная разработка GUI возможна с помощью либо Tk, либо двух других инструментариев. Python/C-приложения могут быть "замороженными", квази-скомпилированными в чистый исходный код С, который должен быть переносимым на системы без установленного Python.

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

14.4.6.1. Небольшой учебный пример по Python:
imgsizer

Imgsizer — утилита, которая переписывает WWW-страницы так, что теги включения изображений автоматически получают верные размеры изображения (это ускоряет загрузку страниц во многих браузерах). Исходные коды и документацию можно найти в подкаталоге "URL WWW tools" архива ibiblio.

Программа imgsizer первоначально была написана на Perl, и была почти идеальным примером небольшого, управляемого шаблонами инструмента обработки текста. Позднее программа была транслирована в Python в целях получения преимущества библиотеки Python по поддержке HTTP-передачи. В результате этого зависимость от внешней утилиты доставки страницы была устранена. Примечательно использование утилит file(1) и identify(1) пакета ImageMagick в качестве специальных средств для получения размеров изображений в пикселях.

Динамическая обработка строк и необходимость замысловатой проверки регулярных выражений сделала бы написание imgsizer на С или С++ крайне сложным. Такая версия также была бы значительно больше и сложнее для чтения. Язык Java решил бы проблему неявного управления памятью, но он едва ли был бы более выразительным, чем С или С++ при проверке текстовых шаблонов.

14.4.6.2. Учебный пример по Python среднего размера: fetchmailconf

В главе 11 пара fetchmail/fetchmailconf рассматривалась как пример одного из способов отделения реализации от интерфейса. Преимущества Python хорошо иллюстрируются на примере утилиты fetchmailconf.

В программе fetchmailconf используется инструментарий Tk для реализации многопанельного редактора конфигурации с графическим интерфейсом (для GTK также существуют Python-привязки и другие инструментарии, но Tk-привязки поставляются с каждым интерпретатором Python).

В режиме для высококвалифицированных пользователей GUI-интерфейс поддерживает редактирование около 60 атрибутов, распределенных по трем уровням панелей. Элементы управления атрибутами включают в себя комбинацию из флажков, переключателей, текстовых полей и списков. Несмотря на эти сложности, автору книги потребовалось меньше недели, включая четыре дня на изучение Python и Tk, для проектирования и кодирования первой полнофункциональной версии.

Python превосходит другие языки в быстром создании прототипов GUI-интерфейсов, а (как показывает пример fetchmailconf) прототипы часто являются основными составляющими реальных проектов. Perl и Tcl имеют аналогичные сильные стороны в данной области (включая инструментарий Tk, который был написан для Tcl), но они сложнее в управлении на уровне сложности кода fetchmailconf (приблизительно 1400 строк). Emacs Lisp не подходит для GUI-программирования.

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

14.4.6.3. Большой учебный пример Python: PIL

Python-библиотека обработки графики (Python Imaging Library — PIL) поддерживает обработку растровой графики. Программа поддерживает множество популярных форматов, включая PNG, JPEG, BMP, TIFF, PPM, XBM и GIF. Python-программы могут использовать ее для того, чтобы конвертировать и трансформировать изображения. В число поддерживаемых трансформаций входит кадрирование, вращение, масштабирование и сдвиг. Также поддерживается пиксельное редактирование, искривление изображений и преобразования цветов. Дистрибутив PIL включает в себя Python-программы, которые делают доступными данные средства из командной строки. Таким образом, программу PIL можно использовать для трансформации изображений в пакетном режиме или в качестве мощного инструментария, посредством которого реализуется программируемая обработка растровых изображений.

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

Было бы сложно или даже невозможно написать аналогичный инструментарий на Emacs Lisp или shell, которые вообще не имеют или не документируют интерфейс C-расширений. Tcl имеет хорошие средства C-расширений, но программа PIL была бы неудобно большим проектом на Tcl. Подобные средства имеются в Perl (Perl XSO), но они представляют собой узкоспециальные решения, слабо документированы, сложны и нестабильны по сравнению со средствами Python, а также используются редко. Интерфейс машинно-зависимых методов в Java (Native Method Interface), вероятно, обеспечивает возможности, сравнимые с возможностями Python. Было бы, вероятно, рационально создать проект PIL на Java.

Код программы PIL и документация доступны на Web-сайте проекта .

14.4.7. Java

Язык программирования Java был разработан под девизом "написанное однажды работает везде" и предназначен для поддержки встроенных интерактивных программ (или аплетов) на Web-страницах, которые запускались бы из любого браузера. Благодаря серии грубых технических и стратегических просчетов правообладателя, корпорации Sun Microsystems, данный язык потерпел неудачу в достижении обеих первоначальных целей. Однако Java до сих пор достаточно силен как в системном, так и в прикладном программировании, чтобы составить конкуренцию С и С++. Язык Java был анонсирован в 1995 году.

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

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

Недостатком Java можно считать то, что (по сравнению, например, с Python) некоторые части языка представляются чрезмерно сложными, а другие неразвиты. Видимость классов Java и правила явного определения блоков являются вычурными. Такие функции, как внутренние и неименованные классы, могут привести к очень запутанному коду. Отсутствие надежных методов деструкторов означает, что трудно гарантировать соответствующее управление другими ресурсами, кроме памяти, например, мьютексами и блокировкой файлов. Значительная часть средств операционной системы Unix, включая сигналы, опрос и выбор, не доступна из Java в стандартной поставке. Несмотря на то, что в Java имеются очень мощные I/O-средства, простое чтение текстовых файлов затруднено.

Наблюдается и сложная проблема с библиотеками, подобная "аду DLL" в Windows. В Java отсутствует метод для управления различными версиями библиотек, что может привести к возникновению крупных проблем в таких средах, как серверы приложений, где сервер может поставляться с одной версией (например) XML-библиотеки, а приложение с другой (обычно более новой) версией. Единственной возможностью решения таких проблем является переменная среды

CLASSPATH
, источник хронических проблем внедрения.

Более того, Sun неразумно управляет Java как с политической, так и с технической точки зрения. Первый GUI-инструментарий Java, AWT был неорганизованной смесью, которую необходимо было в корне заменить. Отказ от стандартизации ECMA/ISO еще больше раздражал разработчиков, уже расстроенных положениями SCSL (Sun Community Source License — лицензия на исходный код в сообществе Sun). Ограничения данного документа препятствуют реализациям Java 1.2 с открытым исходным кодом и их спецификациям J2EE (Java 2 Enterprise Edition). Это дискредитирует исходные цели универсальной переносимости Java.

К сожалению, аплеты браузеров мертвы. Фактически их уничтожило решение Microsoft не поддерживать Java 1.2 в Internet Explorer. Однако язык Java, вероятно, нашел гарантированную нишу в экологии вычислений — "сервлеты", работающие внутри серверов Web-приложений. Java также все чаще используется для многих внутри корпоративных программных разработок, не связанных непосредственно с базами данных или Web-серверами. Данный язык становится главным конкурентом как для платформы Microsoft ASP/COM, так и для Perl CGI. Наконец, Java широко распространен и все больше используется как язык для обучающего, вводного программирования (роль, для которой он чрезвычайно хорошо подходит).

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

Как Java достигнет равновесия с другими рассмотренными здесь языками до сих пор не ясно и может сильно зависеть от масштаба проекта. Можно ожидать, что соответствующая Java ниша будет подобна нише Python. Как и Python, Java не может конкурировать ни с С или С++ в отношении чистой скорости выполнения, ни с Perl в мелких проектах, где интенсивно используется шаблонное редактирование. Данный язык (более явно, чем Python) является избыточным для мелких проектов. Можно предположить, что Python получит преимущество в мелких проектах, a Java в более крупных, но окончательный результат будет ясен позднее.

Лучшим печатным справочником, вероятно, является книга "Java In A Nutshell" [19], она, однако, не является лучшим учебным пособием. Таким, вероятно, можно считать книгу "Thinking in Java" [17]. Ссылки на все мировые Web-сайты, посвященные Java, начинаются с сайта Java Sun , на котором также представлена полная HTML-документация, доступная для бесплатной загрузки. Полезные ссылки по Java также собраны на сайте проекта Open Directory Java Page .

Реализации Java доступны для всех Unix-систем, операционных систем Microsoft, MacOS и многих других платформ.

Исходные коды Kaffe, открытой реализации Java, в которой библиотеки классов согласуются с большей частью JDK 1.1 и частично с JDK 1.2, доступны на сайте проекта Kaffe .

Существует Java-интерфейс для GCC. Программа GCJ способна компилировать Java-код либо в Java-байткод, либо в собственный код, а также Java-байткод — в собственный код. Данный компилятор поставляется в пакете с открытыми библиотеками классов, реализующими большую часть JDK 1.2, и интерпретатором Java-байткода, который называется gij. Подробности описаны на странице проекта GCJ .

Существует Java IDE-среда для Emacs, см. сайт проекта JDEE .

Java прекрасно переносится между платформами на уровне языка. Проблема может возникнуть из-за несовершенных реализаций библиотек (особенно ранних версий JDK 1.1, которые не поддерживают более новый пакет JDK 1.2).

Наилучшим качеством Java является то, что данный язык достаточно плотно приблизился к идеалу "написанное однажды работает везде", для того чтобы быть полезным в качестве независимой от операционной системы среды. Наибольший недостаток Java заключается в том, что дробление Java 1/Java 2 подрывает эту цель, глубоко разочаровывая разработчиков.

14.4.7.1. Учебный пример: FreeNet

Freenet — одноранговый сетевой проект, цель которого заключается в том, чтобы сделать невозможной цензуру и подавление информационного наполнения Web-страниц[122]. Разработчики Freenet предвидят несколько видов использования проекта.

• Неподконтрольное цензуре распространение спорной информации: Freenet защищает свободу слова, без цензуры разрешая анонимную публикацию различных материалов, от рядовой альтернативной журналистики до официально запрещенных разоблачений.

• Эффективное распространение информационного наполнения, требующего большой полосы пропускания: адаптивное кэширование и зеркала используются для распространения программных обновлений Debian Linux.

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

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

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

Язык С является традиционным для инфраструктурных проектов такого рода, которые имеют высокие требования к производительности, но недостаток стандартизированного сетевого API значительно затруднил бы переносимость. Такие же трудности возникли бы и в случае использования С++. Языки Tсl, Perl или Python могли бы сократить нагрузку переносимости, но ценой больших потерь в производительности. Emacs Lisp был бы крайне медленным и совершенно несоответствующим.

14.4.8. Emacs Lisp

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

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

Тем не менее, существует значительный диапазон применений, в которых Emacs более эффективен, чем любой другой язык. Многие из вариантов применения с обеспечением клиентского интерфейса для инструментов разработки, таких как С-компилятор и линкер, утилита make(1), системы контроля версий и символические отладчики; такое применение Emacs Lisp рассматривается в главе 15.

В более общем смысле, Emacs для шаблонного или синтаксического интерактивного редактирования является тем же, чем язык Perl для шаблонного пакетного редактирования. Любое приложение, в котором задействована интерактивная обработка специального файлового формата или текстовой базы данных, является достойным кандидатом для того, чтобы его прототип (а возможно, и окончательная реализация) был написан как Emacs-режим (программа на Emacs Lisp, которая точно определяет поведение редактора).

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

Emacs Lisp — по сути Lisp. Следовательно, он автоматически управляет памятью и гораздо более изящен, а также имеет большую мощность, чем большинство традиционных языков, или, в действительности, большинство нетрадиционных языков. Emacs Lisp на этом уровне способен соперничать с Java или Python и превосходит языки С или С++, Perl, shell или Tсl. Неизменная проблема Lisp, которая заключается в недостатке стандартизированной ОС-привязки для переносимости, решается ядром Emacs, которое на самом деле является ОС-привязкой.

Другая непреодолимая проблема — чрезмерное потребление ресурсов, на современных машинах более не является реальной проблемой. Ранее были широко распространены насмешливые расшифровки названия, такие как "Emacs Makes A Computer Slow" (Emacs тормозит компьютер) и "Eventually Munches All Computer Storage" (в конечном итоге занимает всю память) (фактически в дистрибутив Emacs включен список таких расшифровок). Но в настоящее время многие другие широко используемые категории программ (такие как Web-браузеры) превышают размеры и являются более сложными, чем Emacs, который в сравнении с ними кажется довольно умеренным.

Наиболее полным справочником по Emacs Lisp является руководство "The GNU Emacs Lisp Reference Manual", которое можно просмотреть с помощью справочной системы "info-документов", встроенной в Emacs. Данное руководство также можно загрузить с FTP-узла FSF . Если оно покажется малопонятным, может помочь книга "Writing GNU Emacs Extensions" [30].

Программы Emacs Lisp отличаются превосходной переносимостью между платформами. Реализации Emacs доступны для всех Unix-систем, операционных систем Microsoft и MacOS.

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

Дополнительная информация, касающаяся Emacs, приводится в разделе "Полезные сведения о Emacs" следующей главы.

14.5. Тенденции будущего

В таблице 14.1 приведены приблизительные показатели современного распространения языков. Данные взяты с проектов SourceForge[123] и Freshmeat[124], двух важнейших сайтов новых программ по состоянию на март 2003 года.


Таблица 14.1. Выбор языков

Язык SourceForge Freshmeat
01.03.03 01.03.04 01.03.03 01.03.04
С 10296 13479 4845 6191
С++ 9880 13570 2098 2922
Shell 1058 1473 487 620
Perl 4394 5560 2508 3031
Tcl 649 811 328 377
Python 2222 3267 948 1459
Java 8032 12126 1900 2977
Emacs Lisp ? ? 31 37

Данные проекта SourceForge несколько сглажены по ряду причин: интерфейс запроса не позволяет осуществлять фильтрацию одновременно по операционным системам и языкам, поэтому некоторые из чисел содержат MacOS- и Windows-проекты. В результате, вероятно, значительно преувеличивается распространение С++ и Java. Однако Unix-проекты значительно преобладают (в соотношении приблизительно 3:1), поэтому показатели других языков, вероятно, не слишком искажены.

Данные Freshmeat меньше, но на данном сайте поддерживаются только Unix-версии программ и учитываются только актуальные разработки, а не беспорядочное нагромождение исчезнувших и приостановленных проектов SourceForge. Таким образом, примечательно, что совокупные данные отстают от данных SourceForge примерно в соотношении 1:2, кроме случаев (С++ и Java), где можно было бы ожидать отклонения от пропорции ввиду отсутствия Windows-проектов.

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

В целом, показатели С, С++ и Emacs Lisp оставались стабильными в течение 1997-2003 годов. Показатели языка С росли медленно за счет более ранних традиционных языков, таких как FORTRAN. С другой стороны, С++ утратил некоторую часть поклонников, которые предпочли язык Java.

Прилично возросло использование Perl, однако развитие языка приостановлено. Внутреннее устройство Perl печально известно своей неряшливостью. Несомненно, уже давно пора переписать реализацию данного языка с самого начала. Правда, одна такая попытка, предпринятая в 1999 году, провалилась, а другая пока, видимо, приостановлена. Тем не менее, Perl остается ведущим языком сценариев и преобладает в Web-сценариях и CGI.

Язык Tcl переживает период относительного спада. В 1996 году широко распространенная и правдоподобная оценка размеров сообщества указывала на то, что на каждого Python-хакера приходится пять Tcl-хакеров и двенадцать Perl-хакеров. По данным SourceForge, в настоящее время соотношение приблизительно равно 3:1:7. Однако Tcl представляется весьма широко используемым в написании сценариев для специализированных компонентов в различных отраслях промышленности, включая автоматизацию проектирования электроники, радио и телевещание, а также киноиндустрию.

Рост популярности Python настолько же стремителен, насколько стремителен спад Tcl. Хотя размеры Perl-сообщества до сих пор вдвое превышают численность поклонников Python, видимая тенденция перехода талантливейших Perl-хакеров к использованию Python является довольно угрожающей для первого языка, особенно, учитывая то, что миграция в противоположном направлении полностью отсутствует. Язык Java стал широко использоваться в тех местах, которые уже охвачены технологией Sun Microsystems, и активно внедряется в качестве учебного языка в образовательном процессе для студентов компьютерных специальностей. Вместе с тем в других областях данный язык только в малой степени более популярен, чем это наблюдалось в 1997 году. Стремление корпорации Sun к использованию частной модели лицензирования предотвратило главный рост, предсказываемый многими наблюдателями. В сообществе Linux и в более широком сообществе открытого исходного кода, язык Java не соперничает с С, как в других культурах.

За весь период не появилось ни одного нового универсального языка, который мог бы составить конкуренцию рассматриваемым здесь языкам. PHP вторгается в Web-разработку, вытесняя Perl CGI-сценарии (как и ASP и серверные Java-приложения), однако он почти никогда не используется для автономного программирования. Не-Emacs-диалекты Lisp, перспективная в свое время область, которая, казалось, должна была возродиться в середине 1990-х годов, продолжает сдавать свои позиции. Недавние разработки, такие как Ruby (гибрид Python-Perl-Smalltalk, разработанный в Японии) и Squeak (вариант Smalltalk с открытым исходным кодом), выглядят многообещающими, но пока не привлекли хакеров из других сообществ и не продемонстрировали "неослабевающей силы".

14.6. Выбор Х-инструментария

Проблемой, связанной с выбором языка, является выбор X-инструментария для GUI-программирования. Здесь уместно упомянуть затронутую в главе 1 тему отделения политики от механизма в системе X.

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

Несомненно, язык Java обладает собственными встроенными кроссплатформенными инструментариями, поэтому выбирать придется между AWT (используемым везде) и Swing (более мощным, более сложным, медленным и поставляемым только в составе пакета JDK 1.2/Java 2). В оставшейся части данного раздела основное внимание уделено другим уже рассмотренным языкам. Аналогично, при использовании Tcl будет использоваться и Tk. Вероятно, существует не слишком много особенностей в оценке альтернатив.

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

В настоящее время серьезно стоит рассматривать четыре вида инструментария: Tk, GTK, Qt и wxWindows, из которых очевидно ведущими являются GTK и Qt. Для всех четырех инструментариев предусмотрены версии для MacOS и Windows, поэтому в любом случае разработчик получает возможность кроссплатформенной разработки.

Старейшим и наиболее распространенным из них считается инструментарий Tk. Он является собственным инструментарием для Tcl, и привязки к нему поставляются вместе со стандартной версией Python. Библиотеки для обеспечения языковых привязок к Tk, как правило, доступны для С и С++. К сожалению, стандартный набор элементов управления Tk ограничен и довольно уродлив. С другой стороны, элемент управления Canvas (холст) обладает возможностями, которые в других инструментариях до сих пор реализуются с трудом.

Инструментарий GTK возник как замена для Motif и создавался для поддержки GIMP. В настоящее время он является предпочтительным инструментарием проекта GNOME и используется в сотнях GNOME-приложений. Собственным API-интерфейсом является С. Доступны привязки для С++, Perl и Python, но они не поставляются в стандартных дистрибутивах языка. GTK является единственным из четырех инструментариев с естественной C-привязкой.

Qt — инструментарий, связанный с KDE-проектом. Он представляет собой собственную библиотеку С++. Доступны привязки для Python и Perl, но они не поставляются со стандартными интерпретаторами. Qt получил известность благодаря наличию хорошо спроектированного и наиболее выразительного API из всех четырех инструментариев, однако его принятие в начальной стадии было заблокировано полемикой по ранним версиям лицензии и в дальнейшем тормозилось медленным созданием С-привязки.

Инструментарий wxWindows также является естественным для С++ и имеет доступные привязки в Perl и Python. Его разработчики придают особое значение главным образом поддержке кроссплатформенной разработки и рассматривают ее как главную рыночную цель инструментария. Другая цель связана с тем, что wxWindows фактически является упаковщиком для собственных (GTK, Windows и MacOS 9) элементов управления на каждой платформе, а поэтому приложения, написанные с его использованием, имеют естественные для данных систем вид и восприятие.

К середине 2003 года было описано не слишком много подробных исследований, однако Web-поиск фразы "X toolkit comparison" поможет найти некоторые полезные справочные сведения. В табл. 14.2 обобщена информация о состоянии рассмотренной области.


Таблица 14.2. Сравнительные характеристики X-инструментариев

Инструментарий Собственный язык Поставляется с Привязки
C С++ Perl Tcl Python
Tk Tcl Tcl, Python + + + + +
GTK С Gnome + + + + +
Qt С++ KDE + + + + +
wxWindows С++ + + + +

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

15 Инструментальные средства: тактические приемы разработчика

Unix дружественна к пользователю, но привередлива в выборе друзей.

—Аноним

15.1. Операционная система, дружественная к разработчику

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

Несмотря на то, что в Unix есть все необходимые инструменты и каждый из них хорошо документирован, они не связаны с помощью интегрированной среды разработки (Integrated Development Environment — IDE). Их поиск и внедрение в инструментальный набор, удовлетворяющий потребностям разработчика, всегда требовали значительных усилий.

Разработчику, привыкшему к хорошей IDE-среде (GUI-управляемой комбинации редактора, конфигуратора, компилятора и отладчика, которая в наши дни широко распространена в системах Macintosh и Windows), принятый в Unix подход может показаться бессистемным, туманным и примитивным. Однако в действительности он достаточно систематизирован.

Использование IDE имеет смысл для одноязыкового программирования в слабо оснащенной инструментами среде. Если работа программиста ограничена оттачиванием вручную кода на С или С++, то IDE-среды весьма целесообразны. Однако в Unix выбор языков и вариантов реализации гораздо разнообразнее, а практика использования нескольких генераторов кода, специальных конфигураторов и многих других стандартных и нестандартных инструментов является общепринятой.

В Unix действительно существуют IDE-среды (имеется несколько таких сред с открытыми исходными кодами, включая эмуляции основных IDE Macintosh и Windows). Однако с их помощью трудно контролировать неограниченное множество инструментальных средств, и поэтому IDE-среды используются нечасто. Операционная система Unix поддерживает более гибкий стиль, центром которого не является исключительно цикл редактирование/компиляция/отладка.

В данной главе рассматриваются тактические приемы разработки в Unix — создание кода, управление его конфигурацией, профилирование, отладка, а также автоматизация большого количества монотонной работы, связанной с этими задачами, с тем чтобы разработчик мог сконцентрироваться на более увлекательных аспектах. Как обычно, при изложении материала основное внимание в большей степени уделено архитектурной картине, чем пошаговым инструкциям. Если же читатель интересуется пошаговыми деталями, то рекомендуется обратиться к книге "Programming with GNU Software" [50], в которой описывается большинство инструментов, рассмотренных в данной главе.

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

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

15.2. Выбор редактора

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

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

В качестве инструментов для серьезной работы в сфере Unix-программирования полностью доминируют два редактора. Каждый из них доступен в нескольких вариантах реализации, однако имеет стандартную версию, которую, несомненно, можно найти в любой современной Unix-системе. Речь идет о редакторах vi и Emacs. Они рассматривались в главе 13 как часть темы целесообразного размера программного обеспечения.

Как отмечалось в главе 13, данные редакторы отражают тенденции резко контрастирующих философий проектирования, причем оба являются чрезвычайно популярными среди определенной части пользовательского контингента. Опросы Unix- программистов непротиворечиво указывают на соотношение 50/50 между ними, тогда как доля всех остальных редакторов минимальна.

Ранее при рассмотрении vi и Emacs основное внимание уделялось их необязательной сложности и сопутствующим вопросам философии проектирования. Многие другие аспекты данных редакторов достойны изучения как с практической точки зрения, так и с точки зрения культурной грамотности в Unix-сообществе.

15.2.1. Полезные сведения о vi

Название vi — аббревиатура от "visual editor" (визуальный редактор), произносится как "ви ай" (а не "вай" и определенно не "шесть").

vi не был самым ранним экранным редактором. Пальма первенства в этой области принадлежит программе Rand editor (re), которая работала в Version 6 Unix в 1970-х годах. Однако vi — самый долгоживующий экранный редактор, созданный для Unix, который до сих пор используется и является "священной" составляющей традиции Unix.

Первоначальная версия vi была в наличии в самых ранних дистрибутивах BSD начиная с 1976 года; в настоящее время она устарела. Данную версию заменил редактор "new vi", который поставлялся с 4.4BSD и имеется в современных ее вариантах, таких как BSD/OS, FreeBSD и NetBSD. Существует несколько вариантов с расширенными функциями, особенно vim, vile, elvis и xvi, среди которых vim, вероятно, является наиболее популярным и поставляется в составе многих Linux-систем. Все варианты довольно похожи и используют основной набор команд, неизменный со времен первоначальной версии vi.

Версии vi доступны для операционных систем Windows и MacOS.

Большинство вводных книг по Unix включают в себя главу, описывающую основное использование редактора vi. Ответы на часто задаваемые вопросы по использованию vi доступны на сайте Editor FAQ/vi . Множество других копий данной страницы можно найти с помощью поиска в Web страниц, в заголовках которых имеются слова "vi" и "FAQ".

15.2.2. Полезные сведения о Emacs

Emacs означает "EDiting MACroS" (произносится "и-макс"). Он первоначально был написан в конце 1970-х годов как набор макросов в редакторе, который назывался ТЕСО, после чего переписывался несколько раз различными способами. Забавно, что современные реализации Emacs включают в себя режим эмуляции TECO.

Ранее при обсуждении редакторов и необязательной сложности отмечалось, что многие пользователи считают Emacs чрезмерно тяжеловесным. Однако затраты времени на его изучение окупаются впоследствии повышением продуктивности. Emacs поддерживает множество мощных режимов редактирования, которые помогают с синтаксисом различных языков программирования и разметки. Далее в настоящей главе рассматривается возможность использования Emacs в комбинации с другими средствами разработки, предоставляющей возможности, сравнимые (а во многих случаях превосходящие) с возможностями традиционных IDE-сред.

Стандартной версией Emacs, повсеместно доступной на современных Unix-системах, является GNU Emacs; программа, которая обычно запускается при вводе команды

emacs
в командной строке Unix-оболочки. Исходный код и документация по GNU Emacs доступны на сайте архива Фонда свободного программного обеспечения .

Существует вариант, который называется XEmacs. Он имеет улучшенный X-интерфейс, но совершенно те же возможности (унаследованные от Emacs 19). Домашняя страница XEmacs: . Emacs (и Emacs Lisp) повсеместно доступны в современных Unix-системах. Он перенесен на MS-DOS (где работает слабо), а также на операционные системы Windows 95 и NT (где, как говорят, работает достаточно неплохо).

Emacs включает в себя собственное интерактивное учебное руководство и очень подробную документацию. Инструкции по запуску данных ресурсов можно найти на стандартном экране запуска Emacs. Хорошим введением является книга "Learning GNU Emacs" [10].

Клавиатурные комбинации, используемые в Unix-версиях Netscape/Mozilla, а также в текстовых окнах Internet Explorer (в формах и почтовой программе), скопированы со стандартных привязок для основных операций редактирования текста. Данные привязки — ближайшие элементы к кроссплатформенному стандарту клавиатурных комбинаций редакторов.

15.2.3. "Антирелигиозный" выбор: использование обоих редакторов

Многие люди, обычно использующие оба редактора vi и Emacs, склонны применять их для различных задач и находят весьма ценными преимущества использования обоих.

Вообще, vi наилучшим образом подходит для решения мелких задач — быстрого написания ответов на письма, простых изменений в конфигурации системы и т.д. Данный редактор особенно полезен при работе в новой системе (или на удаленной системе через сеть), когда не доступны файлы настроек Emacs.

Роль Emacs — расширенные сеансы редактирования, в которых необходимо решать сложные задачи, модифицировать несколько файлов и использовать результаты работы других программ в течение данного сеанса. Для программистов, использующих систему X на своих консолях (что типично для современных Unix-систем), считается обычным запускать Emacs сразу после регистрации в системе в большом окне и оставлять его работающим постоянно, возможно, просматривая десятки файлов и даже запуская программы в многочисленных подокнах Emacs.

15.3. Генераторы специализированного кода

Unix имеет давнюю традицию поддержки инструментов, которые специально предназначены для генерации кода для различных специальных целей. Давними "монументами" данной традиции, которые "уходят корнями" в Version 7 и ранние дни Unix, а также фактически использовались для написания оригинального Portable С Compiler в 1970-х годах, являются утилиты lex(1) и yacc(1). Их современными совместимыми потомками являются flex(1) и bison(1), часть GNU-инструментария, которая до сих пор интенсивно используется и в наши дни. Данные программы послужили примером, который продолжает развиваться в проектах, подобных построителю интерфейсов Glade в GNOME.

15.3.1. yacc и lex

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

Если разработчик достиг того момента, когда планируется реализовать мини- язык с нуля, а не путем расширения или внедрения существующего языка сценариев или анализатора XML, то утилиты yacc и lex, вероятно, окажутся наиболее важными инструментами после компилятора С.

Как lex, так и yacc генерируют код для одной функции, соответственно, для "получения лексемы из входного потока" и для "синтаксического анализа последовательности лексем на предмет ее соответствия грамматике". Обычно созданная yacc функция синтаксического анализатора вызывает функцию анализатора лексем, сгенерированного lex, каждый раз при необходимости получения следующей лексемы. Если в yacc-сгенерированном синтаксическом анализаторе вообще не существует написанных пользователем обратных вызовов С, то работа данного анализатора сводится только к проверке синтаксиса. Возвращаемое значение сообщит вызывающей программе о совпадении входных данных с ожидаемой грамматикой.

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

Утилита yacc имеет довольно некрасивый интерфейс — через экспортируемые глобальные переменные с именным префиксом

yy_
. Это связано с тем, что программа yacc предшествовала структурам С. Фактически yacc предшествовала самому языку С; первая реализация утилиты была написана на языке В, предшественнике С. Грубый, хотя и эффективный алгоритм, используемый в сгенерированных yacc синтаксических анализаторах при попытках восстановления после ошибок анализа (лексемы выталкиваются до тех пор, пока не обнаружится явная ошибка), также может привести к проблемам, включая утечки памяти.

Если вы создаете деревья грамматического разбора с использованием функции malloc и начинаете выталкивать элементы стека в процессе восстановления после ошибки, то вам не удастся восстановить (высвободить) память. Как правило, утилита yacc не способна это делать, поскольку не имеет достаточных сведений о содержимом стека. Если бы yacc-анализатор был написан на С++, то он мог бы "предположить", что значения являются классами, и использовать деструктор. В "реальных" компиляторах узлы дерева грамматического разбора генерируются с помощью распределителя динамической памяти, поэтому узлы "не вытекают", но так или иначе, проявляется логическая утечка памяти, которую необходимо проанализировать, чтобы создать систему восстановления после ошибок промышленного уровня.

Стив Джонсон.

Программа lex — генератор лексических анализаторов. Она входит в состав того же функционального семейства, что и grep(1) и awk(1), но является более мощной, поскольку позволяет подготовить произвольный С-код для выполнения при каждом совпадении. Программа принимает декларативный мини-язык и создает "скелетный" С-код.

Для того чтобы понять работу lex-сгенерированного анализатора лексем, существует грубый, но удобный способ — мысленная инверсия работы grep(1). Тогда как grep(1) принимает одно регулярное выражение и возвращает список совпадений во входном потоке данных, каждый вызов lex-сгенерированного анализатора лексем принимает список регулярных выражений и указывает, какое выражение встречается следующим в потоке данных.

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

Генри Спенсер.

Утилита lex была написана для автоматизации создания лексических анализаторов (анализаторов лексем) для компиляторов. Впоследствии оказалось, что она имеет удивительно широкий диапазон применения для других видов распознавания образцов, и с тех пор описывается как "швейцарский нож Unix-программирования"[125].

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

Джон Джарвис (John Jarvis) в Холмделе (Holmdel — лаборатория AT&T) использовал lex для поиска неисправностей в монтажных платах. Он сканировал плату, применял методику кодирования цепей для представления границ областей на плате, а затем использовал Lex для определения образцов, с помощью которых можно было бы находить распространенные ошибки монтажа.

Майк Леск.

Важнее то, что мини-язык lex-спецификации является более высокоуровневым и компактным, чем эквивалентный С-код, написанный вручную. Доступны модули для использования flex (версия с открытым исходным кодом) с Perl (их можно найти в Web с помощью фразы "lex perl"), а также идентично работающая реализация, которая является частью средства PLY в Python.

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

Утилита yacc — генератор синтаксических анализаторов. Она также была написана для автоматизации части работы по написанию компиляторов, yacc принимает на входе грамматическую спецификацию в декларативном мини-языке, подобном BNF (Backus-Naur Form — запись Бэкуса-Наура), с С-кодом, связанным с каждым элементом грамматики. Данная программа генерирует код для функции синтаксического анализа, которая при вызове принимает текст, соответствующий грамматике из входного потока. По мере распознавания каждого грамматического элемента, функция анализатора запускает связанный С-код.

Комбинация утилит lex и yacc весьма эффективна для написания языковых интерпретаторов всех видов. Хотя большинству Unix-программистов никогда не придется выполнять данный вид универсального построения компилятора, для которого задумывались эти инструменты, они чрезвычайно полезны для написания анализаторов синтаксиса конфигурационных файлов и узкоспециальных мини-языков.

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

Если существует возможность реализовать анализатор на языке более высокого уровня, чем С (что и рекомендуется; см. главу 14), то следует рассмотреть такие

эквивалентные средства, как PLY в Python (которое охватывает функции lex и yacc)[126] или Perl-модули PY и Parse::Yapp, либо Java-пакеты CUP[127], Jack[128] или Yacc/M[129].

Как и в случае с макропроцессорами, одной из проблем, связанных с генераторами кода и препроцессорами, является то, что ошибки компиляции в сгенерированном коде могут содержать номера строк сгенерированного кода (который редактировать нежелательно), а не номера строк во входных данных генератора (т.е. там, где необходимо внести изменения). В утилитах yacc и lex данная проблема решается такими же конструкциями

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

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

15.3.1.1. Учебный пример: грамматика
fetchmailrc

Канонический демонстрационный пример, который, видимо, приводится в каждом учебном пособии по lex и yacc, представляет собой игрушечную программу интерактивного калькулятора, которая анализирует и вычисляет введенные пользователем арифметические выражения. В данной книге нет этого избитого клише. Заинтересованные читатели могут обратиться к исходному коду реализации bc(1) и dc(1) проекта GNU или к принципиальному примеру "hoc"[130] см. [39].

Вместо этого грамматика анализатора конфигурационных файлов fetchmail предоставляет хороший учебный пример среднего размера по использованию lex и yacc. Здесь имеется несколько интересных моментов.

lex-спецификация в файле

rcfile_l.l
— весьма типичная реализация shell-подобного синтаксиса. Обратите внимание на то, как два дополняющих правила поддерживают строки либо с одинарными, либо с двойными кавычками; данная идея хороша в принципе. Правила для принятия (возможно, со знаком) целых литералов и отклонения комментариев также являются достаточно распространенными.

yacc-спецификация в файле

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

15.3.2. Учебный пример: Glade

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

Glade представляет собой хороший современный пример генератора прикладного кода. Описанные ниже функции, которые отсутствуют в большинстве GUI-построителей (особенно в большинстве коммерческих GUI-построителей), делают Glade. Unix-программой "по духу".

Glade GUI и генератор кода Glade не связаны в массивном монолите, а подчиняются правилу разделения (и построены согласно модели "разделения ядра и интерфейса").

• GUI и генератор кода соединяются с помощью текстового формата (основанного на XML), который можно читать и модифицировать с помощью других инструментов.

• Поддерживается несколько целевых языков (а не только С или С++). Существует возможность легко добавлять другие языки.

Конструкция позволяет при необходимости заменить редактор GUI-интерфейса в Glade.

15.4. Утилита make: автоматизация процедур

Сами по себе исходные коды программ не делают приложения. Также важен способ их компоновки и упаковки для распространения. Операционная система Unix предоставляет инструментальное средство для частичной автоматизации данных процессов — make(1). Утилита make описывается в большинстве вводных книг по операционной системе Unix. Более конкретная ссылка приводится в книге "Managing Projects tenth Make" [57]. В случае использования GNU make (наиболее развитого варианта make, который обычно поставляется в составе Unix-систем с открытым исходным кодом) рецепты книги "Programming with GNU Software" [50] могут в некотором отношении оказаться лучшими. Большинство Unix-систем, содержащих GNU make, также поддерживают GNU Emacs. В таких системах, вероятно, полное руководство по make можно обнаружить в info-системе документации Emacs.

На сайте FSF доступны версии GNU make для DOS и Windows.

15.4.1. Базовая теория make

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

Unix-программа make(1), родоначальник всех этих средств, была разработана специально для того, чтобы помочь C-программистам управлять данными инструкциями. Она позволяет описать зависимости между файлами проекта в одном или нескольких "make-файлах". Каждый make-файл состоит из последовательности правил, каждое из которых указывает утилите make, что некоторый заданный целевой файл зависит от некоторого набора исходных файлов и определяет действия в случае, если любой из файлов исходного кода является более новым, чем целевой файл. Фактически программисту не требуется описывать все зависимости, поскольку программа make способна установить большинство очевидных зависимостей по именам файлов и расширениям.

Например, программист может указать в make-файле, что бинарный файл myprog зависит от трех объектных файлов

myprog.о
,
helper.о
и
stuff.о
. Если имеются файлы исходного кода
myprog.с
,
helper.с
и
stuff.с
, то утилита make без специальных указаний определит, что каждый
-файл зависит от соответствующего
-файла, и предоставит собственную стандартную инструкцию для сборки
-файла из
-файла.

Возникновение make связано с визитом ко мне Стива Джонсона (Steve Johnson — автор yacc и других программ). Когда он пришел, он был очень недоволен тем, что ему пришлось потратить впустую утро, занимаясь отладкой корректной программы (ошибка была устранена, файл не был откомпилирован, и, следовательно,

cc *.о
не работала). А поскольку я потратил часть предыдущего вечера, справляясь с той же проблемой в разрабатываемом мною проекте, у нас появилась идея создания инструмента для решения данной задачи. Все началось с тщательно продуманной идеи анализатора зависимостей, потом свелось к нечто более простому и в те же выходные превратилось в make. Использование инструментов, которые все еще оставались сырыми, было частью культуры. Make-файлы были текстовыми, а не "волшебно" закодированными бинарными файлами, поскольку это было в духе Unix: печатаемый, отлаживаемый, понятный материал.

Стюарт Фельдман.

После ввода команды

make
, в каталоге проекта программа make просматривает все правила и временные метки, после чего выполняет минимальный объем работы, необходимый для того, чтобы гарантировать актуальность производных файлов.

Хороший пример make-файла умеренной сложности можно взять из исходных кодов программы fetchmail. В дальнейших подразделах он будет рассматриваться снова.

Очень сложные make-файлы (особенно, когда они вызывают вспомогательные make-файлы) могут стать источником осложнений вместо того, чтобы упростить процесс сборки. Ставшее классическим предупреждение впервые прозвучало в статье "Recursive Make Considered Harmful"[131]. Аргумент в данной статье со времени ее публикации в 1997 году стал общепринятым и почти стал переворотом предыдущей практики в сообществе.

Обсуждение утилиты make(1) будет неполным без признания того факта, что она включает в себя одну из худших недоработок конструкции в истории Unix. Использование символов табуляции в качестве необходимых начальных символов командных строк, связанных с правилом, означает, что интерпретация make-файла может радикально измениться из-за невидимых различий в пустых пространствах.

Почему в столбце 1 используется табуляция? Yacc была новой программой, a Lex ещё более новой. Я их еще не попробовал, поэтому предположил, что это было бы хорошим поводом для обучения. После того как я запутался, впервые попробовав Lex, я просто сделал нечто простое с моделью "конец строки-табуляция". Конструкция работала, и поэтому осталась без изменений. А спустя несколько недель сформировалось сообщество пользователей (около десятка человек), причем большинство из них были моими друзьями, и я не хотел им навредить. Остальное, к сожалению, уже стало историей.

Стюарт Фельдман.

15.4.2. Утилита make в разработке не на C/C++

Программа make может оказаться полезной не только для программ на C/C++. Языки сценариев, подобные описанным в главе 14, могут не требовать традиционных этапов компиляции и компоновки, однако часто существуют другие виды зависимостей, с которыми поможет справиться утилита make(1).

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

Весьма распространенной является практика использования правил make-файлов в целях выражения инструкций для создания документации, так же как и кода. Часто такой подход используется для автоматического создания PostScript или другой производной документации из главных документов, написанных на каком-либо языке разметки (например, HTML или одном из языков создания документов в Unix, которые рассматриваются в главе 18). Фактически такое использование настолько широко распространено, что его стоит проиллюстрировать учебным примером.

15.4.2.1. Учебный пример: использование make для преобразования файла документации

В make-файле программы fetchmail, например, есть три правила, которые связывают файлы FAQ, FEATURES и NOTES с исходными HTML-файлами

fetchmail-FAQ.html
,
fetchmail-features.html
и
design-notes.html
.

HTML-файлы предназначены для просмотра на Web-странице программы fetchmail, но если Web-браузер не используется, то HTML-разметка делает эти файлы неудобными для просмотра. Поэтому

FAQ
,
FEATURES
и
NOTES
представляют собой простые текстовые файлы, предназначенные для быстрого просмотра с помощью редактора или программы-пейджера при чтении собственно исходного кода fetchmail (или, возможно, для размещения на FTP-сайтах, не поддерживающих Web-доступ).

Простые текстовые формы могут быть получены из главных HTML-файлов с помощью распространенной программы lynx(1) с открытым исходным кодом. lynx — является Web-браузером для текстовых дисплеев. Однако если данную программу вызвать с параметром -dump, то она будет достаточно корректно функционировать в качестве преобразователя HTML-ASCII.

Правила make позволяют разработчику редактировать главные HTML-документы, не заботясь впоследствии о повторной ручной сборке простых текстовых форм, поскольку файлы

FAQ
,
FEATURES
и
NOTES
будут соответствующим образом при необходимости каждый раз создаваться заново.

15.4.3. Правила make

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

Нефайловые правила были созданы -умышленно и существовали с первого дня. Правила "make all" и "clean" были моими собственными ранними соглашениями.

Стюарт Фельдман.

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

all

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

make
без аргументов.

test

Запуск автоматизированного тестового пакета для программы, обычно состоящего из набора блочных тестов (unit tests)[132] для поиска регрессий, ошибок или других отклонений от ожидаемого поведения во время процесса разработки. Правило "test" также могут использовать конечные пользователи программы, для того чтобы убедиться, что их инсталляция функционирует корректно.

clean

Удаление всех файлов (таких как бинарные исполняемые и объектные файлы), которые обычно создаются во время выполнения команды

make all
. Команда
make clean
должна вернуть процесс сборки программного обеспечения в исходное состояние.

dist

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

all
таким образом, чтобы правило
make dist
автоматически заново собирало целый проект, прежде чем создать его дистрибутивный архив. Это хороший способ избежать ошибок, в результате которых в дистрибутив не включаются действительно необходимые производные файлы (например, простой текстовый файл
README
в fetchmail, который фактически генерируется из HTML-файла).

distclean

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

make dist
. Действие может быть аналогичным команде
make clean
, но distclean следует в любом случае включать как отдельное правило для документирования происходящего. Если действие отличается, то обычно оно отличается отбрасыванием локальных конфигурационных файлов, которые не являются частью обычной последовательности сборки
make all
(такой как последовательность, сгенерированная утилитой autoconf(1)\autoconf(1) рассматривается в главе 17).

realclean

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

make distclean
, но realclean следует в любом случае включать как отдельное правило для документирования происходящего. Если действие отличается, то обычно оно отличается отбрасыванием файлов, которые являются производными, но (по какой-либо причине), так или иначе поставляются с исходными кодами проекта.

install

Инсталляция исполняемых файлов проекта и документации в системные каталоги таким образом, чтобы они были доступны пользователям (обычно данная операция требует полномочий администратора). Инициализация или обновление баз данных или библиотек, которые необходимы исполняемым файлам для работы.

uninstall

Удаление файлов, установленных в системные каталоги командой

make install
(обычно данная операция требует полномочий администратора). Эта операция должна быть полностью противоположной
make install
. Это правило означает подчинение соглашениям, которые ищут опытные пользователи Unix, так как для них они являются подтверждением продуманной конструкции. Напротив, его отсутствие является в лучшем случае небрежностью и (например, когда в ходе инсталляции создаются большие файлы базы данных) может рассматриваться как некомпетентность и невнимательность.

Работающие примеры всех стандартных целей доступны для изучения в make-файле программы fetchmail. Их изучение позволит понять модель и полнее изучить структуру пакета fetchmail. Одним из преимуществ использования данных стандартных правил является то, что они формируют полную схему проекта.

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

15.4.4. Генерация make-файлов

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

В середине 1980-х годов в дистрибутивах крупных Unix-программ были достаточно распространены сложные специальные shell-сценарии, которые исследовали окружение и использовали собранную информацию для создания нестандартных make-файлов. Такие специальные конфигураторы достигали абсурдных размеров. Автор данной книги однажды написал такой конфигуратор, состоящий из 3000 строк shell-кода, почти вдвое больше любого отдельного модуля программы, для которой он был предназначен, и это не было необычным.

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

Одной из проблем была переносимость на другие платформы. Генераторы make-файлов в большинстве случаев создаются для работы на множестве различных аппаратных платформ и вариантов Unix. Как правило, они пытаются определить параметры локальной системы (включая все от размера машинного слова до инструментов, языков служебных библиотек и даже имеющихся в системе программ форматирования документов). Затем генераторы пытаются использовать полученные сведения для написания make-файлов, которые используют средства локальной системы и компенсируют ее индивидуальные особенности.

Другой проблемой является вывод зависимостей. Существует возможность получить множество сведений о зависимостях в семействе файлов исходного C-кода путем анализа самих файлов (особенно их директив

#include
). Многие генераторы make-файлов выполняют данные действия для автоматического создания make-зависимостей.

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

15.4.4.1. makedepend

Несколько небольших инструментов решают исключительно часть описанной выше проблемы, связанную с автоматизацией правил. Утилита makedepend, распространяемая наряду с системой X Window разработки MIT, является самым быстрым и наиболее полезным из таких инструментов и предустанавливается на все современные Unix-системы, включая все дистрибутивы Linux.

makedepend принимает коллекцию исходных кодов С и генерирует зависимости для соответствующих

-файлов из их директив
#include
. Их можно добавлять непосредственно в make-файл, и makedepend фактически предназначена именно для этого.

makedepend бесполезна для проектов, написанных не на С. Утилита не пытается решать несколько частей проблемы создания make-файлов. Однако то, что она делает, она делает особенно хорошо.

Утилита makedepend полностью документирована на соответствующей странице руководства. Команда

man makedepend
, введенная в терминальном окне, быстро предоставит сведения, необходимые для запуска данной утилиты.

15.4.4.2. Imake

Утилита Imake была написана в попытке автоматизировать создание make-файлов для системы X Window. Она надстраивается на makedepend для решения как проблемы вывода зависимостей, так и проблемы переносимости.

Imake-система эффективно заменяет традиционные make-файлы Imake-файлами, написанными в более компактной и мощной форме, которая (эффективно) компилируется в make-файлы. В процессе компиляции используется файл правил, который считается специфическим для системы и включает в себя множество сведений о локальной среде.

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

Imake-инструменты доступны на любой Unix-системе, поддерживающей X, включая Linux. Существует один "героический" проект [16], цель которого заключается в "прояснении тайн" Imake для не X-программистов. Вопросы, освещаемые данным проектом, стоит изучить всем, кто собирается заниматься X-программированием.

15.4.4.3. autoconf

Утилита autoconf была написана программистами, которые изучили и отклонили подход Imake. Утилита autoconf генерирует для каждого проекта shell-сценарии

configure
, которые подобны старомодным специальным конфигураторам,
configure
-сценарии способны генерировать make-файлы (в том числе).

Утилита autoconf направлена на разрешение проблемы переносимости и вообще не выполняет встроенного вывода зависимостей. Несмотря на то, что данная программа, вероятно, такая же сложная, как Imake, она является гораздо более гибкой и проще расширяется. Вместо использования базы данных правил в системе, утилита генерирует shell-код configure, который осматривает систему, определяя необходимые параметры.

Каждый

configure
-сценарий создается на основе уникального для проекта шаблона
configure.in
, который должен написать разработчик. Однажды сгенерированный сценарий
configure
является самодостаточным и способен конфигурировать данный проект на системах, которые не содержат саму утилиту autoconf(1).

Подход к созданию make-файлов, принятый для autoconf, подобен подходу Imake в том, что разработчик начинает с написания шаблона make-файла для своего проекта. Однако файлы

Makefile.in
, созданные autoconf, по существу являются просто make-файлами с метками-заполнителями для простой текстовой замены; не существует второй формы записи, которую требуется изучать. Если требуется осуществить вывод зависимостей, то необходимо предпринять явные шаги для вызова makedepend(1) или подобного инструмента, или использовать утилиту automake(1).

Утилита autoconf документирована в руководстве в GNU info-формате. Исходные сценарии autoconf доступны на сайте архива FSF, а кроме того, они предустановлены на многих Unix и Linux-системах. Упомянутое руководство можно просматривать с помощью справочной системы Emacs.

Несмотря на отсутствие непосредственной поддержки вывода зависимостей и характерного для autoconf узкоспециального подхода, в середине 2003 года данная утилита, несомненно, была наиболее популярной из всех генераторов make-файлов. Она превзошла Imake и стала причиной выхода из употребления, по крайней мере, одного главного конкурента (metaconfig).

Существует справочник "GNU Autoconf Automake and Libtool" [86]. Дополнительная информация по утилите autoconf в несколько ином аспекте рассматривается в главе 17.

15.4.4.4. automake

Утилита automake — это попытка добавить Imake-подобную функцию вывода зависимостей как уровень над autoconf(1). Разработчик пишет шаблоны

Makefile.am
в форме записи, которая явно подобна Imake-нотации. Затем утилита automake(1) компилирует их в файлы
Makefile.in
, которыми впоследствии оперируют autoconf-сценарии
configure
.

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

Полная документация поставляется с утилитой automake, которую можно загрузить с сайта архива FSF.

15.5. Системы контроля версий

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

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

15.5.1. Для чего используется контроль версий

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

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

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

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

Генри Спенсер.

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

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

15.5.2. Контроль версий вручную

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

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

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

15.5.3 Автоматизированный контроль версий

Для того чтобы избежать описанных выше проблем, можно использовать какую- либо систему контроля версий (Version-Control System — VCS), пакет программ, который автоматизирует большую часть рутинной работы по поддержанию аннотированной истории проекта и позволяет избежать конфликтов модификации.

Большинство VCS-систем используют одну и ту же базовую логику. Использование такой системы начинается с регистрации семейства файлов исходного кода, т.е. с указания VCS-системе начать архивирование файлов, описывая историю их изменения. После этого при необходимости отредактировать один из таких файлов требуется отметить (check out) данный файл — объявить его исключительную блокировку. По окончании редактирования необходимо сдать (check in) файл, добавляя внесенные изменения в архив, снимая блокировку и вводя комментарии, поясняющие суть внесенных изменений.

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

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

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

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

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

Кроме того, системы VCS полезны не только для программного кода. Рукопись данной книги во время написания поддерживалась как совокупность файлов в системе RCS.

15.5.4. Unix-инструменты для контроля версий

Историческое значение в мире Unix имеют три VCS-системы; они рассматриваются в данном разделе. Более развернутое введение и учебные материалы приведены в книге "Applying RCS and SCCS" [5].

15.5.4.1. Source Code Control System (SCCS)

Первой из рассматриваемых систем появилась SCCS, оригинальная система управления исходным кодом (Source Code Control System), разработанная в Bell Labs примерно в 1980 году и представленная в System III Unix. SCCS — это, вероятно, первая серьезная попытка создания унифицированной системы управления исходным кодом. Передовые идеи, впервые реализованные в ней, до сих пор встречаются на некотором уровне во всех последующих системах, включая коммерческие Unix- и Windows-продукты, такие как ClearCase.

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

Не существует ни одной реализации SCCS с полностью открытым исходным кодом. Клон, который называется CSSC (Compatibly Stupid Source Control), разрабатывается при поддержке FSF.

15.5.4.2. Revision Control System (RCS)

Список превосходных альтернатив с открытым исходным кодом начинается с системы RCS (Revision Control System — система управления ревизиями), которая была создана в Университете Пурдью через несколько лет после SCCS и первоначально распространялась с 4.3BSD Unix. Данная система логически подобна SCCS, но имеет более четкий командный интерфейс и хорошие средства для группировки целых редакций проекта под символическими именами.

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

Исходные коды RCS курируются и распространяются FSF. Существуют бесплатные версии для операционных систем Microsoft и VAX VMS.

15.5.4.3. Concurrent Version System (CVS)

CVS (Concurrent Version System — система параллельных версий) была разработана в начале 1990-х годов как клиентская часть к RCS, но модель контроля версий, которая использовалась в ней, настолько отличалась, что данная система была немедленно квалифицирована как новая конструкция. Современные реализации не основываются на RCS.

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

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

В сообществе открытого исходного кода система CVS интенсивно используется для таких проектов, как GNOME и Mozilla. Обычно такие CVS-репозитории позволяют любому разработчику отмечать исходные файлы удаленно. Следовательно, кто угодно может создать локальную копию проекта, модифицировать ее и отправить заплаты с изменениями по почте кураторам проекта. Реальный доступ на запись к такому репозиторию более ограничен и должен быть явно предоставлен кураторами проекта. Разработчик, имеющий такой доступ, может обновить проект из своей модифицированной локальной копии, в результате чего локальные изменения вносятся непосредственно в удаленный репозиторий.

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

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

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

Исходные коды системы CVS сопровождаются и распространяются FSF.

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

15.5.4.4. Другие системы контроля версий

Конструктивные проблемы системы CVS достаточны для того, чтобы создать потребность в лучших VCS-системах с открытым исходным кодом. Несколько таких проектов полным ходом разрабатывались в 2003 году. Наиболее выдающимися из них являются проекты Aegis и Subversion.

Проект Aegis имеет самую длинную историю среди CVS-альтернатив и является зрелой действующей системой. С 1991 года данная система обеспечивает контроль версий для самого проекта Aegis. Особый акцент в этой системе сделан на возвратное тестирование и проверку достоверности.

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

Проект BitKeeper исследует некоторые интересные конструктивные идеи, связанные с группами изменений и множественными распределенными репозиториями кода. Линус Торвальдс использует Bitkeeper для исходных кодов ядра Linux. Однако лицензия (отличная от лицензий на открытый исходный код) на данную систему является спорной и значительно задерживает признание данного продукта сообществом.

15.6. Отладка времени выполнения

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

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

Однако одного только проектирования прозрачных конструкций недостаточно. При отладке программы во время выполнения чрезвычайно полезно иметь возможность изучать состояние выполняемой программы, устанавливать точки останова и контролируемым способом выполнять блоки программы вплоть до уровня одного оператора. В операционной системе Unix имеется давняя традиция поддержки программ, способствующих решению данных проблем. В состав Unix-систем с открытыми исходными кодами входит одно мощное средство, gdb (еще один FSF-проект), которое поддерживает отладку кода, написанного на С и С++.

Языки Perl, Python, Java и Emacs Lisp поддерживают стандартные пакеты или программы (которые включаются в состав их базовых дистрибутивов), позволяющие устанавливать контрольные точки, управлять выполнением и осуществлять общие операции отладки во время выполнения. Tcl, разработанный как небольшой язык для небольших проектов, не имеет такого средства (хотя он имеет средство трассировки, которое можно использовать для наблюдения за переменными во время выполнения).

Для разработчика важно правильно понимать философию Unix и тратить свое время не на низкоуровневые детали, а на качество конструкции, а также автоматизировать все, что возможно, включая кропотливую работу по отладке программы во время выполнения.

15.7. Профилирование

Общее правило: 90% времени выполнения программы тратится на 10% ее кода. Профайлеры представляют собой инструменты, способствующие идентификации этих 10% "горячих точек", которые ограничивают скорость программы, а значит, профайлеры — это хороший способ повышения скорости.

Однако в традиции Unix профайлерам отводится гораздо более важная функция. Они позволяют разработчику не оптимизировать оставшиеся 90%. И это не только сокращает объем работ. Действительно ценный эффект заключается в том, что программист, который не оптимизирует 90% кода, сдерживает глобальную сложность и сокращает количество ошибок.

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

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

Профайлеры для компилируемых языков зависят от измерения параметров объектного кода, поэтому они еще больше зависят от платформы, чем компиляторы. С другой стороны, профайлер компилируемого языка не заботится об исходном языке измеряемой им программы. В Unix один профайлер gprof(1) обрабатывает С, С++ и все остальные компилируемые языки.

Языки Perl, Python и Emacs Lisp имеют собственные профайлеры, включенные в их базовые дистрибутивы. Такие профайлеры переносятся на все платформы, где работают данные языки. В языке Java имеется встроенное профилирование. Tcl все еще не имеет поддержки профилирования.

15.8. Комбинирование инструментов с Emacs

Одной из областей, где редактор Emacs весьма хорошо применим, является его использование в качестве интерфейсной части для других инструментов разработки (эта тема рассматривалась с философской точки зрения в главе 13). Действительно, почти всеми инструментами, рассмотренными в данной главе, можно управлять из сеанса редактора Emacs посредством интерфейсных частей, которые увеличивают эффективность данных инструментов по сравнению с их автономным использованием.

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

Разработчику необходимо читать и учиться — не только использованию Emacs, но и выработке ментальной склонности к поиску синергии между программами и ее созданию. Данный раздел рекомендуется читать как инструкцию в философском смысле, а не только в методическом.

15.8.1. Emacs и make

Например, утилиту make можно запустить из Emacs с помощью команды

ESC-x compile
[Enter]. Данная команда запускает make(1) в текущем каталоге, собирая вывод в буфер Emacs.

Сама по себе данная операция не была бы очень полезной, но Emacs-режим make распознает формат сообщений об ошибках (указывая исходный файл и номер строки), которые генерируются Unix C-компиляторами и многими другими инструментами.

Если какая-либо выполняемая make инструкция генерирует сообщения об ошибках, то команда

Ctl-X `
(Ctrl-X-обратная кавычка) пытается выполнить их синтаксический анализ и последовательно переходит к каждой ошибке, открывая окно соответствующего файла и перемещая курсор к строке с ошибкой[133].

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

15.8.2. Emacs и отладка во время выполнения

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

Emacs-режим Grand Unified Debugger (большой унифицированный отладчик) поддерживает все основные отладчики С: gdb(1), sdb(1), dbx(1) и xdb(1). Он также поддерживает символический отладчик Perl с использованием модуля perldb и стандартные отладчики для Java и Python. Средства, встроенные в сам Emacs Lisp, поддерживают интерактивную отладку кода Emacs Lisp.

К моменту написания книги (середина 2003 года) еще не существовало поддержки для Tcl-отладки из Emacs. Конструкция Tcl такова, что вряд ли когда-либо такая поддержка будет добавлена.

15.8.3. Emacs и контроль версий

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

К счастью, Emacs может помочь и в данной ситуации. Код, встроенный в Emacs, реализует простой в использовании пользовательский интерфейс к системам SCCS, RCS, CVS или Subversion. Команда

Ctl-x v v
пытается определить логически следующую операцию контроля версий, которую необходимо выполнить для редактируемого файла. В число данных операций входят регистрация файла, его отметка и блокировка, а также возвращение (комментарии по изменениям принимаются во всплывающем буфере)[134].

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

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

15.8.4. Emacs и профилирование

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

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

Реальная цель в данном случае также является философской. Не следует выполнять монотонную работу — это пустая трата времени и продуктивности. Если выясняется, что разработчик тратит много времени на низкоуровневые механические части разработки, то следует вернуться назад, воспользоваться философией Unix, применить имеющийся инструментарий для полной или частичной автоматизации задачи.

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

15.8.5. Лучше, чем IDE

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

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

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

16 Повторное использование кода: не изобретая колесо

Когда великий человек воздерживается от действий, его сила чувствуется за тысячу миль.

—Тао Ти Чинг (популярный неправильный перевод)

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

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

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

Генри Спенсер.

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

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

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

16.1. История случайного новичка

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

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

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

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

Ньюби слегка озабочен датой завершения проекта. Ему может не хватать опыта, но он прочел Dilbert и слышал несколько "боевых" историй от опытных программистов. Ньюби знает, что менеджеры имеют склонность к тому, что можно иносказательно назвать "агрессивным" расписанием. Возможно, он прочел "Death March" Эда Йордона (Ed Yourdon) [91], который в 1996 году отмечал, что большинство проектов слишком, по крайней мере, на 50% лимитированы в отношении времени выполнения и ресурсов, и что этот лимит усугубляется.

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

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

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

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

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

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

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

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

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

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

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

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

Так программисты сталкиваются с повторным использованием кода (и другими хорошими практическими приемами, которые ему сопутствуют, например, модульностью и прозрачностью), которое извне систематически обусловливается комбинацией технических проблем, барьеров интеллектуальной собственности, политических и личных потребностей. "Умножьте" Дж. Рэндома Ньюби на сто тысяч, "состарьте" его на пару десятков лет и дайте ему стать более циничным и привыкшим к данной системе. В результате получится описание большей части индустрии программного обеспечения, рецепт огромных потерь времени, средств и человеческого мастерства — даже если не учитывать приемы маркетинговой тактики поставщиков, некомпетентный менеджмент, нереальные сроки завершения и все остальные факторы, которые усложняют качественное выполнение работы.

Профессиональная культура, происходящая из опыта Ньюби, ярко отражает этот опыт. Предприятия, разрабатывающие программное обеспечение, получат сильный NIH-комплекс (Not Invented Here — изобретено не здесь). Они будут упорно противостоять повторному использованию кода, навязывая своим программистам в целях соблюдения жестких проектных рамок неадекватные, но интенсивно продаваемые поставщиками компоненты, отвергая при этом повторное использование своего же протестированного кода. Они будут создавать множество узкоспециальных, дублируемых программ. Программисты, создающие эти программы, будут знать, что результатом будет свалка программ, но покорно смирятся с невозможностью исправить что-либо, кроме отдельных частей, созданных самостоятельно.

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

16.2. Прозрачность — ключ к повторному использованию кода

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

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

В основе большинства неприятностей Дж. Рэндома Ньюби (и крупномасштабных проблем качества, которые они подразумевают) лежит прозрачность — или, вернее, ее недостаток. Невозможно устранить проблему в коде, который невозможно посмотреть изнутри. Фактически в любой программе с нетривиальным API-интерфейсом невозможно даже должным образом использовать то, что нельзя увидеть изнутри. Документация неадекватна не только на практике, но и в принципе. Она не способна передать все нюансы, реализованные в коде.

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

Существует еще одна жизненно важная необходимость в исходном коде. Урок, который Unix-программисты изучали в течение десятилетий, заключается в том, что исходный код выдерживает испытание временем, а объектный — нет. Изменяются аппаратные платформы и служебные компоненты, такие как библиотеки поддержки, в операционных системах появляются новые API-интерфейсы и устаревают прежние. Меняется все, а непроницаемые двоичные исполняемые модули не способны адаптироваться к изменениям. Они хрупки, невозможно надежно обеспечить их дальнейшую переносимость, а кроме того, их необходимо поддерживать с помощью расширяющихся и подверженных ошибкам уровней кода эмуляции. Они замыкают пользователей в рамках предположений разработчиков. Исходный код необходим, ведь даже если нет ни намерений, ни необходимости изменять программу, в будущем для обеспечения работоспособности программы ее придется перекомпилировать в новых средах.

Важность прозрачности и проблема устаревания кода являются теми причинами, по которым следует требовать открытости повторно используемого кода для проверки и модификации[135]. Это лишь предварительный аргумент в пользу того, что сейчас называется "открытым исходным кодом". Понятие "открытый исходный код" имеет более важный смысл, чем просто требование прозрачности и видимости кода.

16.3. От повторного использования к открытому исходному коду

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

В настоящее время открытый исходный код снова является одним из наиболее мощных инструментов в коллекции любого Unix-программиста.

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

Ранее в настоящей книге историческое описание было подано в контексте причинных и культурных связей между Unix и открытым исходным кодом. Организация и тактика разработки открытого исходного кода рассматривается в главе 19. При обсуждении теоретических и практических вопросов повторного использования полезно рассматривать открытый исходный код отдельно, как непосредственную реакцию на проблемы, драматизированные в истории о Дж. Рэндома Ньюби.

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

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

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

Таким образом, с конца 90-х годов прошлого века более нет никакого смысла рекомендовать стратегию и тактику повторного использования кода без обсуждения открытого исходного кода, соответствующих практических приемов, вариантов лицензирования и норм сообщества открытого исходного кода. Даже если данные проблемы в других сообществах могли бы быть обособленными, в мире Unix они связаны неразрывно.

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

16.4. Оценка проектов с открытым исходным кодом

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

./configure; make; make install;
для выполнения инсталляционной части обычно требуются привилегии администратора.

Люди за пределами мира Unix (особенно нетехнические пользователи) склонны рассматривать программное обеспечение с открытым исходным кодом (или "свободное") как непременно худшее по сравнению с коммерческим, т.е. как низкокачественное, ненадежное и вызывающее больше проблем, чем способно решить. Они упускают из виду важный момент: в общем, программное обеспечение с открытым исходным кодом пишется людьми, которым оно не безразлично, которые сами его используют, и, опубликовывая его, рискуют своей личной репутацией. Они также склонны тратить меньше времени на совещания, давние изменения конструкции и бюрократические издержки. Следовательно, они сильнее мотивированы и лучше нацелены на выполнение качественной работы, чем оплачиваемые невольники, для которых главное — соблюсти невозможные сроки завершения проекта в частных предприятиях.

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

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

Читателю, который не является Unix-программистом, может быть трудно поверить в это. Еще один аргумент: в современных Unix-системах C-компилятор почти неизменно является программой с открытым исходным кодом. Коллекция GNU-компиляторов (GNU Compiler Collection — GCC) Фонда свободного программного обеспечения настолько мощная, хорошо документированная и надежная, что рынка для коммерческих Unix-компиляторов фактически не осталось, а для Unix-поставщиков стало нормой создавать версии GCC для своих платформ, не разрабатывая собственных компиляторов.

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

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

Хорошим знаком является также наличие Web-страницы программы, списка часто задаваемых вопросов (Frequently Asked Questions — FAQ) и связанного списка почтовой рассылки или группы новостей Usenet. Все это подтверждает, что вокруг данной программы формируется настоящее сообщество заинтересованных пользователей. Недавние обновления Web-страниц и обширный список зеркал также являются надежными признаками проекта с жизнеспособным пользовательским сообществом. Бесполезные пакеты просто не получат такого длительного вклада, поскольку не способны его оправдать.

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

Информацию о высококачественных программах с открытым исходным кодом можно найти на следующих Web-страницах.

• GIMP ;

• GNOME ;

• KDE ;

• Python ;

• Ядро Linux ;

• PostgreSQL ;

• XFree86 ;

• InfoZip .

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

16.5. Поиск открытого исходного кода

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

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

man -k
с несколькими ключевыми словами часто дает полезные результаты.

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

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

Архивы Linux на сайте ibiblio до появления SourceForge были крупнейшими в мире. Архивы ibiblio являются пассивным хранилищем, т.е. просто местом для публикации пакетов. Однако они имеют лучший интерфейс к World Wide Web, чем большинство пассивных сайтов (программа, создающая вид и восприятие данного проекта в Web рассматривалась в качестве одного из учебных примеров при обсуждении Perl в главе 14). Данный сайт также является основным для проекта Linux Documentation Project, в рамках которого поддерживаются множество документов, являющихся замечательными ресурсами для Unix-пользователей и разработчиков.

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

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

Архив CPAN — центральный репозиторий полезного бесплатного кода на Perl. Он доступен с домашней страницы проекта Perl .

Сообщество Python-программистов (Python Software Activity) создает архив Python-программ и документации, доступной на домашней странице проекта Python, .

Множество Java-аплетов и ссылок на другие сайты, содержащие бесплатные Java-программы, доступны на странице Java-аплетов .

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

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

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

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

16.6. Вопросы использования программ с открытым исходным кодом

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

Документация часто является более серьезным вопросом. Многие высококачественные пакеты с открытым исходным кодом, ввиду своей слабой документированности, гораздо менее полезны, чем хотелось бы. Традиции Unix строго определяют стиль документации, в соответствии с которым (несмотря на то, что она может технически охватить все функции пакета) предполагается, что читатель хорошо знаком с проблемной областью и читает очень внимательно. Для этого есть весомые причины, которые обсуждаются в главе 18, однако данный стиль может создавать некоторые препятствия. К счастью, извлечение из документации ценной информации относится к развиваемым навыкам.

Рекомендуется использовать Web-поиск по фразам, включающим в себя название программного пакета или тематические ключевые слова, а также строку "HOWTO" или "FAQ". Такие запросы часто позволяют найти более полезную для новичков информацию, чем man-страницы.

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

16.7. Вопросы лицензирования

Любое произведение, которое не является общедоступным, охраняется авторским правом, а возможно, даже не одним.

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

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

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

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

16.7.1. Что определяется как открытый исходный код

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

Определение открытого исходного кода (Open Source Definition) является результатом долгих размышлений о том, что делает программное обеспечение "открытым" (open source) или (в прежней терминологии) "свободным" (free). Оно широко принимается в сообществе открытого исходного кода как озвучивание общественной договоренности среди разработчиков открытого исходного кода. Его ограничения относительно лицензирования предполагают выполнение следующих требований:

• гарантия неограниченного права копирования;

• гарантия неограниченного права на воспроизведение в неизменной форме;

• гарантия неограниченного права на модификацию для персонального использования.

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

OSD — легальное определение сертификационного знака "OSI-сертифицированного открытого исходного кода" (OSI Certified Open Source), а также лучшее из когда-либо созданных определений "свободного программного обеспечения". Все стандартные лицензии (MIT, BSD, Artistic, GPL/LGPL и MPL) соответствуют данному определению (хотя некоторые, такие как GPL, имеют другие ограничения, в условиях которых следует разобраться, прежде чем принимать их).

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

Статья 5 данного документа была написана после нескольких лет болезненных экспериментов. Лицензии на некоммерческое использование столкнулись с проблемой отсутствия четко сформулированного правового теста для определения того, какой вид воспроизведения квалифицируется как "коммерческий". Безусловно, так квалифицируется продажа программы как продукта. А если программа распространяется с нулевой номинальной ценой совместно с другой программой или данными, а цена "ложится грузом" на всю коллекцию? Что изменится, если данная программа существенна для работы всей коллекции?

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

Это беспокойство не является гипотетическим. Например, очень важной частью цепи распространения в сообществе открытого исходного кода являются CD-ROM-дистрибьюторы, которые собирают программы в полезные коллекции, начиная от простых сборников до загружаемых операционных систем. Должны быть запрещены ограничения, которые чрезмерно усложнили бы жизнь CD-ROM-дистрибьюторов или других дистрибьюторов, пытающихся распространять программное обеспечение с открытым исходным кодом на коммерческой основе.

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

16.7.2. Стандартные лицензии на открытый исходный код

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

MIT

Лицензия MIT или Консорциума X (MIT X Consortium License— подобна лицензии BSD, но без рекламных требований).

BSD

Авторское право членов правления Калифорнийского университета в Беркли (используется для BSD-кода).

Artistic License (Артистическая лицензия)

Те же условия, что и в Артистической лицензии Perl (Perl Artistic License).

GPL

Общедоступная лицензия GNU (GNU General Public License).

LGPL

"Library" (Библиотечная) или "Lesser" (Облегченная) GPL-лицензия.

MPL

Общественная лицензия Mozilla (Mozilla Public License).

Данные лицензии более подробно (с точки зрения разработчика) обсуждаются в главе 19. В данной главе достаточно подчеркнуть, что единственное важное отличие между ними заключается в том, что лицензии могут быть переходящими и непереходящими. Лицензия является переходящей (infectious) в случае, если она требует, чтобы любая производная работа лицензионной программы также попадала под условия данной лицензии.

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

GPL является одновременно самой распространенной и самой спорной переходящей лицензией. В ней есть статья 2(b), которая требует, чтобы любая производная работа GPL-программы сама была лицензирована на условиях GPL, что вызывает разногласия. (К некоторым разногласиям привела статья 3(b), требующая, чтобы обладатели лицензий предоставляли по требованию исходный код на физических носителях, однако взрывной рост Internet сделал публикацию архивов исходного кода, как того требует пункт 3(a) лицензии, настолько дешевой, что никто более не заботится о требованиях опубликования.)

Никто не может точно сказать, что подразумевается под словами "содержит или является производной из..." в статье 2(b), или какие виды использования стоят за формулировкой "простое агрегирование" несколькими параграфами ниже. Спорные вопросы касаются компоновки библиотек и включения GPL-лицензированных файлов заголовков. Часть проблемы состоит в том, что законы США, касающиеся авторского права, не определяют понятия производной; эта задача оставлена судам для создания определений прецедентного права, а компьютерные программы являются той областью, в которой данный процесс начался совсем недавно.

С одной стороны, "простое агрегирование" определенно делает безопасным поставку GPL-программы на одном носителе с разработанным коммерческим кодом при условии, что они не связаны друг с другом или не вызывают друг друга. Они даже могут быть инструментами, оперирующими с одинаковыми файловыми форматами или дисковыми структурами; данная ситуация, согласно закону об авторском праве, не сделала бы одну программу производной от другой.

С другой стороны, внедрение GPL-кода в разрабатываемый коммерческий код или связывание объектного GPL-кода с коммерческим определенно делает последний производным продуктом и требует его лицензирования на условиях GPL.

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

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

Некоторые считают, что формулировка статьи 2(b) умышленно направлена на "инфицирование" каждой части любой коммерческой программы, использующей даже небольшой фрагмент GPL-лицензированного кода. Они называют данную лицензию GPV (General Public Virus — общедоступный вирус). Другие считают, что формулировка "простое агрегирование" скрывает все недостатки "смеси" GPL и не-GPL-кода в одной компиляции или загрузочном модуле.

Эта неопределенность вызвала немалое возмущение в сообществе открытого исходного кода, и FSF пришлось разработать специальную, несколько менее жесткую версию "Library GPL" (которая затем была переименована в "Lesser GPL"), чтобы заверить людей в том, что они могут продолжать использовать динамические библиотеки, поставляемые с коллекцией GNU-компиляторов.

Разработчику приходится выбирать собственную интерпретацию статьи 2(b); большинство юристов не понимают связанных с ней технических вопросов, а прецедентного права не существует. Что касается практического опыта, FSF никогда (с момента своего основания в 1984 году и, по крайней мере, до середины 2003 года) не преследовал никого в судебном порядке за нарушение GPL, однако Фонд успешно во всех известных случаях усилил GPL угрозой судебного преследования. Известно также, что Netscape включает исходный и объектный код GPL-программы с коммерческим дистрибутивом браузера Netscape Navigator.

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

16.7.3. Когда потребуется адвокат

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

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

Правда, адвокаты и судьи запутываются в формулировках лицензий больше, чем разработчики. Законодательство о правах на программное обеспечение туманное, а прецедентного права по лицензиям на открытый исходный код (к середине 2003 года) не существовало; никто никогда не преследовался за нарушение данных лицензий.

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

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

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

Загрузка...