Искусство программирования для Unix.

17. Переносимость: переносимость программ и соблюдение стандартов.

Осознание того, что операционные системы целевых машин были настолько же большим препятствием для переносимости, насколько их аппаратная архитектура, привело нас к радикальному предложению: избежать этой проблемы путем переноса самой операционной системы. Переносимость С-программ и операционной системы Unix (Portability of С Programs and the UNIX System, 1978) –Стив Джонсон, Деннис Ритчи

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

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

Unix-программисты знают по опыту, что очень легко ошибиться, предрекая короткую жизнь программному проекту[136]. Поэтому они склонны избегать создания программного обеспечения, зависимого от специфических и недолговечных технологий, и в большой степени полагаются на открытые стандарты. Привычка писать программы с учетом переносимости уже стала одной из традиций Unix, так что теперь она относится даже к мелким проектам, которые задумывались как однократно используемый код. Она оказала влияние на всю конструкцию инструментария Unix-разработчика, а также на языки программирования, такие как Perl, Python и Tcl, которые создавались в Unix-среде.

Непосредственный выигрыш от переносимости заключается в том, что для Unix-программ является нормой пережить свою исходную аппаратную платформу, поэтому не требуется каждые несколько лет заново изобретать инструменты и приложения. Сегодня приложения, первоначально написанные для Version 7 Unix (1979), регулярно используются не только на Unix-системах, "генетически" происходящих от V7, но и на таких вариантах системы, как Linux, в которой API-интерфейс операционной системы был написан на основании Unix-спецификации и не содержит кода, заимствованного у Bell Labs.

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

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

17.1. Эволюция С.

Главным фактом практики Unix-программирования всегда была стабильность языка С и небольшого количества служебных интерфейсов, которые всегда ему сопутствовали (особенно стандартная I/O-библиотека и подобные ей). Тот факт, что язык, созданный в 1973 году, в течение тридцати лет интенсивного использования потребовал небольших изменений, является действительно примечательным, в этом отношении язык С не имеет аналогов в компьютерной науке.

В главе 4 приводились аргументы в пользу того, что С достиг успеха, благодаря тому, что он выполняет роль тонкого связующего уровня над аппаратным обеспечением компьютера, приближающимся к "стандартной архитектуре" [4]. Однако для того чтобы понять другие факторы, необходимо кратко рассмотреть историю данного языка.

17.1.1. Ранняя история С.

Язык С родился в 1971 году как язык системного программирования для PDP-11-варианта Unix и основывался на раннем интерпретаторе языка В, разработанного Кеном Томпсоном. Язык В, в свою очередь, был смоделирован с базового языка общего программирования (Basic Common Programming Language — BCPL), разработанного в Кембриджском университете в 1966–1967 годах[137].

Первоначальный C-компилятор Денниса Ритчи (который часто называли "DMR" по инициалам его создателя) обслуживал сообщество, быстро разрастающееся вокруг операционной системы Unix версий 5, 6 и 7. От шестой версии С языка произошла версия С компании Whitesmiths, повторная реализация, которая стала первым коммерческим C-компилятором и ядром IDRIS, первого клона Unix. Однако самые современные реализации С смоделированы на основе "переносимого C-компилятора" (Portable С Compiler — PCC) Стивена Джонсона (Steven С. Johnson), который дебютировал в седьмой версии и полностью заменил компилятор DMR как в ветви System V, так и в BSD-версиях четвертого поколения.

В 1976 году в шестой версии С были представлены объявления typedef, union и unsigned int. Также подверглись изменениям утвержденный синтаксис для инициализации переменных и некоторые составные операторы.

Оригинальным описанием языка С была книга Брайана Кернигана и Денниса Ритчи "The С Programming Language", которую также называли "Белой книгой" [42]. Она была опубликована в 1978 году, в том же году появился C-компилятор Whitemiths.

Белая книга описывала усовершенствованную шестую версию языка С с одним значительным исключением, которое касалось обработки общедоступных переменных. Первоначально Ритчи намеревался смоделировать правила С с COMMON- объявлений языка FORTRAN, основываясь на теоретическом соображении, что любая машина, способная поддерживать FORTRAN, будет готова к использованию С. В модели с общим блоком общедоступная переменная может быть объявлена несколько раз; идентичные объявления объединяются компоновщиком. Однако два ранних варианта С (для мэйнфреймов Honeywell и IBM 360) работали на машинах с очень ограниченной общей памятью или примитивным компоновщиком, или имели оба недостатка. Таким образом, компилятор Version 6 С был перенесен в боле строгую модель определения (которая требовала максимум одно определение любой заданной общедоступной переменной и ключевого слова extern, задающего на нее ссылки), описанную в [42].

Это решение было отменено в C-компиляторе, который поставлялся с Version 7, после выяснения того, что большое количество существующего исходного кода зависело от более свободных правил. Под давлением обратной совместимости закончилась неудачей еще одна попытка перестроиться (в версии System V Release 1 1983 года), перед тем как в 1988 году в проекте стандарта ANSI (ANSI Draft Standard) окончательно установились правила определения. Общий блок внешних переменных до сих пор является общепризнанным отклонением от стандарта.

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

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

