Искусство программирования для Unix - Реймонд Эрик Стивен
С другой стороны, тот,.кому впоследствии придется изменять программу, вряд ли будет поставлен в тупик изящным и ясным кодом — более вероятно, что он немедленно в нем разберется. Это особенно важно, если спустя несколько лет следующим куратором, возможно, будет сам создатель программы.
Не пытайтесь трижды расшифровывать хитроумный код. Однажды возможна неповторимая удача, однако, если придется разбираться в коде во второй раз, поскольку впервые он рассматривался слишком давно и детали позабыты, то это означает, что необходимо внести в код комментарии таким образом, чтобы третий раз был относительно безболезненным.
Генри Спенсер
1.6.3. Правило композиции: следует разрабатывать программы, которые будут взаимодействовать с другими программами
Если разрабатываемые программы не способны взаимодействовать друг с другом, то очень трудно избежать создания сложных монолитных программ.
Традиция Unix значительно способствует написанию программ, которые считывают и записывают простые текстовые форматы, ориентированные на потоки и не зависящие от устройств. В классической реализации Unix как можно больше программ создаются в виде простых фильтров (filters), которые на входе принимают простой текстовый поток и преобразовывают его в другой простой текстовый поток на выходе.
Вопреки распространенному мифу, такая практика широко распространена не потому, что Unix-программисты ненавидят графические пользовательские интерфейсы. Данная практика пользуется популярностью Unix-программистов, поскольку связывать вместе программы, не принимающие и не создающие на выходе простые текстовые потоки, гораздо труднее.
Текстовые потоки для Unix-инструментов являются тем же, чем являются сообщения для объектов в объектно-ориентированной среде. Простота интерфейса текстовых потоков усиливает инкапсуляцию инструментальных средств. Более сложные формы межпроцессного взаимодействия, такие как удаленный вызов процедур (remote procedure call), демонстрируют тенденцию к использованию программ с сильными внутренними зависимостями друг от друга.
Для того чтобы создавать компонуемые программы, их следует делать независимыми. Программа с одной стороны текстового потока как можно меньше должна зависеть от программы на другой стороне. Должна быть обеспечена возможность замены программы с одной стороны абсолютно другой реализацией без нарушения работы второй стороны.
GUI-интерфейсы могут быть разработаны на весьма высоком уровне. Иногда просто невозможно избежать сложных двоичных форматов данных какими-либо приемлемыми способами. Однако прежде чем создавать GUI-интерфейс, разумно будет изучить возможность выделения частей разрабатываемой программы со сложным взаимодействием в один блок, а основных алгоритмов — в другой, а также использования простого потока команд или прикладного протокола для связи двух блоков. Прежде чем изобретать нетривиальный двоичный формат для распространения данных, стоит экспериментальным путем проверить, имеется ли возможность задействовать простой текстовый формат и согласиться с небольшими затратами на синтаксический анализ в обмен на возможность обработки потока данных с помощью универсальных инструментальных средств.
В ситуации, когда сериализованный интерфейс, подобный протоколу, не является естественным для разрабатываемого приложения, конструкция "в духе Unix" заключается в том, чтобы, по крайней мере, организовать как можно больше программных примитивов в библиотеку с хорошо определенным API-интерфейсом. В таком случае открываются возможности вызова приложений путем связывания или привязки к приложению нескольких интерфейсов для различных задач.
Данная проблема подробнее обсуждается в главе 7.
1.6.4. Правило разделения: следует отделять политику от механизма и интерфейсы от основных модулей
В разделе "Что в Unix делается неверно" отмечалось, что разработчики системы X Window приняли основное решение о реализации "механизма, а не политики". Такой подход был направлен на то, чтобы сделать систему X общим сервером обработки графики и передать решение о стиле пользовательского интерфейса инстру-ментариям и другим уровням системы. Это оправданно, если учесть, что политика и механизм стремятся к изменению на протяжении различных периодов времени, причем политика меняется гораздо быстрее, чем механизм. Мода на вид и восприятие GUI-инструментариев может прийти и уйти, но растровые операции и компоновка останутся.
Таким образом, жесткое объединение политики и механизма имеет два отрицательных эффекта. Во-первых, политика становится негибкой и усложняется ее изменение в ответ на пользовательские требования. Во-вторых, это означает, что попытка изменения политики имеет строгую тенденцию к дестабилизации механизмов.
С другой стороны, разделение двух этих элементов делает возможным экспериментирование с новой политикой без разрушения механизмов. Кроме того, облегчается написание хороших тестов для механизма (политика, ввиду своего быстрого изменения, часто не оправдывает вложений).
Данное правило проектирования широко применимо вне контекста GUI-интерфейсов. В целом оно подразумевает, что следует искать способы разделения интерфейсов и основных модулей.
Одним из способов осуществления такого разделения, в частности, является написание приложения в виде библиотеки служебных подпрограмм на С, которые приводятся в действие встроенным языком сценариев, а управляющая логика приложения вместо С написана на языке сценариев. Классическим примером такой модели является редактор Emacs, в котором для управления редактирующими примитивами, написанными на С, используется встроенный интерпретатор языка Lisp. Такой стиль программирования обсуждается в главе 11.
Другой способ заключается в разделении приложения на взаимодействующие процессы клиента (front-end) и сервера (back-end), которые обмениваются данными через специализированный протокол прикладного уровня посредством сокетов. Данный конструкторский подход рассматривается в главах 5 и 7. Во внешней или клиентской части реализуется политика, а во внутренней или серверной — механизм. Глобальная сложность данной пары часто является значительно меньшей, чем сложность одного процесса, в котором монолитно реализованы те же функции. Одновременно уменьшается уязвимость относительно ошибок и сокращаются затраты на жизненный цикл.
1.6.5. Правило простоты: необходимо проектировать простые программы и "добавлять сложность" только там, где это необходимо
Многие факторы приводят к усложнению программ (а следовательно, делают их более дорогими и более уязвимыми относительно ошибок). Программисты — это зачастую яркие люди, которые гордятся (часто заслуженно) своей способностью справляться со сложностями и ловко обращаться с абстракциями. Часто они состязаются друг с другом, пытаясь выяснить, кто может создать "самые замысловатые и красивые сложности". Столь же часто их способность проектировать превалирует над способностью реализовывать и отлаживать, а результатом является дорогостоящий провал.
Мнение о "замысловатой и красивой сложности" является почти оксюмороном. Unix-программисты соперничают друг с другом за "простоту и красоту". Эта мысль неявно присутствует в данных правилах, но ее стоит сделать очевидной.
Дуг Макилрой.
Еще чаще (по крайней мере, в мире коммерческого программного обеспечения) излишняя сложность исходит от проектных требований, которые скорее основаны на маркетинговой причуде месяца, а не на реальных пожеланиях заказчика или фактических возможностях программы. Множество хороших конструкций были раздавлены маркетинговым нагромождением "статей контрольного перечня" — функций, которые часто вообще не востребованы заказчиком. Круг замыкается; соперники полагают, что должны соревноваться с чужими "украшательствами" путем добавления собственных. Довольно скоро "массивная опухоль" становится индустриальным стандартом, и все используют большие, переполненные ошибками программы, которые не способны удовлетворить даже их создателей.