Эрик Реймонд - Искусство программирования для Unix
Кроме упрощения процесса обучения, тестовые программы библиотек часто создают превосходные тестовые структуры. Поэтому опытные Unix-программисты видят в них не только форму для приложения умственных усилий пользователя библиотеки, но и свидетельство того, что код, вероятно, был хорошо протестирован.
Важной формой создания иерархии библиотек является подключаемая подпрограмма (plugin) — библиотека с набором известных входных точек, которая динамически загружается после запуска и предназначена для решения специализированной задачи. Для работы таких подпрограмм необходимо, чтобы вызывающая программа была организована в значительной степени как документированная служебная библиотека, в которую подключаемая подпрограмма может направить обратный вызов.
4.4.1. Учебный пример: подключаемые подпрограммы GIMP
Программа GIMP (GNU Image Manipulation program— программа обработки изображений) разрабатывалась как графический редактор с управлением посредством интерактивного GUI-интерфейса. Однако GIMP построена как библиотека подпрограмм обработки изображений и вспомогательных подпрограмм, которые вызываются сравнительно тонким уровнем управляющего кода. Управляющий код "знает" о GUI, но не имеет непосредственной информации о форматах изображений. Библиотечные подпрограммы, напротив, распознают форматы изображений, но не имеют информации о GUI-интерфейсе.
Библиотечный уровень документирован (и фактически распространяется в виде пакета "libgimp" для использования другими программами). Это означает, что программы на языке С, которые называются "подключаемыми подпрограммами", могут динамически загружаться в GIMP и вызывать библиотеку для обработки изображений, фактически принимая на себя управление на том же уровне, что и GUI-интерфейс (см. рис. 4.2).
Рис. 4.2. Связи между вызывающей и вызываемой программой в редакторе GIMP с загруженной подключаемой подпрограммой
Подключаемые подпрограммы используются для осуществления множества специализированных преобразований. В число таких преобразований входят: обработка карты цветов, размывание границ и очистка изображения; чтение и запись форматов, не характерных для ядра GIMP; такие расширения, как редактирование мультипликации и тем оконных менеджеров; многие другие виды обработки изображений, которые могут быть автоматизированы с помощью сценариев, содержащих логику обработки изображений в ядре GIMP. Перечень подключаемых подпрограмм для GIMP доступен в World Wide Web.
Несмотря на то, что большинство подключаемых подпрограмм для GIMP являются небольшими и простыми C-программами, существует также возможность написать подпрограмму, которая открывает библиотечный API-интерфейс для языков сценариев. Данная возможность описывается в главе 11 в ходе изучения модели "многопараметрических (polyvalent) программ".
4.5. Unix и объектно-ориентированные языки
С середины 80-х годов прошлого века большинство новых конструкций языков обладают собственной поддержкой объектно-ориентированного программирования (Object-Oriented Programming — OO). Напомним, что в объектно-ориентированном программировании функции, воздействующие на определенную структуру данных, инкапсулируются вместе с данными в объект, который может рассматриваться как единое целое. В противоположность этому, модули в не-ОО-языках делают связь между данными и воздействующими на них функциями весьма второстепенной, и модули часто воздействуют на данные и внутреннее устройство других модулей.
Первоначально идея ОО-дизайна позитивно сказалась в конструировании графических систем, графических пользовательских интерфейсов и определенных видов моделирования. К удивлению и постепенному разочарованию многих, обнаружились трудности в проявлении существенных преимуществ вне этих областей. Причины этого заслуживают отдельного рассмотрения.
Существует некоторый конфликт между Unix-традицией модульности и моделями использования, которые развились вокруг ОО-языков. Unix-программисты всегда несколько более скептически относились к ОО-технологии, чем их коллеги, работающие в других операционных системах. Частично из-за правила разнообразия. Слишком часто ОО-подход объявлялся единственно верным решением проблемы сложности программного обеспечения. Однако здесь кроется еще одна проблема, которую стоит исследовать, прежде чем оценивать определенные ОО (объектно-ориентированные) языки в главе 14. Рассмотрение этой проблемы также поможет лучше описать некоторые характеристики Unix-стиля не-ОО-программирования.
Выше отмечалось, что Unix-традиция модульности является традицией тонкого связующего, минималистского подхода с несколькими уровнями абстракции между аппаратным обеспечением и объектами верхнего уровня программ. Частично это является влиянием языка С. Моделирование истинных объектов в языке С обычно сопряжено с большими усилиями. Вследствие этого нагромождение уровней абстракции является утомительным. Поэтому иерархии объектов в С склонны к относительной простоте и прозрачности. Даже применяя другие языки, Unix-программисты склонны переносить стиль использования тонкого связующего уровня и простой иерархии, которому они научились, используя Unix-модели.
ОО-языки упрощают абстракцию, возможно, даже слишком упрощают. Они поддерживают создание структур с большим количеством связующего кода и сложными уровнями. Это может оказаться полезным в случае, если предметная область является действительно сложной и требует множества абстракций, и вместе с тем такой подход может обернуться неприятностями, если программисты реализуют простые вещи сложными способами, просто потому что им известны эти способы и они умеют ими пользоваться.
Все ОО-языки несколько склонны "втягивать" программистов в ловушку избыточной иерархии. Объектные структуры и браузеры объектов не являются заменой хорошего дизайна или документации, но часто рассматриваются как таковые. Чрезмерное количество уровней разрушает прозрачность: крайне затрудняется их просмотр и анализ ментальной модели, которую по существу реализует код. Всецело нарушаются правила простоты, ясности и прозрачности, а в результате код наполняется скрытыми ошибками и создает постоянные проблемы при сопровождении.
Данная тенденция, вероятно, усугубляется тем, что множество курсов по программированию преподают громоздкую иерархию как способ удовлетворения правила представления. С этой точки зрения множество классов приравниваются к внедрению знаний в данные. Проблема данного подхода заключается в том, что слишком часто "развитые данные" в связующих уровнях фактически не относятся к какому-либо естественному объекту в области действия программы — они предназначены только для связующего уровня. (Одним из верных признаков этого является распространение абстрактных подклассов или "смесей".)
Другим побочным эффектом ОО-абстракции представляется то, что постепенно исчезают возможности для оптимизации. Например, а+а+а+а может стать а*4 или даже a<<2, в случае если a — целое число. Однако если кто-либо создаст класс с операторами, то ничто не будет указывать на то, являются ли они коммутативными, дистрибутивными или ассоциативными. Так как создатель класса не предполагал показывать внутреннее устройство объекта, то невозможно определить, какое из двух эквивалентных выражений более эффективно. Само по себе это не является достаточной причиной избегать использования ОО-методик в новых проектах; это было бы преждевременной оптимизацией. Однако это причина подумать дважды, прежде чем преобразовывать не-ОО-код в иерархию классов.
Для Unix-программистов характерно инстинктивное осознание данных проблем. Данная тенденция представляется одной из причин, по которой ОО-языкам в Unix не удалось вытеснить не-ОО-конструкции, такие как С, Perl (который в действительности обладает ОО-средствами, но они используются не широко) и shell. В мире Unix больше открытой критики ОО-языков, чем это позволяют ортодоксы в других операционных системах. Unix-программисты знают, когда не использовать объектно-ориентированный подход, а, если они действительно используют ОО-языки, то тратят большие усилия, пытаясь сохранить объектные конструкции четкими. Как однажды (в несколько другом контексте) заметил автор книги "The Elements of Networking Style" [60]: "... если программист знает, что делает, то трех уровней будет достаточно, если же нет, то не помогут даже семнадцать уровней".
Одной из причин того, что ОО-языки преуспели в большинстве характерных для них предметных областей (GUI-интерфейсы, моделирование, графические средства), возможно, является то, что в этих областях относительно трудно неправильно определить онтологию типов. Например, в GUI-интерфейсах и графических средствах присутствует довольно естественное соответствие между манипулируемыми визуальными объектами и классами. Если выясняется, что создается большое количество классов, которые не имеют очевидного соответствия с тем, что происходит на экране, то, соответственно, легко заметить, что связующий уровень стал слишком большим.