Версия System III С компилятора PCC (которая также поставлялась с BSD 4.1с) изменила обработку объявлений struct так, чтобы члены с одинаковыми именами в различных структурах не конфликтовали. Также были внесены объявления void и unsigned char. Область действия extern-объявлений локальных для функций была ограничена данной функцией и более не включала всего следующего за ней кода.

В проекте стандарта ANSI С (ANSI С Draft Proposed Standard) были добавлены определения const (для памяти только для чтения) и volatile (для таких ячеек, как отображаемые на память регистры ввода/вывода, которые можно модифицировать асинхронно из потока управления программы). В целях применения к любому типу был обобщен модификатор типа unsigned, а также добавлен идентификатор signed. Был добавлен синтаксис для auto-массива, инициализаторов структур, а также типов union. Наиболее важно то, что были добавлены прототипы функций.

Самыми важными изменениями в раннем С был переход к определению и введение прототипов функций в проекте стандарта ANSI С. Язык по существу остается стабильным с тех пор, как в 1985 - 1986 годах копии рабочих документов комитета X3J11 по проекту стандарта довели намерения комитета до сведения разработчиков компиляторов.

Более подробно история раннего С описана создателем языка в статье [70].

17.1.2. Стандарты С.

Разработка стандартов С была консервативным процессом, в котором серьезное внимание уделялось "сохранению духа" оригинального С, а акцент был сделан скорее на утверждении экспериментов в существующих компиляторах, чем на создании новых функций. Документ-хартия C9X (C9X charter)[138] является превосходным выражением данной миссии.

Работа над первым официальным стандартом С началась в 1983 году при содействии комитета X3J11 ANSI. Главные функциональные дополнения к языку были утверждены к концу 1986 года, и с этого момента программисты стали различать "K&R С" и "ANSI С".

Многие не осознают, насколько необычным, особенно оригинальная работа ANSI С, был проект стандартизации языка в своем упорном требовании стандартизировать только проверенные функции. Большинство комитетов по стандартизации языков тратят большую часть времени на создание новых функций, зачастую уделяя мало внимания их возможной реализации. Фактически несколько функций ANSI С, которые были созданы с нуля, например, печально известные "триграфы" (trigraphs), были самыми непопулярными и наименее успешными функциями С89. Генри Спенсер. Пустые указатели были созданы как часть работы по стандартизации и стали успешными. Но точку зрения Генри принимают до сих пор. Стив Джонсон.

Несмотря на то, что основа ANSI С была согласована достаточно рано, споры о содержимом стандартных библиотек продолжались в течение нескольких лет. Формальный стандарт не был опубликован до конца 1989 года, сразу после этого в большинстве компиляторов были реализованы рекомендации 1985 года. Данный стандарт первоначально назывался ANSI Х3.159, но был переименован в ISO/IEC 9899:1990, когда Международная организация по стандартизации (International Standards Organization — ISO) приняла на себя спонсорские обязательства. Описываемый в стандарте вариант языка обычно называется C89 или C90.

Первая книга по С и практической реализации переносимости, "Portable С and Unix Systems Programming" [44], была опубликована в 1987 году. (Я написал ее под корпоративным псевдонимом, навязанным мне моими тогдашними работодателями.) Второе издание книги Кернигана и Ритчи [42] вышло в 1988 году.

Очень незначительная модификация C89, названная Amendment 1 (поправка 1), AM1, или C93, появилась в 1993 году. В ней было добавлено больше возможностей по поддержке широких символов и Unicode. Данный стандарт получил название ISO/IEC 9899-1:1994.

Пересмотр стандарта C89 начался в 1993 году. В 1999 году организация ISO приняла стандарт ISO/IEC 9899 (обычно называемый C99). Он включал в себя Amendment 1 и большое количество мелких функций. Возможно, наиболее значительной из них для большинства программистов является возможность объявлять переменные в любом месте блока, а не только в начале (аналогичная языку С++). Кроме того, в стандарте были добавлены макросы с переменным числом аргументов.

Рабочая группа C9X имеет Web-страницу <http://anubis.dkuug.dk/JTC1/SC22/WG14/www/рrоjесts>, но к середине 2003 года планов на третий проект стандарта не было. Члены рабочей группы разрабатывают дополнения к С для встроенных систем.

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

17.2. Стандарты Unix.

Тот факт, что в 1973 году Unix была переписана на С, беспрецедентно упростил переносимость и модификацию операционной системы. В результате исходная Unix вскоре разделилась на семейство операционных систем. Unix-стандарты первоначально разрабатывались в целях согласования API-интерфейсов различных ветвей фамильного дерева.

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

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

17.2.1. Стандарты и Unix-войны.

Первоначальным мотивом для разработки Unix-стандартов было расхождение линий разработки AT&T и Университета Беркли, которое рассматривалось в главе 7.

Unix-системы 4.x BSD происходили от Version 7, вышедшей в 1979 году. После выхода 4.1 BSD в 1980 году BSD-линия быстро приобрела репутацию революционной Unix-системы. В число важных дополнений входили визуальный редактор vi, средства контроля заданий для управления с одной консоли многочисленными высокоприоритетными и фоновыми задачами, а также усовершенствование сигналов (см. главу 7). Несравненно более важным дополнением была поддержка TCP/IP-сетей, но хотя Университет Беркли получил контракт на их реализацию в 1980 году, протокол TCP/IP в течение трех лет не включался в состав внешних версий.

