Венгерская нотация

Вам говорит о чем-нибудь имя Чарлза Симонэ? Ну как же, тот самый, из "Майкрософта"! Помимо прочего он известен как создатель венгерской записи, названной так в честь его родины. Венгерская нотация используется программистами "Майкрософт" уже с добрый десяток лет, если не больше. Но лучше я не стану пересказывать, а предложу вам перевод того, что писал сам Симонэ о правилах выбора имен переменных и типов в программах. В чем он прав, а в чем нет - судите сами.

Когда возникает нужда ввести в программу новое имя, хороший программист начинает думать о следующих его свойствах:
1. Мнемоничность - имя должно легко запоминаться.
2. Содержательность - другие должны легко читать код.
3. Стиль - его часто считают данью эстетике, но он во многом влияет на информативность текста программ. Грубо говоря, нужно давать сходные имена сходным переменным.
4. Скорость выбора - непозволительно ни проводить время в раздумьях по поводу имени каждой переменной, ни тратить его на ввод слишком длинных идентификаторов.

В общем и целом это может отнимать время без всякой пользы. Удачное в одном смысле имя оказывается неподходящим в другом, и особенно сложно соблюдать стиль. Именно во избежание этих проблем предлагается система правил образования имен, которые бы удовлетворяли изложенным критериям. В основу системы положена идея именования переменных по их типу. Это простое положение нуждается в обширных разъяснениях, но если оно принимается, мы немедленно получаем выгоду, так как:
1. Имена будут содержательными и запоминающимися, поскольку если помнить тип переменной, то очевидно ее назначение и наоборот.
2. Соблюдение стиля обеспечивается применением правил.
3. Имена не придумываются, а механически, т.е. быстро, конструируются.
4. Текст программы можно проверять на правильность операций, исходя из имен переменных.

Понятие типа, предложенное выше, трактуется как набор операций, допустимых для переменной. Проверка на совпадение типов очень проста: если к переменным применим один и тот же набор содержательных операций, они равны, если различаются, то нет.
Понятие содержательной операции трактуется достаточно широко. Например, "подмножество массива А" или "второй параметр процедуры Position". В этом смысле целые числа X, Y имеют разный тип в вызове Position (x,y), а вызов Position (y,x) вообще лишен содержания. Это легко понять, если приписать переменным X, Y смысл координат точки на плоскости. Такова связь типов, операций и имен переменных.
Подобное очень общее понимание типа не уникально, нечто похожее предлагается в языках SIMULA и Smalltalk.

Но не будем забывать, что мы говорим об удобстве для людей, а не обсуждаем языки программирования. Поэтому четкое определение типа не критично: если переменная неверно поименована, это погрешность стиля, а не ошибка в программе.
Итак, на основе предложенного подхода сформулированы следующие конкретные правила по выбору имен переменных:
1. Переменные именуются согласно типу, за которым может следовать квалификатор. Их рекомендуется разделять. В С таким разделителем служит заглавная буква, например, rowFirst, где row (столбец) - тип, a First (первый) - квалификатор.
2. Квалификаторы служат различению переменных одного типа, существующих в общем контексте. Таким контекстом может служить вся система, модуль, процедура или структура данных. Если для конкретного случая существует "стандартный" квалификатор, то его и следует использовать. В противном случае выбор остается за программистом.
Он не очень сложен, поскольку имя должно быть уникальным в пределах типа и ограниченного контекста. Случай, когда в имени должно присутствовать два квалификатора, является редкостью.
Перечень стандартных квалификаторов и их значение приводятся ниже.
3. Простые типы следует обозначать коротко. Рекомендуемая краткость многих программистов удивляет, но ее назначение - придать реализм четвертому пункту.
4. Имена сложных или производных типов следует конструировать из обозначений составляющих типов. К стандартным случаям таких типов относятся указатели и массивы. В остальных программист свободен в своем выборе. Например, префикс p служит для обозначения указателей, тогда имя prowLast означает указатель на последний элемент столбца.

В принципе, эти правила можно было бы обогатить новыми схемами, но стандартные способы образования имен за годы использования доказали свою достаточность. Уместно заметить, что имена типов структур данных в общем случае не составляются из обозначений типов полей. Во-первых, если полей больше двух, это непрактично. Во-вторых, что важнее, собственные свойства структуры и операции, в которых она может принимать участие, не сводятся к сумме свойств отдельных полей.
У каждого из нас есть богатый опыт того, что пересмотр состава полей структуры не затрагивает набора допустимых операций (речь не идет о реализации этих операций).
Как следствие, для структур рекомендуется выбирать собственные краткие обозначения, которые сами будут использоваться при образовании новых имен.
Опыт свидетельствует, что выбирать такие обозначения труднее, чем строить имена. Когда нужно новое обозначение, первый импульс побуждает взять короткое содержательное название. Почти всегда это ошибка.

