Выбрать главу

Здесь авторы не предпринимают попытки слишком подробно осветить все вопросы, связанные с модульностью внутри программ: во-первых, потому, что данная тема сама по себе является предметом целого тома (или нескольких томов), и, во-вторых, ввиду того, что настоящая книга посвящена искусству Unix-программирования.

Здесь более конкретно рассматриваются уроки Unix-традиции, связанные с правилом модульности. В данной главе рассматривается разделение внутри процесса. Далее, в главе 7, будут рассматриваться условия, при которых следует разделять программы на множество взаимодействующих процессов, а также более специфические методики для осуществления такого разделения.

4.1. Инкапсуляция и оптимальный размер модуля

Первым и наиболее важным качеством модульного кода является инкапсуляция. Правильно инкапсулированные модули не открывают свое внутренне устройство друг другу. Они не обращаются к центральной части реализации друг друга, кроме того, они не используют глобальные данные беспорядочно. Они осуществляют связь друг с другом посредством программных интерфейсов приложений (API) — компактных, четких наборов вызовов процедур и структур данных. Именно об этом гласит правило модульности.

API-интерфейсам между модулями отведена двойная роль. На уровне реализации они функционируют как заслонка между модулями, которая предотвращает воздействие внутренних данных модулей на соседние модули. На уровне проектирования именно API-интерфейсы (а не описание структур данных между ними) действительно определяют структуру программ.

Хороший способ проверить правильность проектирования API-интерфейса — определить, будет ли ясен смысл, если попытаться описать API обычным человеческим языком (без демонстрации фрагментов исходного кода)? Весьма целесообразно выработать привычку писать неформальные описания для API-интерфейсов до их кодирования. Более того, некоторые из наиболее способных разработчиков начинают с определения интерфейсов и написания кратких комментариев для них, а затем пишут код, поскольку процесс написания комментариев разъясняет задачи, возлагаемые на код. Подобные описания помогают организовать мышление разработчика, а также создают полезные комментарии для модулей, и в конечном итоге их можно включить в справочный документ для будущих читателей кода.

Чем дальше проводится декомпозиция модулей, тем меньше становятся блоки и более важными определения API-интерфейсов, Сокращается глобальная сложность и, как следствие, уязвимость относительно ошибок. С 70-х годов прошлого века общепринятым правилом (описанном в статьях, подобных [61]) стало проектирование программных систем в виде иерархий вложенных модулей, с сохранением минимального размера модулей на каждом уровне.

Возможно, однако, что подобное разбиение на модули будет слишком строгим и приведет к появлению очень маленьких модулей. Доказано [33], что при построении диаграммы плотности дефектов относительно размера модуля, кривая становится U-образной, а ее лучи направлены вверх (см. рис. 4.1). Очень большие и очень малые модули обычно служат причиной возникновения гораздо большего количества ошибок, чем модули среднего размера. Другим способом рассмотрения тех же данных является построение диаграммы количества строк кода в модуле относительно общего количества ошибок. Кривая в таком случае подобна логарифмической кривой до зоны "наилучшего восприятия", где она сглаживается (соответственно с минимумом кривой плотности дефектов), и после которой она направляется вверх (как квадрат числа, соответствующего количеству строк кода, т.е. наблюдается то, что, согласно закону Брукса[40], можно интуитивно ожидать для всей кривой).

Рис. 4.1. Качественная диаграмма количества и плотности дефектов относительно размера модуля

Этот неожиданный рост количества ошибок при малых размерах модулей устойчиво наблюдается в широком диапазоне систем, реализованных на различных языках программирования. Хаттон (Hatton) предложил модель, связывающую данную нелинейную зависимость с объемом краткосрочной человеческой памяти[41]. Другая интерпретация данной нелинейной зависимости состоит в том, что при малых размерах элемента модуля возрастающая сложность интерфейсов становится доминирующим фактором. Трудно читать код, поскольку необходимо понять все еще до того, как станет возможным понять что-либо. В главе 7 рассматриваются более сложные формы разделения программ. В них также сложность интерфейсных протоколов начинает доминировать на фоне общей сложности системы по мере уменьшения размеров процессов

вернуться

40

Закон Брукса гласит, что подключение к запаздывающему проекту новых программистов еще больше замедляет работу, В более широком смысле рост затрат и количества ошибок можно выразить квадратом числа, соответствующего количеству программистов, которые были задействованы в проекте.

вернуться

41

Согласно модели Хаттона, небольшие различия в максимальном размере элемента кода, который программист может держать в краткосрочной памяти, непосредственно влияют на эффективность работы программиста.