Однако другая версия, System III, появившаяся в 1981 году, стала основой дальнейшей разработки в корпорации AT&T. В System III интерфейс терминалов версии 7 был переработан в более четкую и изящную форму, которая была абсолютно несовместимой с усовершенствованиями Беркли. В ней сохранялась (необратимая) семантика сигналов (см. главу 7). В состав январской версии System V Release 1 в 1983 году вошли некоторые BSD-утилиты (такие как vi(1)).

Первая попытка заполнить разрыв была предпринята в феврале 1983 года влиятельной группой Unix-пользователей, UniForum. Вышедший в 1983 году проект стандарта Uniforum (Uniforum 1983 Draft Standard — UDS 83) описывал "основную Unix систему", состоящую из подмножества ядра System III и библиотек плюс примитив блокировки файлов. AT&T объявила о поддержке UDS 83, однако данный стандарт представлял собой неадекватный набор разрабатываемых практических приемов 4.1BSD. Проблема обострилась с выходом в июле 1983 года 4.2BSD, в которой было добавлено множество новых функций (включая TCP/IP-сети) и введена некоторая неочевидная несовместимость с исходной версией 7.

Лишение прав компаний Bell в 1984 году и начало Unix-войн (см. главу 2) значительно усложнило проблемы. Sun Microsystems лидировала в индустрии рабочих станций в BSD-направлении; AT&T пыталась внедриться в компьютерный бизнес и использовать контроль над Unix как стратегическое оружие, даже продолжая лицензировать операционную систему таким конкурентам, как Sun. Все поставщики принимали бизнес-решения дифференцировать свои версии Unix для получения конкурентного преимущества.

В ходе Unix-войн техническая стандартизация стала точкой зрения, которую отстаивали сотрудничающие технические специалисты, а большинство менеджеров по продуктам принимали ее неохотно или активно сопротивлялись. Одним крупным и важным исключением была корпорация AT&T, которая, анонсируя в январе 1984 года System V Release 2 (SVr2), объявила о намерении сотрудничать с группами пользователей по формированию стандартов. Второй пересмотр проекта стандарта UniForum в 1984 году проложил путь и одновременно повлиял на API SVr2. Позднее Unix-стандарты также стремились следовать System V, кроме тех областей, где BSD-средства имели очевидное функциональное превосходство (поэтому, например, современные стандарты Unix описывают средства управления терминалами System V, вместо BSD-интерфейса к тем же средствам).

В 1985 году AT&T опубликовала документ System V Interface Definition (SVID), в котором содержалось более формальное описание SVr2 API, включающего стандарт UDS 84. Более поздние модификации SVID2 и SVID3 наметили курс на создание интерфейсов System V версий 3 и 4. Документ SVID стал основой POSIX-стандартов, которые в конечном итоге склонили большую часть спора Беркли/АТ&Т по поводу системных и библиотечных вызовов С в сторону подхода AT&T.

Однако это не стало очевидным в течение еще нескольких лет. Тем временем Unix-войны были в разгаре. Например, в 1985 году вышли два конкурирующих API-стандарта для файловой системы с совместным доступом по сети: Network File System (NFS) корпорации Sun и АТ&Т-система Remote File System (RFS). NFS-система выиграла, поскольку Sun была готова разделить с другими не только спецификации, но и открытый исходный код.

Это был настоящий успех, потому что по своему логическому построению система RFS была все-таки лучшей моделью. Она поддерживала лучшую семантику блокировки файлов и лучшую идентификацию пользователей в различных системах и, в отличие от NFS, как правило, была направлена на точное получение более мелких деталей семантики файловой системы Unix. Однако данный урок был проигнорирован, даже когда в 1987 году его повторила система X Window с открытым исходным кодом, победив частную систему Sun Networked Window System (NeWS).

После 1985 года главные вопросы по Unix-стандартизации решались Институтом инженеров электротехники и электроники (Institute of Electrical and Electronic Engineers — IEEE). Комитет 1003 IEEE разработал серию стандартов, которые широко известны как POSIX[139]. Данные стандарты выходили за рамки простых системных вызовов и библиотечных средств С. Они определяли подробную семантику оболочки и минимальный набор команд, а также детальные привязки для различных языков программирования, отличных от С. Первая версия появилась в 1990 году, а вторая редакция была опубликована в 1996 году. Международная организация по стандартизации (International Standards Organization — ISO) приняла данные стандарты под названием ISO/IEC 9945.

Ниже перечислены некоторые из ключевых POSIX-стандартов.

1003.1 (опубликован в 1990 году).

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

1003.2 (опубликован в 1992 году).

Стандартная оболочка и утилиты. Семантика оболочки строго повторяет семантику Bourne shell в System V.

1003.4 (опубликован в 1993 году).

Unix-система реального времени. Бинарные семафоры, блокировка памяти процесса, файлы с распределением памяти, приоритетное расписание, сигналы реального времени, циклы и таймеры, передача IPC-сообщений, синхронизированный I/O, асинхронный I/O, файлы реального времени.

Во втором издании 1996 года стандарт 1003.4 был разделен на 1003.1b (система реального времени) и 1003.1с (параллельные процессы).