Посторонние не смогут понять особого смысла распространенного выражения в конкретной версии данной программы. Все шансы за то, что термин окажется применимым ко многим типам в программе.
Как понять, какое имя очень "логично", а какое использовано в одном из возможных смысловых оттенков? Кроме того, беседуя с программистом, как различить, когда используется общепринятый термин, а когда - упоминается переменная?

На поверку лучше всех оказываются аббревиатуры, не ассоциируемые с такими терминами. Их легко произносить и воспринимать на слух. Кто понимает суть дела - разберется.

А обычные слова лучше оставить для обычных разговоров.
Например, программа цветной графики может иметь набор имен для обозначения цветов. Как следует назвать красный (red)? Очевидный и порочный выбор - RED. Проблема в том, что это ничего не говорит о самом типе. Это название процедуры, которая сделает объект КРАСНЫМ? Даже зная, что RED обозначает константу, поскольку имя набрано заглавными буквами, нельзя сказать, к какому типу она относится. Если процедура вызывается, как paint (color), можно ли передать ей RED в роли параметра? А может ли RED использоваться в других местах программы и в иных целях? Вот потому-то и следует найти обозначение для цветового типа, а RED использовать для квалификации.

Заметьте, что для квалификатора очевидный выбор оказывается правильным! Дело в том, что к ним не предъявляются подобные требования. Квалификаторы не обязаны быть уникальными, во всяком случае область их действия уже. Специальное значение составного имени прояснит обозначение типа.
Кроме того, поскольку квалификаторы не участвуют в комбинировании, к ним не предъявляется требование исключительной лаконичности.
Точно так же обозначением типа цветовой переменной не должен быть "цвет". Только представьте все разнообразие цветовых типов: аппаратный код цвета, номер в таблице цветов, указатель на значение цвета в таблице, значения в таблице перекодировки цветов, RGB или HLS тройки...
И еще: обозначение будет присутствовать в именах наряду с другими обозначениями и/или квалификаторами.

Типичный условный выбор для данного случая - "со" (от color), или, если это обозначение уже использовано, "cl", "clr" и т.п. Обратите внимание, что мнемоническая ценность этих обозначений средняя: ни очень хорошая, ни слишком плохая.
Они служат для сжатого указания на уникальные свойства соответствующего раздела программы и потому достаточно произвольны. Недостаток осмысленности обозначений возмещается содержательным комментарием в том месте программы, где они вводятся.
Это разумное соглашение, так как количество базовых обозначений очень невелико даже в больших системах.
Наконец, можно сказать, что имя нашей переменной будет "coRed". Ценность такого имени демонстрирует следующая строка исходного текста гипотетической программы:
if co == coRed then *mpcopx[coRed]+=dx...
С первого взгляда видно, что переменная "co" сравнивается со значением собственного типа "coRed", которое используется как индекс массива, имеющего должный тип. Далее мы видим, что цвету поставлен в соответствие (mp от map) указатель (оператор "*" и px от pointer to x) на величину x, которая затем будет приращена на dx (delta x).

Подобный анализ не гарантирует, что программа окажется свободной от всех ошибок, но позволяет не допустить самых вопиющих. Кроме того, в процесс написания кода привносится некий ритм: "Посмотрим... У меня есть со, а нужен x... Есть ли mpcox? Нет, но имеется mpcopx, откуда возьму px, а вот через *px доберусь и до х..."

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

И проблема постижения ее смысла и внесения изменений в чужую программу вдруг перестанет быть проблемой.
Должное применение описанных соглашений по выбору конструирования имен типов и переменных непосредственно служит указанной цели и подводит к ней очень близко. Роль обозначений оказывается просто огромной. Если нашим программистам удалось бы договориться об обозначениях и конструировании имен, они могли бы сорвать куш.
Вот почему документирование обозначений является таким важным делом.
Примером соглашения по конструированию имен может служить правило уточнения основных обозначений квалификаторами. Каждый такой квалификатор должен иметь небольшую область действия, в которой данное проявление более общего типа является уникальным. Примером могут служить небольшие процедуры с малым числом параметров или структуры данных с ограниченным количеством полей.

