Здесь авторы не предпринимают попытки слишком подробно осветить все вопросы, связанные с модульностью внутри программ: во-первых, потому, что данная тема сама по себе является предметом целого тома (или нескольких томов), и, во-вторых, ввиду того, что настоящая книга посвящена искусству
Здесь более конкретно рассматриваются уроки Unix-традиции, связанные с правилом модульности. В данной главе рассматривается разделение внутри процесса. Далее, в главе 7, будут рассматриваться условия, при которых следует разделять программы на множество взаимодействующих процессов, а также более специфические методики для осуществления такого разделения.
4.1. Инкапсуляция и оптимальный размер модуля
Первым и наиболее важным качеством модульного кода является
API-интерфейсам между модулями отведена двойная роль. На уровне реализации они функционируют как заслонка между модулями, которая предотвращает воздействие внутренних данных модулей на соседние модули. На уровне проектирования именно API-интерфейсы (а не описание структур данных между ними) действительно определяют структуру программ.
Хороший способ проверить правильность проектирования API-интерфейса — определить, будет ли ясен смысл, если попытаться описать API обычным человеческим языком (без демонстрации фрагментов исходного кода)? Весьма целесообразно выработать привычку писать неформальные описания для API- интерфейсов до их кодирования. Более того, некоторые из наиболее способных разработчиков начинают с определения интерфейсов и написания кратких комментариев для них, а затем пишут код, поскольку процесс написания комментариев разъясняет задачи, возлагаемые на код. Подобные описания помогают организовать мышление разработчика, а также создают полезные комментарии для модулей, и в конечном итоге их можно включить в справочный документ для будущих читателей кода.
Чем дальше проводится декомпозиция модулей, тем меньше становятся блоки и более важными определения API-интерфейсов, Сокращается глобальная сложность и, как следствие, уязвимость относительно ошибок. С 70-х годов прошлого века общепринятым правилом (описанном в статьях, подобных [61]) стало проектирование программных систем в виде иерархий вложенных модулей, с сохранением минимального размера модулей на каждом уровне.
Возможно, однако, что подобное разбиение на модули будет слишком строгим и приведет к появлению очень маленьких модулей. Доказано [33], что при построении диаграммы плотности дефектов относительно размера модуля, кривая становится U-образной, а ее лучи направлены вверх (см. рис. 4.1). Очень большие и очень малые модули обычно служат причиной возникновения гораздо большего количества ошибок, чем модули среднего размера. Другим способом рассмотрения тех же данных является построение диаграммы количества строк кода в модуле относительно общего количества ошибок. Кривая в таком случае подобна логарифмической кривой до зоны 'наилучшего восприятия', где она сглаживается (соответственно с минимумом кривой плотности дефектов), и после которой она направляется вверх (как квадрат числа, соответствующего количеству строк кода, т.е. наблюдается то, что, согласно закону Брукса[40], можно интуитивно ожидать для всей кривой).
Рис. 4.1. Качественная диаграмма количества и плотности дефектов относительно размера модуля
Этот неожиданный рост количества ошибок при малых размерах модулей устойчиво наблюдается в широком диапазоне систем, реализованных на различных языках программирования. Хаттон (Hatton) предложил модель, связывающую данную нелинейную зависимость с объемом краткосрочной человеческой памяти[41]. Другая интерпретация данной нелинейной зависимости состоит в том, что при малых размерах элемента модуля возрастающая сложность интерфейсов становится доминирующим фактором. Трудно читать код, поскольку необходимо понять все еще до того, как станет возможным понять что-либо. В главе 7 рассматриваются более сложные формы разделения программ. В них также сложность интерфейсных протоколов начинает доминировать на фоне общей сложности системы по мере уменьшения размеров процессов
В нематематических понятиях эмпирические результаты Хаттона предполагают точку наилучшего восприятия между 200 и 400 логических строк кода, где сведена к минимуму возможная плотность дефектов, а все остальные факторы (такие как профессионализм программиста) равны. Размер не зависит от используемого языка программирования. Это замечание весьма усиливает приводимый в данной книге совет о программировании с использованием наиболее мощных из доступных языков и инструментов. Не следует однако принимать данные числа слишком буквально. Методы подсчета строк кода в значительной степени различаются в зависимости от того, как аналитик определяет логическую строку, а также от других условий (например, от того, отбрасываются ли комментарии). Сам Хаттон в качестве приближенного подсчета советует использовать двукратное преобразование между логическими и физическими строками, рекомендуя оптимальный диапазон в 400–800 физических строк кода.
4.2. Компактность и ортогональность
Код не является единственным объектом, который имеет оптимальный размер своего элемента. Языки программирования и API-интерфейсы (например, библиотечных или системных вызовов) сталкиваются с теми же ограничениями человеческого восприятия, которые создают U-образную кривую Хаттона.
Следовательно, Unix-программисты научились весьма тщательно продумывать при проектировании API-интерфейсов, наборов команд, протоколов и других способов повышения эффективности два другие свойства:
4.2.1. Компактность
Компактность — свойство, которое позволяет конструкции 'поместиться' в памяти человека. Для того чтобы проверить компактность, можно использовать хороший практический тест: необходимо определить, нуждается ли обычно опытный пользователь в руководстве для работы с данной программой. Если нет, то конструкция (или, по крайней мере, ее подмножество, которое охватывает обычное использование) является компактной.
Компактные программные средства обладают всеми достоинствами удобных физических инструментов. Их приятно использовать, они не препятствуют работе, позволяют повысить продуктивность.
Компактность не является синонимом 'слабости'. Конструкция может обладать большой мощностью и гибкостью, оставаясь при этом компактной, если она построена на абстракциях, которые просты в осмыслении и хорошо взаимосвязаны. Компактность также не эквивалентна 'простоте обучения'. Некоторые компактные конструкции являются весьма сложными для понимания до тех пор, пока пользователь не овладеет сложной основополагающей концептуальной моделью настолько, что его