Несмотря на то, что некоторые ключевые области, такие как семантика обработки сигналов и упущение BSD-сокетов, были недоопределены, первоначальные POSIX-стандарты стали основой всей последующей работы по стандартизации Unix. Они до сих пор цитируются как авторитетные, хотя и косвенно, посредством таких справочников, как "POSIX Programmer's Guide" [47]. Де-факто стандарт Unix API до сих пор остается "POSIX плюс сокеты", более поздние стандарты, главным образом, добавляют функции и более точно определяют согласование в необычных крайних случаях.

Следующим игроком на этом поле была группа X/Open (позднее переименованная в Open Group), консорциум поставщиков Unix, сформированный в 1984 году. Стандарты данной организации X/Open Portability Guides (XPGs) первоначально развивались параллельно с проектами POSIX, а затем после 1990 года стандарты XPGs были включены в POSIX и расширили его. В отличие от POSIX, который представлял собой попытку сбора надежного подмножества из всех Unix-систем, стандарты XPGs больше склонялись к общей практике в наиболее развитом участке исследований; даже XPG1 в 1985 году, охватывающий системы SVr2 и 4.2BSD, включал в себя сокеты.

В 1987 году в стандарт XPG2 был добавлен API-интерфейс поддержки терминалов, которым, по существу, была библиотека curses(3) системы System V. XPG3 в 1990 году влился в X11 API. XPG4 в 1992 году полностью утвердил полное соответствие стандарту 1989 года ANSI С. В стандартах XPG2, 3 и 4 интенсивно рассматривалась поддержка интернационализации и описывался сложный API для обработки наборов кодов и каталогов сообщений.

В материалах по стандартам Unix могут встретиться ссылки на стандарты "Spec 1170" (1993 года), "Unix 95" (1995 года) и "Unix 98" (1998 года). Это были сертификационные знаки стандартов X/Open, и в настоящее время они представляют только исторический интерес. Однако работа над стандартом XPG4 превратилась в спецификацию Spec 1170, которая стала первой версией Единой спецификации Unix (Single Unix Specification, SUS).

В 1993 году 75 поставщиков систем и программного обеспечения, включая все главные Unix-компании, поставили окончательную точку в Unix-войнах, когда объявили о финансовой поддержке X/Open в разработке общего определения Unix. Как часть данного соглашения X/Open приобрела права на торговую марку Unix. Объединенный стандарт стал называться Single Unix Standard (Единый стандарт Unix) версии 1. Версия 2 вышла в 1997 году. В 1999 году деятельность по стандарту POSIX перешла к X/Open.

В 2001 году группа X/Open (сейчас The Open Group) выпустил Единый стандарт Unix версии 3 (Single Unix Standard version 3) <http://www.Unix.org/version3/>. Все "потоки" стандартизации Unix API были, наконец, объединены в одно целое. Различные вариации Unix воссоединились на основе общего API. И это было встречено с большим энтузиазмом, по крайней мере, среди давних поклонников Unix, которые помнили о бурях 80-х годов.

17.2.2. Влияние новых Unix-систем.

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

Тестирование соответствия, необходимое для проверки соответствия Единой спецификации Unix, — дорогая проблема. Оно должно осуществляться для каждого дистрибутива, но не учитывает многообразия дистрибьюторов операционных систем с открытым исходным кодом. В любом случае, Linux изменяется столь быстро, что любая версия дистрибутива, вероятно, устарела бы к моменту окончания сертификации[140].

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

17.2.3. Стандарты Unix в мире открытого исходного кода.

В середине 1990-х годов сообщество открытого исходного кода начало собственную работу по стандартизации. Эти усилия основывались на совместимости на уровне кода, закрепленной стандартом POSIX и его потомками. В частности Linux, была написана с нуля способом, который зависел от доступности таких стандартов Unix API, как POSIX[141].

В 1998 году компания Oracle перенесла на Linux свой лидирующий на рынке баз данных продукт, этот шаг справедливо рассматривается как главный прорыв в движении принятия Linux. Ответственный инженер проекта, в подтверждение того, что API-стандарты выполнили свою задачу, на вопрос репортера о том, какие технические трудности пришлось преодолеть Oracle ответил: "Мы вводили make".

Таким образом, проблемой для Unix-систем новой школы была не API-совместимость на уровне исходного кода. Все принимают как должное возможность переносить исходный код между различными Linux, BSD и коммерческими дистрибутивами Unix без необходимости тратить серьезные усилия на обеспечение переносимости кода. Новой проблемой была не совместимость исходного кода, а бинарная совместимость. Почва под Unix неожиданно зашаталась вследствие массового спроса на аппаратное обеспечение PC.

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

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

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

Первым проектом по стандартизации, который развился внутри нового сообщества Linux и BSD (начиная с 1993 года) был Стандарт иерархии файловой системы (Filesystem Hierarchy Standard — FHS). Он был включен в состав Базы стандартов Linux (Linux Standards Base — LSB), которая также стандартизировала ожидаемый набор служебных библиотек и вспомогательных приложений. Оба стандарта стали результатом деятельности Группы свободных стандартов (Free Standards Group) <http://www.freestandards.org/>, которая к 2001 году стала играть роль аналогичную позиции консорциума X/Open среди Unix-поставщиков старой школы.

17.3. IETF и процесс RFC-стандартизации.