Кто-нибудь может даже решить использовать квалификаторы в названиях уникальных типов вместо обозначений, чтобы любой читатель кода без сомнений проникал в смысл программы. Но этот случай мы уже обсуждали. Как свидетельствует опыт и указывают многие учебники, этим "любым" по прошествии времени может легко оказаться сам автор программы. Мораль: не пользуйтесь квалификаторами без нужды, даже если это и кажется полезным.

К сожалению, простой принцип квалификации обозначений типов не совсем применим, когда требуется выбрать имя для процедуры. Некоторые процедуры не имеют параметров или не возвращают значения. Область действия имени процедуры обычно обширна. Тем не менее есть набор неплохо зарекомендовавших себя правил и для этого случая.
1. Имена процедур следует писать иначе, чем все остальные. Например, они должны начинаться с заглавной буквы, тогда как имена переменных - со строчной. Это смягчает проблему большой области действия имени.
2. Если процедура возвращает значение, ее имя следует начинать с обозначения типа возвращаемой переменной.
3. Действие, производимое процедурой, выражается одним-двумя словами, обычно переходными глаголами. Эти слова следует разделять. Обыкновеным способом разделения служит написание слов с заглавной буквы.
4. Если это приемлемо, имя процедуры можно завершить перечнем обозначений типов ее формальных параметров.

Последний пункт несколько противоречит предыдущим замечаниям о правилах именования полей структур данных.

Но разница между двумя случаями в том, что когда изменяется состав параметров процедуры, претерпевает изменение и способ ее применения. Поэтому не так сложно заодно заменить имя процедуры. Более того, такая замена может служить полезной цели проверки вхождения вызовов процедуры в код.

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

Вот несколько примеров имен процедур: "InitSy" - инициализирует аргумент "sy"; "OpenFn" - открывает то, что указано аргументом "fn", результата не возвращает; "FcFromBnRn" - возвращает значение "fc", соответствующее параметрам "bn", "rn".

А вот обещанный список стандартных конструкций имен типов. X и Y символизируют произвольные обозначения типов. Действительные обозначения типов в именах будут, согласно правилам, начинаться со строчной буквы.
pX - указатель на X.
dX - разница между двумя значениями типа X. X + dX принадлежит типу X.
cX - счетчик экземпляров X.
mpXY - массив "игреков", индексированный "иксами".
rgX - массив "иксов".
iX - индекс (смещение) элемента в массиве "иксов".
dnX - (редко) массив индексированный типом X.
eX - (редко) элемент массива dnX.
grpX - группа "иксов" последовательно сохраненных в памяти. Используется, когда элементы типа X имеют переменный размер и стандартное индексирование не применимо. Доступ к элементам группы осуществляется иными средствами. Группой может быть, например, набор блоков доступной оперативной памяти.
bX - относительное смещение внутри типа X. Используется для указания смещения полей внутри структур данных. Может выражаться байтами или словами, в зависимости от типа указателя базы смещения.
В приемлемых случаях переменные с именами mp, rg, dn или grp являются указателями на описанные выше структуры.
cbX - размер переменной типа X в байтах.
cwX - размер переменной типа X в байтах.

Одна из очевидных проблем с нашими конструкциями заключается в их неоднозначности. Означает ли "pfc" самостоятельный тип или указатель на тип "fc"?

Этот и многие другие вопросы можно прояснить, только зная специфические обозначения, используемые в программе. В качестве отправной точки можно привести список стандартных квалификаторов. Х символизирует обозначение произвольного типа.
хfirst - первый элемент в упорядоченном множестве (интервале) значений типа Х.
хLast - первый элемент в упорядоченном множестве (интервале) значений типа Х. Условие продолжения цикла должно выглядеть как x<=xlast.
хLim - точный верхний предел упорядоченного множества значений типа Х. Условие продолжения цикла должно выглядеть как x<xLim.<br>
хMax - предел максимального значения всех возможных значений X. Например, длина вектора dnx vector в общем случае равна xMax.
хMac - максимальный выбранный предел текущего значения X в программе. Например, для итераций в массиве dnx используется цикл for x=0 step 1 to xMac-1 do... dnx[x] или for ix=0 step 1 to ixMac-1 do... rgx[ix].
хnil - условное "нулевое" значение типа X. Обычно выбирается равным 0 или -1.
хT - временная переменная типа X.

