Искусство программирования для Unix
Шрифт:
PDP-11 стал важной моделью для последующих поколений микропроцессоров. Оказалось, что базовые абстракции С весьма четко охватывают классическую архитектуру. Таким образом, С начинался как хорошее дополнение для микропроцессоров и, вместо того чтобы стать непригодным в связи с тем, что его предположения устарели, фактически становился лучше, по мере того, как аппаратное обеспечение все более сильно сливалось с классической архитектурой. Одним примечательным примером этой конвергенции была замена в 1985 году процессора Intel 286 с неуклюжей сегментной адресацией памяти процессором серии 386 с большим простым адресным пространством памяти. Чистый
Не случайно, что экспериментальная эра в компьютерной архитектуре завершилась в середине 80-х годов прошлого века, т.е. в то время, когда язык С (и его ближайший потомок С++) побеждали все предшествующие им универсальные языки программирования. Язык С, разработанный как тонкий, но гибкий уровень над классической архитектурой, выглядит в перспективе двух десятилетий как почти наилучшая из возможных конструкций для ниши структурированного ассемблера, которую он и должен был занять. В дополнение к компактности, ортогональности и независимости (от машинной архитектуры, на которой он первоначально был разработан), данный язык также имеет важное качество прозрачности, рассмотренное в главе 6. В конструкции нескольких языков программирования, которые, возможно, являются лучшими, потребовалось внести серьезные изменения (такие как введение функции сборки мусора в памяти), чтобы создать достаточную функциональную дистанцию от С и избежать вытеснения им.
Эту историю стоит вспоминать и переосмысливать, поскольку пример языка С показывает, насколько мощной может быть четкая, минималистская конструкция. Если бы Томпсон и Ритчи были менее дальновидными, то они создали бы язык, который делал бы гораздо больше, опирался бы на более строгие предположения, никогда удовлетворительно не переносился бы с исходной аппаратной платформы и исчез бы вместе с ней. Напротив, язык С расцвел, и с тех пор пример Томпсона и Ритчи влияет на стиль Unix-разработки. Однажды в беседе о конструировании самолетов, писатель, искатель приключений, художник и авиаинженер Антуан де Сент-Экзюпери подчеркнул: "Совершенство достигается не в тот момент, когда более нечего добавить, а тогда, когда нечего более удалить".
Ритчи и Томпсон жили по этому принципу. Долгое время после того как ресурсные ограничения на ранних Unix-программах были смягчены, они работали над тем, чтобы поддерживать С в виде настолько тонкого уровня над аппаратным обеспечением, насколько это возможно.
Когда я просил о какой-либо особенно экстравагантной функции в С, Деннис обычно говорил мне: "Если тебе нужен PL/1, ты знаешь, где его взять". Ему не приходилось общаться с каким-либо маркетологом, утверждающим: "На диаграмме продаж нам нужна галочка в рамочке!".
История С также подтверждает важность существования работающей эталонной реализации до стандартизации. Повторно данная тема затрагивается в главе 17, где рассматривается развитие стандартов С и Unix.
4.4. Библиотеки
Одним из последствий того влияния, которое стиль Unix-программирования оказал на модульность и четко определенные API-интерфейсы, является устойчивая тенденция к разложению программ на фрагменты связующего уровня, объединяющего семейства библиотек, особенно общих библиотек (эквивалентов структур, которые в Windows и других операционных системах называются динамически подключаемыми библиотеками или DLL (Dynamically-Linked Libraries)).
Если подходить к проектированию тщательно и обдуманно,
Практика, при которой подобное разделение на уровни осуществляется явно, в Unix-программировании является стандартной. При этом служебные подпрограммы собираются в библиотеку, которая документируется отдельно. В таких программах клиентская часть специализируется на задачах пользовательского интерфейса и протоколе высокого уровня. Несколько большего внимания к конструкции требует отделение оригинальной клиентской части и ее замена другими, адаптированными для иных целей. Некоторые другие преимущества позволит раскрыть учебный пример.
Существует оборотная сторона данной проблемы. В мире Unix библиотеки, поставляемые как библиотеки, должны сопровождаться тестовыми программами.
API-интерфейсы должны сопутствовать программам и наоборот. API, для использования которого необходимо написать C-код и который невозможно без труда вызвать из командной строки, очень тяжело изучать и использовать. И наоборот, невероятно сложно использовать интерфейсы, единственной открытой и документированной формой которых является какая-либо программа и которые невозможно просто вызвать из программы на С, — например, route(1) в прежних Linux-системах.
Кроме упрощения процесса обучения, тестовые программы библиотек часто создают превосходные тестовые структуры. Поэтому опытные Unix-программисты видят в них не только форму для приложения умственных усилий пользователя библиотеки, но и свидетельство того, что код, вероятно, был хорошо протестирован.
Важной формой создания иерархии библиотек является подключаемая подпрограмма (plugin) — библиотека с набором известных входных точек, которая динамически загружается после запуска и предназначена для решения специализированной задачи. Для работы таких подпрограмм необходимо, чтобы вызывающая программа была организована в значительной степени как документированная служебная библиотека, в которую подключаемая подпрограмма может направить обратный вызов.
4.4.1. Учебный пример: подключаемые подпрограммы GIMP
Программа GIMP (GNU Image Manipulation program— программа обработки изображений) разрабатывалась как графический редактор с управлением посредством интерактивного GUI-интерфейса. Однако GIMP построена как библиотека подпрограмм обработки изображений и вспомогательных подпрограмм, которые вызываются сравнительно тонким уровнем управляющего кода. Управляющий код "знает" о GUI, но не имеет непосредственной информации о форматах изображений. Библиотечные подпрограммы, напротив, распознают форматы изображений, но не имеют информации о GUI-интерфейсе.