Когда Unix-сообщество соединилось с культурой Internet-инженеров, в него также проникло мышление, сформированное процессом RFC-стандартизации IETF (Internet Engineering Task Force — Инженерная группа по решению конкретной задачи в Internet). Согласно традиции IETF, стандарты должны возникать из опыта с работающим прототипом, но как только они становятся стандартами, код, который им не соответствует, считается нерабочим и безжалостно уничтожается.

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

Одним из самых печально известных примеров абсурдной стандартизации были сетевые протоколы Открытого взаимодействия систем (Open Systems Interconnect, OSI), которые недолго конкурировали с TCP/IP в 1980-х годах — семиуровневая модель на расстоянии выглядела изящной, но оказалась чрезмерно сложной и нереализуемой на практике[142]. Другим широко известным негативным примером является стандарт ANSI Х3.64 для средств видеотерминалов, испорченный едва различимой несовместимостью между юридически согласованными реализациями. Даже после того, как символьные терминалы почти были вытеснены растровыми дисплеями, несовместимость продолжала создавать проблемы (в частности, именно поэтому функциональные и специальные клавиши в программе xterm(1) иногда нарушают ее работу). Стандарт RS232 для последовательных соединений был настолько недоопределенным, что иногда казалось, будто не существует двух одинаковых последовательных кабелей. Для описания всех подобных отрицательных примеров стандартов, возможно, потребовалась бы книга такого же объема.

Существует известная фраза, которая обобщает философию IETF: "Мы отвергаем королей, президентов и голосование. Мы верим в суровое единодушие и работающий код"[143]. Данное требование существования работающей реализации предохранило ее от грубейших ошибок. В действительности критерий еще строже.

Прежде чем проектная спецификация будет принята в качестве Internet-стандарта, она должна быть реализована и протестирована несколькими независимыми сторонами на способность корректно работать и взаимодействовать, а также использоваться в еще более требовательных средах. Процесс Internet-стандартизации (The Internet Standards Process) — третья редакция (RFC 2026).

Все IETF-стандарты проходят стадию документов RFC (Requests for Comment — запросы на комментарии). Процесс подачи RFC умышленно неформален. RFC-документы могут предлагать стандарты, результаты исследований, формулировать философские обоснования для последующих RFC-документов или даже представлять собой шутки. Появление ежегодного первоапрельского RFC является ближайшим эквивалентом соблюдения "религиозного праздника" среди Internet-хакеров, что привело к возникновению таких перлов, как "A Standard for the Transmission of IP Datagrams on Avian Carriers" (RFC 1149, стандарт для передачи IP-дейтаграмм с голубиной почтой)[144], "The Hyper Text Coffee Pot Control Protocol" (RFC 2324, гипертекстовый протокол управления кофеваркой)[145] и "The Security Flag in the IPv4 Header" (RFC3514, защитный флаг в заголовке IPv4)[146].

Однако шуточные RFC-документы — единственный вид документов, которые немедленно становятся RFC. Серьезные предложения в действительности начинают свой путь как "Internet-Drafts" (Internet-черновики), которые распространяются для публичного комментирования через IETF-каталоги на нескольких широко известных узлах. Отдельные Internet-черновики не имеют формального статуса и могут быть изменены или удалены их создателями в любое время. Черновики, которые не были отозваны, но и не получили статус RFC, удаляются по истечении шести месяцев.

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

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

Некоторые RFC-документы не продвигаются дальше этой стадии. Спецификация, которая не смогла привлечь внимание и не прошла реальных испытаний, может быть быстро позабыта, и в конечном счете маркируется RFC-редактором как "Not recommended" (не рекомендуется) или "Superseded" (отменено). Неудачные предложения принимаются как издержки процесса, и не считается зазорным быть связанным с таким предложением.

Управляющий комитет IETF (IESG или Internet Engineering Steering Group — группа технического управления Internet) отвечает за продвижение успешных RFC-документов в виде стандартов путем присвоения им грифа "Proposed Standard" (предложенный стандарт). Для того чтобы RFC квалифицировался как стандарт, спецификация должна быть стабильной, пройти экспертную оценку и привлечь значительный интерес Internet-сообщества. Опыт реализации не является абсолютно необходимым, прежде чем RFC получит обозначение Proposed Standard, но считается весьма желательным, и IESG может его потребовать, если RFC затрагивает основные протоколы или иным образом способен дестабилизировать Internet.

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

Когда существует как минимум две работающие, полные, независимо созданные и способные к взаимодействию реализации предложенного стандарта, IESG может повысить стандарт до статуса "Draft Standard" (проект стандарта). RFC 2026 гласит: "Повышение до проекта стандарта является главным повышением статуса и подтверждает уверенность в том, что данная спецификация сформировалась и будет полезной".

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

Когда проект стандарта прошел тест широко распространенной реализации и получил общее одобрение, он может быть назван Internet Standard (Стандарт Internet). Такие стандарты сохраняют свои RFC-номера и, кроме того, получают номер STD-серии. На момент написания данной книги существовало более 3 тыс. RFC и только 60 STD-стандартов.

