![]() |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Чувство стиля Дела косметические!
Если вас заботят размеры кода, убедитесь, что вы позаботились об архитектурных аспектах. Следует быть краткими при выражении сути алгоритма, но не экономьте на нажатиях клавиш ценой ясности.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
Правило: Составные имена компонентов Не включайте в имя компонента имя базовой абстракции данных (служащей именем класса). |
Компонент, задающий номер части в PART, должен называться просто number, а не part_number. Подобная сверхквалификация является типичной ошибкой новичков, скорее затуманивающей, чем проясняющей текст. Помните, каждое использование компонента однозначно определяет класс, например part1.number, где part1 должно быть объявлено типа PART или его потомка.
Для составных имен лучше избегать стиля, популяризируемого Smalltalk и используемого в библиотеках, таких как X Window System, объединяющих несколько слов вместе, начиная каждое внутренне слово с большой буквы, как в yearlyPremium. Вместо этого разделяйте компоненты подчеркиванием, как в yearly_premium. Использование внутренних больших букв конфликтует с соглашениями обычного языка и выглядит безобразно, оно приводит к трудно распознаваемому виду, следовательно, к ошибкам (сравните aLongAndRatherUnreadableIdentifier и an_even_longer_but_perfectly_clear_choice_of_name).
Иногда каждый экземпляр некоторого класса содержит поле, представляющее экземпляр другого класса. Это приводит к мысли использовать для имени атрибута имя класса. Например, вы определили класс RATE, а классу ACCOUNT потребовался один атрибут типа RATE, для которого кажется естественным использовать имя rate - в нижнем регистре, в соответствии с правилами, устанавливаемыми ниже. Хотя можно пытаться найти более специфическое имя, но приемлемо rate: RATE. Правила выбора идентификаторов допускают одинаковые имена компонента и класса. Нарушением стиля является добавление префикса the, как в the_rate, что только добавляет шумовую помеху.
Локальные сущности и аргументы подпрограмм
Акцент на ясные, хорошо произносимые имена сделан для компонентов и классов. Для локальных сущностей и аргументов подпрограмм, имеющих локальную область действия, нет необходимости в подобной выразительности. Имена, несущие слишком много смысла, могут даже ухудшить читабельность текста, придавая слишком большое значение вспомогательным элементам. (Им можно давать короткие однобуквенные имена, как, например, в процедуре класса TWO_WAY_LIST из библиотеки e Base)
move (i: INTEGER) is
-- Поместить курсор в позицию i или after, если i слишком велико
local
c: CURSOR; counter: INTEGER; p: like FIRST_ELEMENT
...
remove is
-- Удаляет текущий элемент; перемещает cursor к правому соседу
-- (или after если он отсутствует).
local
succ, pred, removed: like first_element
...
Если бы succ и pred были бы компонентами, они бы назывались successor и predecessor. Принято использовать имя new для локальной сущности, представляющей новый объект, создаваемый программой, и имя other для аргумента, представляющего объект того же типа, что и текущий, как в объявлении для clone в GENERAL:
frozen clone (other: GENERAL): like other is...
Регистр
Регистр не важен в нашей нотации, поскольку было бы опасным позволять двум почти идентичным идентификаторам обозначать разные вещи. Но настоятельно рекомендуется в интересах читабельности и согласованности придерживаться следующих правил:
Эти правила касаются имен, выбираемых разработчиком. Резервируемые слова нотации разделяются на две категории. Ключевые слова, такие как do и class, играют важную синтаксическую роль; они записаны в нижнем регистре полужирным шрифтом. Несколько зарезервированных слов не являются ключевыми, поскольку играют ассоциированную семантическую роль, они записываются курсивом с начальной буквой в верхнем регистре, подобно константам. К ним относятся: Current, Result, Precursor, True и False.
Грамматические категории
Точные правила управляют грамматическими категориями слов, используемых в идентификаторах. В некоторых языках эти правила могут применяться без колебаний, в английском, как ранее отмечалось, они обеспечивают большую гибкость.
Правило для имен классов уже приводилось: следует всегда использовать существительные, как в ACCOUNT, возможно квалифицированные, как в LONG_TERM_SAVINGS_ACCOUNT, за исключением случая отложенных классов, описывающих структурные свойства, для которых могут использоваться прилагательные, как в NUMERIC или REDEEMABLE.
Имена подпрограмм должны отражать принцип Разделения Команд и Запросов:
Стандартные имена
Вы уже заметили многократное использование во всей книге нескольких базисных имен, таких как put и item. Они являются важной частью метода.
Большинству классов необходимы компоненты, представляющие операции нескольких базисных видов: вставка, замена, доступ к элементам структуры. Вместо придумывания специальных имен для этих и подобных операций в каждом классе, предпочтительно повсюду применять стандартную терминологию.
Вот каковы основные стандартные имена. Начнем с процедур создания: имя make рекомендуется для наиболее общей процедуры создания и имена вида make_some_qualification, например, make_polar и make_cartesian для классов POINT или COMPLEX.
Для команд наиболее общие имена приведены в таблице:
Таблица 8.1. Стандартные имена команд |
|
Команда |
Действие |
extend |
Добавить элемент |
replace |
Заменить элемент |
force |
Подобна команде put, но может работать для большего числа случаев. Например для массивов put имеет предусловие, требующее, чтобы индекс не выходил за границы, в то время как force не имеет предусловий и допускает выход за границы |
remove |
Удаляет (не специфицированный) элемент |
prune |
Удаляет специфицированный элемент |
wipe_out |
Удаляет все элементы |
Для небулевых запросов (атрибутов или функций)
Таблица 8.2. Стандартные имена для не булевых запросов |
|
Запрос |
Действие |
item |
Базисный запрос для получения элемента: в классе ARRAY - элемент с заданным индексом; STACK - элемент вершины стека; QUEUE - "старейший" элемент и так далее |
infix "@" |
Синоним item в некоторых случаях, например в классе ARRAY |
count |
Число используемых элементов структуры |
capacity |
Физический размер, распределенный для ограниченной структуры, измеряемый числом потенциальных элементов. Инвариант должен включать 0<count and count <= capacity |
Для булевых запросов стандартными именами являются:
Таблица 8.3. Стандартные имена булевых запросов |
|
Запрос |
Действие |
empty |
Содержит ли структура элементы? |
full |
Заполнена ли структура ограниченной емкости элементами? Обычно эквивалент count = capacity |
has |
Присутствует ли заданный элемент в структуре? (Базисный тест проверки членства) |
extendible |
Можно ли добавить элемент? (Может служить предусловием для extend) |
prunable |
Можно ли удалить элемент? (Может служить предусловием для remove и prune) |
readable |
Доступен ли элемент? (Может служить предусловием для remove и item) |
writable |
Можно ли изменить элемент? (Может служить предусловием для extend, replace, put и др.) |
Преимущества согласованного именования
Схема имен, данная выше, вносит наиболее видимый вклад в характерный стиль конструирования ПО, разрабатываемый в соответствии с принципами этой книги.
Не зашли ли мы слишком далеко в борьбе за согласованность? Не получится ли, что в программах будут использоваться одни и те же имена, но с разной семантикой? Например, item для стека возвращает элемент вершины, а для массива - элемент с заданным индексом.
При систематическом подходе к ОО-конструированию, использованию статической типизации и Проектированию по Контракту этот страх не оправдан. Знакомясь с компонентом, автор клиента может полагаться на четыре вида свойств, представленных в краткой форме класса:
Подпрограммы имеют, конечно же, тело, но предполагается, что тело не должно волновать клиента. |
Три из этих элементов будут отличаться для вариантов базисных операций. Например, в краткой форме класса STACK можно найти компонент:
put (x: G)
-- Втолкнуть x на вершину
require
writable: not full
ensure
not_empty: not empty
pushed: item = x
В классе ARRAY появляется однофамилец:
put (x: G; i: INTEGER)
-- Заменить на x значение элемента с индексом i
require
not_too_small: i >= lower
not_too_large: i <= upper
ensure
replaced: item (i) = x
Сигнатуры различаются, предусловия, постусловия, заголовочные комментарии - все различно. Использование имени put, не создавая путаницы, обращает внимание читателя на общую роль этих процедур: они обе обеспечивают базисный механизм изменений.
Эта согласованность оказывается одним из наиболее привлекательных аспектов метода, в частности библиотек. Новые пользователи быстро привыкают к ней и при появлении нового класса, следующего стандартному стилю, принимают его как старого знакомого и могут сразу же сосредоточиться на нужных им компонентах.
Многие алгоритмы используют константы. Как отмечалось в одной из предыдущих лекций, у констант плохая слава из-за отвратительной практики изменения их значений. Следует предусмотреть меры против подобного непостоянства.
Манифестные и символические константы
Основное правило использования констант утверждает, что не следует явно полагаться на значения:
Принцип Символических констант Не используйте манифестные (неименованные) константы в любых конструкциях, отличных от объявления символических констант. Исключением являются нулевые элементы основных операций. |
Манифестная константа задается явно своим значением, как, например, 50 (целочисленная константа) или "Cannot find file" (строковая константа). Принцип запрещает использование инструкций в форме:
population_array.make (1, 50)
или
print ("Cannot find file") -- Ниже смотри смягчающий комментарий
Вместо этого следует объявить соответствующий константный атрибут и в телах подпрограмм, где требуются значения, обозначать их именами атрибутов:
US_state_count: INTEGER is 50
file_not_found: STRING is "Cannot find file"
...
population_array.make (1, state_count)
...
print (file_not_found)
Преимущества очевидны: если появится новый штат или изменится сообщение, достаточно изменить только одно объявление.
Использование 1 наряду со state_count в первой инструкции не является нарушением принципа, так как он запрещает манифестные константы, отличные от нулевых элементов. Нулевыми элементами, допустимыми в манифестной форме, являются целые 0 и 1 (нулевые элементы сложения и умножения), вещественное число 0.0, нулевой символ, записываемый как '%0', пустая строка - "". Использование символической константы One каждый раз, когда требуется сослаться на нижнюю границу массива (1 используется соглашением умолчания), свидетельствовало бы о педантичности, фактически вело бы к ухудшению читабельности.
В других обстоятельствах 1 может просто представлять системный параметр, имеющий сегодня одно значение, а завтра другое. Тогда следует объявить символическую константу, как например Processor_count: INTEGER is 1 в многопроцессорной системе, использующей пока один процессор. |
Принцип Символических Констант слишком строг в случае простых, однократно применяемых манифестных строк. Можно было бы усилить исключение, сформулировав его так: "за исключением нулевых элементов основных операций и манифестных строковых констант, используемых однократно". В примерах этой книги используются такие константы. Такое ослабление правила приемлемо, но в долгосрочной перспективе лучше придерживаться правила в первоначальной форме, даже если это кажется педантичным. Одно из главных применений строковых констант - это вывод сообщений пользователю. Когда успешная система, выпущенная для национального рынка, выходит на международный, то с символическими константами переход на любой язык не представляет трудностей.
Где размещать объявления констант
Если число локальных константных атрибутов в классе становится большим, то, вероятно, имеет место нераспознанная абстракция данных - определенное понятие, характеризуемое рядом параметров.
Тогда желательно сгруппировать объявления констант, поместив их в отдельный класс, который может служить предком для любого класса, которому нужны константы. (Некоторые разработчики предпочитают в таких случаях использовать отношение клиента.) Примером является класс ASCII библиотеки Base.
Хотя формальные элементы класса несут достаточно подробную информацию, следует сопровождать класс неформальными пояснениями. Заголовочные комментарии к подпрограммам и предложения feature, дополненные предложениями indexing, задаваемыми для каждого класса, отвечают этой потребности.
Комментарии в заголовках: упражнение на сокращение
Подобно дорожным знакам на улицах Нью-Йорка, говорящим "Даже и не думайте здесь парковаться!", знаки на входе в отдел программистов должны предупреждать " Даже и не думайте написать подпрограмму без заголовочного комментария". Этот комментарий, следующий сразу за ключевым словом is, кратко формулирует цель программы; он сохраняется в краткой и плоской краткой форме класса:
distance_to_origin: REAL is
-- Расстояние до точки (0, 0)
local
origin: POINT
do
create origin
Result := distance (origin)
end
Обратите внимание на отступ: комментарий начинается на шаг правее тела подпрограммы.
Комментарий к заголовку должен быть информативным, кратким, ясным. Он имеет собственный стиль, которому будем обучаться на примере, начав вначале с несовершенного комментария, а затем улучшая его шаг за шагом. В классе CIRCLE к одному из запросов возможен такой комментарий:
tangent_from (p: POINT): LINE is
-- Возвращает касательную линию к текущей
-- окружности,
-- проходящую через данную точку p,
-- если эта точка лежит вне текущей окружности
require
outside_circle: not has (p)
...
В стиле этого комментария много ошибок. Во-первых, он не должен начинаться "Возвращает ..." или "Вычисляет ... ", используя глагольные формы, поскольку это противоречит принципу Разделения Команд и Запросов. Имя, возвращаемое не булевым запросом, типично использует квалифицированное существительное. Поэтому лучше написать так:
-- Касательная линия к текущей окружности,
-- проходящая через данную точку p,
-- если эта точка лежит вне текущей окружности
Так как комментарий теперь не предложение, а просто квалифицированное имя, то точку в конце ставить не надо. Теперь следует избавиться от дополнительных слов (в английском особенно от the), не требуемых для понимания. Для комментариев желателен телеграфный стиль. (Помните, что читатели, любящие литературные красоты, могут выбрать для чтения романы Марселя Пруста.)
-- Касательная линия к текущей окружности,
-- проходящая через точку p,
-- если точка вне текущей окружности
Следующая ошибка содержится в последней строчке. Дело в том, что условие применимости подпрограммы - ее предусловие - not has (p), появится сразу после комментария в краткой форме, где оно выражено ясно и недвусмысленно. Поэтому нет необходимости в его перефразировке, что может привести только к путанице, а иногда и к ошибкам (типичная ситуация: предусловие в форме x >= 0 с комментарием "применимо только для положительных x", а нужно "не отрицательных"); всегда есть риск при изменившемся предусловии забыть об изменении комментария. Наш пример теперь станет выглядеть так:
-- Касательная линия к текущей окружности из точки p
Еще одна ошибка состоит в использовании слов линия (line) и точка (point) при ссылках на результат и аргумент запроса: эта информация непосредственно следует из объявляемых типов LINE и POINT. Лучше использовать формальные объявления типов, которые появятся в краткой форме, чем сообщать эту информацию в неформальной форме комментария. Итак:
-- Касательная к текущей окружности из p
Наши ошибки состояли в излишнем дублировании информации - о типах, о требованиях предусловия. Из их анализа следует общее правило написания комментариев: исходите из того, что читатель компетентен в основах технологии, не включайте информацию, непосредственно доступную в краткой форме класса. Это, конечно, не означает, что никогда не следует указывать информацию о типах, например, в предыдущем примере Расстояние до точки (0,0) было бы двусмысленным без указания слова "точка" (point).
При необходимости сослаться на текущий экземпляр используйте фразы вида: текущая окружность, текущее число, вместо явной ссылки на сущность Current. Во многих случаях можно вообще избежать с упоминания текущего объекта, так как каждому программисту ясно, что компоненты при вызове применяются к текущему объекту. В данном примере наш заключительный комментарий выглядит так:
-- Касательная из p
На этом этапе осталось три слова, а начинали с трех строк из 18 длинных слов. Длина комментария сократилась примерно на 87%, мы можем считать, что упражнение на сокращение выполнено полностью, - сказать короче и яснее трудно.
Несколько общих замечаний. Отметим бесполезность в запросах фраз типа "Возвращает ...", других шумовых слов и фраз, которые следует избегать во всех подпрограммах: "Эта подпрограмма вычисляет (возвращает) ...", просто скажите, что делается. Вместо:
-- Эта программа записывает последний исходящий звонок
пишите
-- Записать исходящий звонок
Как показывает это пример, комментарий к командам (процедурам) должен быть в императивной или инфинитивной форме (в английском это одно и тоже). Он должен иметь стиль приказа и оканчиваться точкой. Для булевых запросов комментарий всегда должен быть в вопросительной форме и заканчиваться знаком вопроса:
has (v: G): BOOLEAN is
-- Появляется ли v в списке?
...
Соглашение управляет использованием программных сущностей - атрибутов, аргументов, появляющихся в комментариях. При наборе текста они выделяются курсивом (о других соглашениях на шрифт смотри ниже). В исходных программных текстах они всегда должны заключаться в кавычки, так что оригинальный текст выглядит так:
-- Появляется ли 'v' в списке?
Инструментарий, генерирующий краткую форму класса, использует это соглашение для обнаружения ссылок на сущности.
Нужно следить за согласованностью. Если функция класса имеет комментарий: "Длина строки", в другой процедуре не должна идти речь о "ширине" строки: "Изменить ширину строки", когда речь идет об одном и том же свойстве строки.
Все эти рекомендации применимы к подпрограммам. Поскольку экспортируемые атрибуты внешне ничем не должны отличаться от функций без аргументов, то они тоже имеют комментарий, появляющийся с тем же отступом, что и у функций:
count: INTEGER
-- Число студентов на курсе
Для закрытых атрибутов комментарии желательны, но требования к ним менее строгие.
Заголовочные комментарии предложений feature
Как вы помните, класс может иметь несколько предложений feature:
indexing
...
class LINKED_LIST [G] inherit ... creation
...
feature -- Initialization
make is ...
feature -- Access
item: G is ...
...
feature -- Status report
before: BOOLEAN is ...
...
feature -- Status setting
...
feature -- Element change
put_left (v: G) is ...
...
feature -- Removal
remove is ...
...
feature {NONE} -- Implementation
first_element: LINKABLE [G].
...
end
Одна из целей введения нескольких предложений feature состоит в том, чтобы придать разным компонентам различный статус экспорта. Но в данном примере все компоненты за исключением последнего доступны всем клиентам. Другой целью введения нескольких предложений feature, демонстрируемой данным примером, является группировка компонентов по категориям. Комментарий, находящийся на той же строке, что и ключевое слово feature, характеризует категорию. Такие комментарии, подобно заголовочным комментариям подпрограмм, обнаруживаются инструментарием, таким как short, создающим документацию и краткую форму класса.
Восемнадцать категорий с соответствующими комментариями стандартизованы в библиотеках Base, так что каждый компонент (из примерно 2000) принадлежит одной из них. В этом примере показаны некоторые из наиболее важных категорий. Status report соответствует опциям (устанавливаются компоненты в категории Status setting, не включенной в этот пример). Закрытые и выборочно экспортируемые компоненты появляются в категории Implementation. Эти стандартные категории появляются всегда в одном и том же порядке, известном инструментарию (через список, редактируемый пользователем), и будут сохраняться или переустанавливаться при выводе документации. Внутри каждой категории инструментарий перечисляет компоненты в алфавитном порядке для простоты поиска.
Категории покрывают широкий спектр областей применения, хотя для специальных проблемных областей могут понадобиться собственные категории.
Предложения индексирования
Подобными заголовочным комментариям, но немного более формальными являются предложения индексирования, появляющиеся в начале каждого класса:
indexing
description: "Последовательные списки в цепном представлении"
names: "Sequence", "List"
contents: GENERIC
representation: chained
date: "$Date: 96/10/20 12:21:03 $"
revision: "$Revision: 2.4$"
...
class LINKED_LIST [G] inherit
...
Предложения индексирования строятся в соответствии с принципом Само-документирования аналогично встроенным утверждениям и заголовочным комментариям, позволяя включать в текст ПО возможную документацию. Для свойств, не появляющихся напрямую в программном тексте класса, можно включить индексирующие разделы в форме:
indexing_term: indexing_value, indexing_value, ...
где indexing_term является идентификатором, а каждое indexing_value является некоторым базисным элементом, таким как строка, целое и так далее. Идентификаторы разделов, имеющие альтернативные имена, позволяют потенциальным авторам клиентов отыскать нужный класс по именам (names), содержанию (contents), выбору представления (representation), информации об обновлениях (revision), информации об авторе и многому другому. В разделы включается все, что может облегчить понимание класса и поиск, использующий ключевые слова. Благодаря специальному инструментарию, поддерживающим повторное использование, облегчается задача разработчиков по поиску в библиотеках нужных им классов с нужными компонентами.
Как индексирующие термы, так и их значения могут быть произвольными, но возможности выбора фиксируются для каждого проекта. Множество стандартов, принятых в библиотеке Base, частично приведено в примере. Каждый класс должен иметь раздел description, значением которого index_value является строка, описывающая роль класса в терминах его экземпляров (Последовательные списки..., но не "этот класс описывает последовательные списки", или "последовательный список", или "понятие последовательного списка" и т. д.). Для наиболее важных классов в этой книге - но не в коротких примерах, предназначенных для специальных целей - раздел description включался в предложение indexing.