Ниже приводятся обозначения некоторых простых типов:
f - логический флаг (boolean). Если используется квалификатор, он должен отражать текущее состояние флага. Исключение делается для констант fTrue и fFalse;
w - машинное слово с произвольным содержанием;
ch - алфавитно-цифровой символ, обычно ASCII;
b - байт, он не обязательно должен содержать код символа, байт скорее похож на машинное слово;
sz - указатель на первый символ текстовой строки, ограниченной нулевым символом;
st - указатель на текстовую строку;
cch - количество символов в строке текста;

Теперь настало время для небольшого примера, иллюстрирующего использование описанных соглашений на практике.
Целью примера не является демонстрация кода самого по себе, но показ того, как можно разобраться с оформленным кодом. Многие имена, использованные в коде, являются стандартными.

1#include "sy.h"
2Extern int *rgwDic;
3extern int bsyMac;
4struct SY *PsySz(sz)
5char sz[];
6{
7char *pch;
8int cch;
9struct SY *psy, *PsyCreate();
10int *pbsy;
11int cwSz;
12unsigned wHash=0;
13pch=sz;
14while (*pch!=0
15wHash=(wHash<<5)+(whash> > 11+*pch++;
16cch=pch-sz;
17pbsy=&rgbsyHash[(wHash&077777)%cwHash];
18for (; *pbsy!=0; pbsy = &psy-> bsyNext)
19{
20char *szSy;
21szSy= (psy=(struct SY *)&rgwDic[*pbsy])-> sz;
22pch=sz;
23while (*pch==*szSy++)
24{
25if (*pch++==0)
26return (psy);
27}
28}
29cwSz=0;
30if (cch> =2)
31cwSz=(cch-2/sizeof(int)+1;
32*pbsy=(int *)(psy=PsyCreate(cwSY+cwSz))-rgwDic;
33Zero((int *)psy,cwSY);
34bltbyte(sz, psy-> sz, cch+1);
35return(psy);
36}

Обозначение типа SY является единственным специфическим в примере. Его определение можно найти в файле sy.h (довольно очевидно). Согласно стандарту имя типа пишется заглавными буквами.
Строка 2 говорит, что имеется массив слов, который называется словарем Dic (tionary). Помните, что поскольку Dic служит квалификатором, ему дано обычное имя.
Строка 3: смещение, указывающее "за" последний sy. Следует предположить, что оно будет использовано для размещения новых sy. Базой смещения, вероятно, будет rgwDic. Возможно, имя grpsy было бы лучше с локальной точки зрения, но на деле rgwDic используется для многих целей и потому выбрано нейтральным.
Строка 4: объявление процедуры, которая возвращает указатель на SY, а в качестве параметра принимает строку, ограниченную нулем.
Строки 7-12 объявляют переменные, назначение которых должно быть ясно из имен. Например, cwSz является счетчиком слов в некоторой строке (возможно, аргументе). Указатель на смещение sy - pbsy. Единственное имя с квалификатором - wHash использовано для обозначения хэш-кода.
Строка 13: заносим в pch указатель на первый символ строки sz.
Строка 16: cch - счетчик числа символов в строке sz.
Строка 17: cwHash - число слов в хэш-таблице. Возможно, его следовало бы назвать ibsyMax. В этом случае можно было бы опустить идентификатор rgbsyHash, но он, впрочем, удобен для идентификации хэш-таблицы в более широком контексте.
Строка 20 вводит новую строку sz, квалифицированную так, чтобы она отличалась от аргумента. Квалификатор - это источник данных, Sy.
Строка 23: судя по тому, как использовано имя szSy в этой строке, название pchSy звучало бы лучше, но и szSy вполне приемлемо.
Строки 29-31: этот странный код должен был иметь отношение к факту, что объявление SY включает 2 байта sz, так что cwSz на деле является числом слов в sz-2 байтах! Этот случай должен был бы заслужить комментарий или квалификатор М2 (минус два). cwSY - длина структуры SY в словах. Квалификатор, написанный заглавными буквами, не вполне соответствует стандарту, но помогает ассоциировать переменную именно с объявлением структуры SY, а не с какой-либо переменной соответствующего типа. И еще одно замечание: PsyCreate - хорошее имя для процедуры, а PsyCreateCw было бы еще лучше.

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

Евгений Щербатюк


Компьютерная газета. Статья была опубликована в номере 14 за 2000 год в рубрике разное :: мелочи жизни

©1997-2024 Компьютерная газета