RFC, отклоняющиеся от схемы стандартизации, могут быть обозначены как Experimental (экспериментальные), Informational (информационные; шуточные RFC тоже получают такое обозначение) или Historic (имеющие историческое значение). Последнее обозначение применимо к вышедшим из употребления стандартам. Цитата из RFC 2026: "Борцы за чистоту литературного языка предложили использовать слово Historical (исторически сложившийся); однако в данном случае "исторически сложившимся" является использование слова Historic.".

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

17.4. Спецификации — ДНК, код — РНК.

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

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

После 1980 года влияние данного урока стало более заметно в Unix-сообществе. Поэтому, несмотря на то, что стандарт ANSI/ISO С 1989 года не был абсолютно безупречным, он был исключительно четким и практичным для стандарта такого размера и важности. Единая спецификация Unix содержит "атавизмы" трех десятилетий экспериментов и фальстартов в более сложной области и, следовательно, является более беспорядочной, чем ANSI С. Однако компонентные стандарты, из которых она собрана, весьма хороши; твердым доказательством этого является тот факт, что, читая ее, Линус Торвальдс успешно создал Unix с нуля. Скромный, но мощный пример IETF создал один из критически важных блоков среды, которая сделала возможным подвиг Торвальдса.

Уважение к опубликованным стандартам и процессу IETF глубоко проникло в Unix-культуру. Умышленное нарушение Internet STD-стандартов просто невыполнимо. Это может иногда создавать пропасть взаимного непонимания между людьми с Unix-опытом и другими, склонными предполагать, что наиболее популярная или широко распространенная реализация протокола по определению является верной — даже если она нарушает стандарт так жестко, что не способна взаимодействовать с тщательно согласованным программным обеспечением.

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

Традиция Unix четко определяет, что спецификации могут иметь большую ценность, но требует, чтобы они интерпретировались как временные и постоянно пересматривались в процессе практического использования так же, как пересматриваются стандарты Internet-Drafts и Proposed Standards. В лучшей практике Unix документация на программу используется как спецификация, подлежащая пересмотру, аналогично предложенному стандарту Internet.

В отличие от других сред, в Unix-разработке документация часто пишется до программы или, по крайней мере, совместно с ней. Для X11 основные стандарты X были закончены до выхода первой версии системы X и с тех пор остались по существу неизменными. Совместимость между различными Х-системами совершенствуется далее путем скрупулезного тестирования, управляемого спецификацией. Наличие хорошо написанной спецификации значительно упростило разработку тестового комплекта для системы X. Каждое положение в X-спецификации было преобразовано в код для проверки реализации. В ходе данного процесса в спецификации было обнаружено несколько незначительных несоответствий, но результатом является тестовый комплект, который охватывает значительную часть кодовых путей внутри пробной X-библиотеки и сервера, и ни один из них не ссылается на исходный код данной реализации. Кит Паккард.

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

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

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

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

Продуманная разработка, определяемая спецификацией, допускает небольшую дискуссию об ошибках в сравнении с функциями; система, которая некорректно реализует спецификацию, неустойчива и должна быть исправлена. Полагаю, эта идея настолько проникла в большинство из нас, что мы забываем о ее силе. Мой друг, работавший для небольшой программной фирмы восточнее Белвю, удивлялся, как разработчики Linux-приложений могут получать изменения операционной системы, синхронизированные с версиями приложений. В его компании главные системные API-интерфейсы часто изменялись, для того чтобы приспособиться к капризам приложений, и поэтому важнейшая функциональность системы часто должна была обновляться наряду с каждым приложением. Я описал силу подчинения спецификациям и то, как от них зависит реализация, а затем перешел к утверждению, что приложение, которое получает неожиданный результат от документированного интерфейса, либо неверно спроектировано, либо содержит ошибку. Он нашел эту идею удивительной. Распознавание таких ошибок является просто вопросом проверки реализации интерфейса относительно спецификации. Естественно, наличие исходного кода для данной реализации несколько упрощает это. Кит Паккард.

Позиция предшествующих стандартов имеет свои преимущества также и для конечных пользователей. Тогда как уже не маленькая компания восточнее Белвю имеет проблемы, сохраняя совместимость офисного набора с его предыдущими версиями, GUI-приложения, написанные в 1988 году, до сих пор работают без изменений на нынешних реализациях системы X. В мире Unix подобное долголетие является нормой, а причиной этого является позиция "стандарт как ДНК".

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

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

17.5. Программирование, обеспечивающее переносимость.

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

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

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

17.5.1. Переносимость и выбор языка.

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

17.5.1.1. Переносимость С.

Базовый язык С в высшей степени переносим. Его стандартной реализацией в Unix является GNU C-компилятор, который повсеместно распространен не только Unix-системах с открытым исходным кодом, но и современных коммерческих вариантах операционной системы. GNU С был перенесен на Windows и классическую MacOS, но ни в одной из этих сред не стал широко распространенным, поскольку испытывает недостаток в переносимых привязках к собственным GUI-интерфейсам данных систем.

Стандартная библиотека ввода/вывода, математические подпрограммы и поддержка интернационализации переносимы во всех реализациях С. Файловый ввод/вывод, сигналы и управление процессами являются переносимыми между вариантами Unix, при условии, что используются только современные API-интерфейсы, описанные в Единой спецификации Unix; более старый C-код часто содержит нагромождения условных операторов препроцессора, направленных на достижение переносимости, но они поддерживают интерфейсы, появившиеся до стандарта POSIX, из старых коммерческих Unix-систем, которые по состоянию на 2003 год устарели или близки к этому.

