Искусство программирования для Unix - Реймонд Эрик Стивен
Шрифт:
Интервал:
Закладка:
Клиент-серверное разделение может также способствовать распределению приложений, интенсивно использующих ресурсы процессора, среди множества узлов. Или такое разделение может сделать их пригодными для распределенных вычислений через Internet (как в случае с игрой Freeciv). Связанная с этим модель CLI-cepeepa рассматривается в главе 11.
Поскольку все эти равноправные IPC-методики являются на некотором уровне идентичными, следует оценивать их главным образом по величине издержек программной сложности, которые они создают, а также по степени непрозрачности, вносимой ими в разрабатываемые конструкции. Именно поэтому в конечном итоге BSD-сокеты опередили остальные IPC-методы Unix, а RPC-методы не смогли привлечь к себе значительный интерес.
Параллельные процессы имеют фундаментальные отличия. Вместо поддержки взаимодействия между различными программами, они поддерживают некий вид разделения времени внутри экземпляра одной программы. Вместо того чтобы быть способом разделения большой программы на мелкие с более простым поведением, параллельная обработка является строго средством повышения производительности, и поэтому имеет все связанные с этим проблемы, а также ряд собственных.
Соответственно, несмотря на то, что проектировщикам следует искать пути разделения крупных программ на более простые взаимодействующие процессы, использование параллельной обработки внутри процессов должно быть скорее последним средством, чем первым. Часто обнаруживается, что их использования можно избежать. Если вместо параллельных процессов можно использовать ограниченную общую память и семафоры, асинхронные 1/О-операции с использованием SXGIO или цикл poll(2)/select(2), то такой возможностью следует воспользоваться. Поддерживайте простоту; используйте менее сложные методики.
Комбинация параллельных процессов, интерфейсов удаленного вызова процедур и тяжеловесного объектно-ориентированного дизайна является особенно опасной. Расчетливое и изящное применение любого из этих технических приемов может оказаться весьма полезным, но от проектов, где предполагается использовать все три, следует решительно отказаться.
Ранее уже отмечалось, что программирование в реальном мире направлено на управление сложностью и инструменты для управления сложностью очень полезны. Но когда эффект от их применения заключается в распространении сложности, а не в управлении ею, то лучше будет отказаться от них и начать проект с нуля. Никогда не забывайте об этом.
8 Мини-языки: поиск выразительной нотации
Хорошая нотация обладает тонкостью и выразительностью, которая со временем делает ее почти похожей на живого учителя. The World of Mathematics (1956) —Бертранд Рассел (Bertrand Russell)
Одним из самых последовательных результатов крупномасштабных исследований ошибок в программировании является то, что уровень ошибок программиста, выраженный в количестве дефектов на 100 строк кода, почти не зависит от языка, на котором написана программа56. Высокоуровневые языки, которые позволяют добиться больших результатов, используя меньшее количество строк, также означают меньшее количество ошибок.
В Unix имеется давняя традиция поддержки небольших языков, предназначенных для определенной прикладной области, языков, которые могут способствовать в радикальном сокращении количества строк кода в программах. Примеры узкоспециальных (domain-specific) языков включают в себя многочисленные языки разметки текстов (troff, eqn, tbl, pic, grap), утилиты оболочки (awk, sed, dc, be) и средства разработки программного обеспечения (make, уасс, lex). Невозможно провести четкие границы между узкоспециальными языками и более гибким видом файлов конфигурации программ (sendmail, BIND, X), или форматами файлов данных, или языками сценариев (которые рассматриваются в главе 14).
В сообществе Unix для таких языков узкоспециального назначения исторически определилось название "малые языки" или "мини-языки" , поскольку ранние их примеры были небольшими и имели небольшую сложность по сравнению с универсальными языками (в настоящее время широко используются все 3 термина для данной категории). Однако если предметная область сложна (тем, что имеет множество различных примитивных операций или включает в себя манипуляцию сложными структурами данных), то для нее может понадобиться прикладной язык, гораздо более сложный, чем некоторые универсальные языки. В данной книге используется традиционный термин "мини-язык" (minilanguage), для того чтобы подчеркнуть, что мудрое решение обычно заключается в сохранении данных конструкций небольшими и простыми насколько это возможно.
Узкоспециальный небольшой язык— чрезвычайно мощная конструкторская идея. Он позволяет определить собственный высокоуровневый язык для указания соответствующих методов, правил и алгоритмов, направленных на разрешение ближайшей задачи, сокращая глобальную сложность по сравнению с конструкцией, в которой для тех же целей используется жестко встроенный низкоуровневый код. Прийти к использованию мини-языка можно как минимум тремя путями, два из которых хороши, а один опасен.
Один из верных путей заключается в том, чтобы заранее осознать возможность использования конструкции на основе мини-языка, для того чтобы поднять данную спецификацию проблемы программирования на уровень выше к форме записи, которая является более компактной и выразительной, чем нотация, поддерживаемая в универсальном языке. Как и в случае с генерацией кода и создания программ, управляемых данными, мини-язык позволяет извлечь практическое преимущество из того факта, что количество ошибок в программном обеспечении будет почти не зависеть от уровня используемого языка; использование более выразительных языков означает более короткие программы и меньшее количество ошибок.
Второй правильный путь — заметить, что один из файловых форматов разрабатываемой спецификации очень похож на мини-язык, т.е. в нем развиваются сложные структуры и подразумеваются действия в контролируемом приложении. Можно ли с помощью данного языка попытаться описать управляющую логику так же, как форматы данных? Если это так, то, возможно, настало время перевести управляющую логику из неявного вида в явный в языке спецификации.
Ошибочный путь к конструкции мини-языка — это растягивать путь к нему, постепенно добавляя заплатки и сложные функции. На этом пути файл спецификации содержит задатки более скрытой управляющей логики и более замысловатых специализированных структур до тех пор, пока незаметно не станет сложным уникальным языком. Несколько "легендарных кошмаров встают" на этом пути. Каждый Unix-гуру вздрогнет при упоминании конфигурационного файла sendmail.cf, связанного с почтовым транспортом sendmail.
К сожалению, большинство разработчиков создают свой первый мини-язык ошибочным способом и только позднее осознают, насколько он запутан. Как очистить мини-язык? Иногда ответ предполагает переосмысление конструкции всего приложения. Другим печально известным примером был редактор ТЕСО, в котором возник первый макрос, а затем появились циклы и условные операторы по мере того, как программисты хотели использовать его для упаковки редактирующих подпрограмм с возрастающей сложностью. Созданная в результате уродливая конструкция была в конечном итоге исправлена путем переработки всего редактора, основанного на заранее продуманном языке. Так развивался Emacs Lisp (который рассматривается ниже).
Все достаточно сложные файлы спецификаций поднимаются до уровня мини-языков. Поэтому часто единственный способ обезопасить себя от создания плохого мини-языка заключается в том, чтобы знать, как создать хороший мини-язык. Это не должно быть сопряжено с неимоверными трудностями и наличием особых знаний относительно формальной теории языков. Вполне достаточно практического проектирования с помощью современных инструментов, изучения немногих относительно простых технических приемов и ознакомления с хорошими примерами.