Более серьезные проблемы с переносимостью С начинаются в области IPC, параллельных процессов и GUI-интерфейсов. Вопросы переносимости IPC и параллельных процессов обсуждались в главе 7. Реальную практическую проблему создают GUI-инструментарии. Большое количество GUI-инструментариев с открытым исходным кодом универсально переносятся между современными вариантами Unix, а также в Windows и классическую MacOS — Tk, wxWindows, GTK и Qt — четыре хорошо известных вида инструментария с открытым исходным кодом и документацией, которую нетрудной найти в Web. Однако ни один из них не распространяется со всеми платформами, и (по причинам более юридическим, чем техническим) ни один из них не предоставляет на всех платформах естественный вид и восприятие GUI-интерфейса. В главе 15 даны некоторые рекомендации, позволяющие справиться с данной проблемой.

По теме разработки переносимого C-кода написаны целые тома. Эта книга не входит в их число, и авторы рекомендуют внимательно изучить "Recommended С Style and Coding Standards" [11], а также главу о переносимости из книги "The Practice of Programming" [40].

17.5.1.2. Переносимость С++

Для С++ на уровне операционной системы характерны те же проблемы переносимости, что и для С, а также ряд собственных. Одной из дополнительных проблем является то, что GNU компилятор с открытым исходным кодом для С++ значительно отстает от коммерческих реализаций; поэтому к середине 2003 года не существовало универсально внедряемого эквивалента GNU С, на котором можно основывать стандарт де-факто. Более того, ни один из компиляторов С++ не реализовывает полностью ISO-стандарт C++99, хотя GNU С++ подошел к этому очень близко.

17.5.1.3. Переносимость shell.

Переносимость shell-сценариев, к сожалению, является низкой. Проблема заключается не в самой оболочке; bash(1) (Bourne Again shell с открытым исходным кодом) распространена достаточно широко, для того чтобы малоразвитые shell-сценарии могли выполняться почти в любой среде. Проблема заключается в том, что в большинстве shell-сценариев интенсивно используются другие команды и фильтры, которые являются менее переносимыми, и их присутствие на какой-либо определенной целевой машине никоим образом не гарантируется.

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

17.5.1.4. Переносимость Perl.

Perl отличается хорошей переносимостью. В стандартном варианте языка даже предоставляется переносимый набор привязок к Tk-инструментарию, который поддерживает переносимые GUI-интерфейсы в Unix, MacOS и Windows. Однако этому мешает одна проблема. Perl-сценарии часто требуют добавочных библиотек из архива CPAN (Comprehensive Perl Archive Network — полный сетевой архив Perl), которые не обязательно присутствуют в любой реализации Perl.

17.5.1.5. Переносимость Python.

Python имеет превосходную переносимость. Как и Perl, стандартный вариант Python предоставляет переносимый набор привязок к Tk-инструментарию, который поддерживает переносимые GUI-интерфейсы в Unix, MacOS и Windows.

Стандартный Python обладает более развитой стандартной библиотекой, чем Perl и не имеет эквивалента архиву CPAN, на который могли бы полагаться программисты. Вместо этого важные модули расширения регулярно встраиваются в стандартный дистрибутив Python в ходе выпуска второстепенных версий. Это заменяет пространственную проблему временной — Python менее подвержен влиянию эффекта недостающих модулей. За это приходится расплачиваться тем, что второстепенные версии Python отчасти более важны, чем главные версии Perl. На практике этот компромисс благоприятствует Python.

17.5.1.6. Переносимость Tcl.

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

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

Парадоксально, но Tcl может страдать от простоты добавления в него расширений. К тому моменту, когда определенное расширение становится интересным в качестве части стандартного дистрибутива, существует, как правило, несколько различных его версий. На симпозиуме разработчиков Tcl/Tk (Tcl/Tk Workshop 1995 года) Джон Аустерхоут (John Ousterhout) объяснил, почему в стандартном дистрибутиве Tcl отсутствует поддержка ОО-средств, так:

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

17.5.1.7. Переносимость Java.

Переносимость Java превосходна — в конце концов, основной целью создания языка был девиз "написанное однажды работает везде". Вместе с тем переносимость Java не идеальна. Трудности в основном связаны с проблемами перекоса версий между JDK 1.1 и более старым GUI-инструментарием AWT (с одной стороны) и JDK 1.2 и более новым Swing. Это обусловлено несколькими важными причинами.

• Конструкция AWT Sun была настолько неадекватной, что ее необходимо было заменить инструментарием Swing.

• Отказ Microsoft от поддержки Java-разработки на Windows и попытка заменить данный язык С#.

• Решение Microsoft удерживать поддержку аплетов в Internet Explorer на уровне JDK 1.1.

• Лицензионные положения Sun, которые делают невозможной реализацию JDK 1.2 с открытым исходным кодом, замедляя внедрение пакета (особенно в мире Linux).

Разрабатывая программы, в которых задействованы GUI-интерфейсы, Java-разработчики, ищущие переносимости, в обозримом будущем окажутся перед лицом выбора: для сохранения максимальной переносимости (включая Microsoft Windows) остановиться на JDK1.1/AWT со слабо спроектированным инструментарием или получить лучший инструментарий и средства JDK 1.2, жертвуя некоторой переносимостью.

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

17.5.1.8. Переносимость Emacs Lisp.

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

Кроме того, также весьма стабилен набор примитивов Emacs. Он достиг завершенности для задач, которые в течение многих лет возлагались на редактор (манипуляция буферами, обработка текста). Только введение системы X нарушило эту картину, и очень немногие режимы Emacs должны иметь сведения об X. Проблемы переносимости обычно являются проявлением капризов средств операционной системы на C-уровне привязок; управление подчиненными процессами в таких режимах в качестве почтовых агентов — почти единственная область, где такие проблемы проявляются с непредсказуемой частотой.

17.5.2. Обход системных зависимостей.

После выбора языка и библиотек поддержки следующим вопросом переносимости обычно является расположение ключевых системных файлов и каталогов: почтовых спулов, каталогов журнальных файлов и т.д. Прообразом данного типа проблем является то, где расположен каталог почтового спула — /var/spool/mail или /var/mail.

Часто можно избежать этого вида зависимости, отступив и воссоздав проблему. Зачем, так или иначе, открывать файл в каталоге почтового спула? Если запись в него необходима, то, возможно, лучшим решением будет просто вызов локального почтового транспортного клиента, с тем чтобы блокировка файла была выполнена верно. Если выполняется чтение из почтового каталога, то, вероятно, лучше будет обратиться к нему посредством POP3- или IМАР-сервера.

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

Если параметры расположения системных файлов должны присутствовать в коде, то наилучшая альтернатива зависит от способа распространения дистрибутива: исходный код или бинарная форма. Если распространяется исходный код, то могут помочь инструменты autoconf, которые рассматриваются в следующем разделе. В случае распространения бинарных файлов хорошей практикой будет заставить программу получать начальные параметры системы и выяснять, возможно ли автоматически настроиться на локальные условия, например, путем проверки существования каталогов /var/mail и /var/spool/mail.

17.5.3. Инструменты, обеспечивающие переносимость.

Часто для разрешения вопросов переносимости, обследования системной конфигурации и настройки make-файлов можно использовать GNU-утилиту с открытым исходным кодом autoconf(1), которая рассматривалась в главе 15. Пользователи, которые сегодня предпочитают компилировать программы из исходного кода, рассчитывают на возможность ввести команды configure; make; make install и получить чистую сборку. На странице <http://seul.org/docs/autotut/> публикуется хорошее учебное пособие по использованию данных инструментов. Даже если программа распространяется в бинарном виде, инструменты autoconf(1) способны помочь автоматизировать решение проблемы подстройки кода под условия различных платформ.

Существуют другие инструменты, которые решают данную проблему. Двумя из наиболее широко известных средств являются утилита Imake(1), связанная с системой X Window и инструмент Configure, созданный Ларри Уоллом (Larry Wall, создавшим позднее язык Perl) и приспособленный для многих различных проектов. Все они, как минимум, также сложны как набор autoconf, и используются в настоящее время не чаще. Они не охватывают такой же широкий диапазон целевых систем как autoconf.

17.6. Интернационализация.

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

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

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

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

Во-вторых, в современных Unix-системах прослеживается отчетливая тенденция избавляться от всего "исторического мусора", связанного с множеством таблиц символов, и делать естественным языком приложений UTF-8, 8-битовое кодирование Unicode-символов со смещением (в противоположность, например, использованию в качестве такого языка 16-битовые широкие символы). Нижние 128 символов UTF-8 представляют собой символы ASCII, а нижние 256 символов — Latin-1, что означает обратную совместимость с двумя наиболее широко используемыми таблицами символов. Этому способствует тот факт, что XML и Java сделали данный выбор, но движущая сила уже определена, даже если бы не было XML и Java.

В-третьих, необходимо внимательно относиться к диапазонам символов в регулярных выражениях. Элемент [a-z] не обязательно охватывает все строчные буквы, если сценарий или программа разрабатывается, например, для Германии, где острая буква "s" или символ "ß" считается строчным, но не входит в данный диапазон. Подобные проблемы возникают с французскими буквами под ударением. Надежнее использовать элемент [[:lower:]] и другие символьные диапазоны, описанные в стандарте POSIX.

17.7. Переносимость, открытые стандарты и открытый исходный код.

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

Рассмотрим, например, разработку программы захвата изображений для цифровой камеры. Зачем создавать собственный формат для сохранения изображений или покупать коммерческий код, когда (как было сказано в главе 5) существует многократно проверенная, полнофункциональная библиотека для записи PNG-изображений, реализованная в открытом исходном коде?

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

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

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

До конца 90-х годов данная рекомендация была бы невыполнимой. Несколько альтернатив частных операционных систем и средств разработки были "благородными экспериментами", подтверждениями академических идей или "игрушками". Но Internet изменил все. В середине 2003 года Linux и другие Unix-системы с открытым исходным кодом доказали свою стойкость как платформы для создания программного обеспечения промышленного качества. В настоящее время разработчики имеют лучшую альтернативу, чем зависимость от краткосрочных бизнес- решений, направленных на защиту чужой монополии. Применяйте защищенные конструкции — создавайте программы на основе открытого исходного кода и вы не попадете в безвыходное положение.