Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
ГЛАВА 11 Сила имен переменных 253 11.1. Общие принципы выбора имен переменных Имя переменной нельзя выбирать, как кличку собаке: опираясь на его вычурность или звучание. В отличие от собаки и ее клички, которые являются разными сущ- ностями, переменная и ее имя формируют по идее одну сущность. Поэтому и адек- ватность переменной во многом определяется ее именем. Выбирайте имена пе- ременных со всей тщательностью. В следующем примере переменные названы плохо: Пример неудачного именования переменных (Java) x = x - xx; xxx = fido + SalesTax( fido ); x = x + LateFee( x1, x ) + xxx; x = x + Interest( x1, x ); Что происходит в этом фрагменте кода? Что означают имена x1, xx и xxx? А fido? Допустим, кто-то сказал вам, что этот код подсчитывает общую сумму предъявля- емого клиенту счета, опираясь на его долг и стоимость новых покупок. Какую переменную вы использовали бы для распечатки общей стоимости только новых покупок? Взглянув на исправленный вариант того же кода, ответить на этот вопрос куда проще: Пример удачного именования переменных (Java) balance = balance - lastPayment; monthlyTotal = newPurchases + SalesTax( newPurchases ); balance = balance + LateFee( customerID, balance ) + monthlyTotal; balance = balance + Interest( customerID, balance ); Из сравнения этих фрагментов можно сделать вывод, что хорошее имя перемен- ной адекватно ее характеризует, легко читается и хорошо запоминается. Чтобы облегчить себе достижение этих целей, соблюдайте несколько общих правил. Самый важный принцип именования переменных Важнейший принцип именования переменных состоит в том, что имя должно полно и точно описывать сущность, представляемую перемен- ной. Один эффективный способ выбора хорошего имени предполагает формулирование сути переменной в словах. Оптимальным именем переменной часто оказывается само это высказывание. Благодаря отсутствию загадочных со- кращений оно удобочитаемо; к тому же оно однозначно. Так как оно является полным описанием сущности, его нельзя спутать с чем-либо другим. Наконец, такое имя легко запомнить, потому что оно похоже на исходную концепцию. Переменную, представляющую число членов олимпийской команды США, мож- но было бы назвать numberOfPeopleOnTheUsOlympicTeam. Переменную, представ- 254 ЧАСТЬ III Переменные ляющую число мест на стадионе, — numberOfSeatsInTheStadium. Для хранения максимального числа очков, набранных спортсменами какой-то страны в совре- менной Олимпиаде, можно было бы создать переменную maximumNumberOfPoint- sInModernOlympics. Переменную, определяющую текущую процентную ставку, лучше было бы назвать rate или interestRate, а не r или x. Думаю, идею вы поняли. Обратите внимание на две характеристики этих имен. Во-первых, их легко рас- шифровать. Фактически их не нужно расшифровывать вообще: их можно просто прочитать. Ну, а во-вторых, некоторые имена велики — слишком велики, чтобы быть практичными. Длину имен переменных мы рассмотрим ниже. Несколько примеров удачных и неудачных имен переменных я привел в табл. 11-1. Табл. 11-1. Примеры удачных и неудачных имен переменных Удачные имена, Неудачные имена, Суть переменной адекватное описание неадекватное описание Сумма, на которую runningTotal, checkTotal written, ct, checks, CHKTTL, на данный момент x, x1, x2 выписаны чеки Скорость поезда velocity, trainVelocity, velocityInMph velt, v, tv, x, x1, x2, train Текущая дата currentDate, todaysDate cd, current, c, x, x1, x2, date Число строк на странице linesPerPage lpp, lines, l, x, x1, x2 Имена currentDate и todaysDate — хорошие имена, потому что полно и точно описывают идею «текущей даты». Фактически они составлены из слов с очевид- ным значением. Программисты иногда упускают из виду обычные слова, которые порой приводят к самому простому решению. Имена cd и c неудачны потому, что слишком коротки и «неописательны». Имя current тоже неудачно: оно не говорит, что именно является «текущим». Имя date кажется хорошим, но в итоге оно ока- зывается плохим, потому что мы имеем в виду не любую дату, а текущую; само по себе имя date об этом не говорит. Имена x, x1 и x2 заведомо неудачны: x тради- ционно представляет неизвестное количество чего-либо, и, если вы не хотите, чтобы ваши переменные были неизвестными величинами, подумайте о выборе других имен. Имена должны быть максимально конкретны. Имена x, temp, i и другие, достаточно общие для того, чтобы их можно было использовать более чем с одной целью, не так информативны, как могли бы быть, и обычно являются плохими. Ориентация на проблему Хорошее мнемоническое имя чаще всего описывает проблему, а не ее решение. Хорошее имя в большей степени выражает что, а не как. Если же имя описывает некоторый аспект вычислений, а не проблемы, имеет место обратное. Предпочи- тайте таким именам переменных имена, характеризующие саму проблему. Запись данных о сотруднике можно было бы назвать inputRec или employeeData. Имя inputRec — компьютерный термин, выражающий идеи ввода данных и запи- си. Имя employeeData относится к проблемной области, а не к миру компьюте- ров. В случае битового поля, определяющего статус принтера, имя bitFlag более ГЛАВА 11 Сила имен переменных 255 компьютеризировано, чем printerReady, а в случае приложения бухгалтерского учета calcVal более компьютеризировано, чем sum. Оптимальная длина имени переменной Оптимальная длина имени, наверное, лежит где-то между длинами имен x и maxi- mumNumberOfPointsInModernOlympics. Слишком короткие страдают от недостат- ка смысла. Проблема с именами вроде x1 и x2 в том, что, даже узнав, что такое x, вы ничего не сможете сказать об отношении между x1 и x2. Слишком длинные имена надоедает печатать, к тому же они могут сделать неясной визуальную струк- туру программы. Горла, Бенандер и Бенандер обнаружили, что отладка программы требо- вала меньше всего усилий, если имена переменных состояли в среднем из 10–16 символов (Gorla, Benander, and Benander, 1990). Отладка про- грамм с именами, состоящими в среднем из 8–20 символов, была почти столь же легкой. Это не значит, что следует присваивать всем переменным имена из 9–15 или 10–16 символов, — это значит, что, увидев в своем коде много более корот- ких имен, вы должны проверить их ясность. Вопрос адекватности длины имен переменных поясняет табл. 11-2. Табл. 11-2. Слишком длинные, слишком короткие и оптимальные имена переменных Слишком длинные имена: numberOfPeopleOnTheUsOlympicTeam numberOfSeatsInTheStadium maximumNumberOfPointsInModernOlympics Слишком короткие имена: n, np, ntm n, ns, nsisd m, mp, max, points То, что надо: numTeamMembers, teamMemberCount numSeatsInStadium, seatCount teamPointsMax, pointsRecord Имена переменных и область видимости Всегда ли короткие имена переменных неудачны? Нет, не всегда. Если вы присваиваете переменной короткое имя, такое как i, сама длина имени говорит о том, что перемен- ная является второстепенной и имеет ограниченную область действия. Программист, читающий код, сможет догадаться, что использование такой пере- менной ограничивается несколькими строками кода. Присваивая переменной имя i, вы говорите: «Эта переменная — самый обычный счетчик цикла/индекс масси- ва, не играющий никакой роли вне этих нескольких строк». У. Дж. Хансен (W. J. Hansen) обнаружил, что более длинные имена лучше присва- ивать редко используемым или глобальным переменным, а более короткие — локальным переменным или переменным, вызываемым в циклах (Shneiderman, 1980). Однако с короткими именами связано много проблем, и некоторые осмот- Перекрестная ссылка Об обла- сти видимости см. раздел 10.4. 256 ЧАСТЬ III Переменные рительные программисты, придерживающиеся политики защитного программи- рования, вообще избегают их. Дополняйте имена, относящиеся к глобальному пространству имен, спе- цификаторами Если у вас есть переменные, относящиеся к глобальному про- странству имен (именованные константы, имена классов и т. д.), подумайте, при- нять ли конвенцию, разделяющую глобальное пространство имен на части и пре- дотвращающую конфликты имен. В C++ и C# для разделения глобального простран- ства имен можно применить ключевое слово namespace: Пример разделения глобального пространства имен с помощью ключевого слова namespace (C++) namespace UserInterfaceSubsystem { // объявления переменных } namespace DatabaseSubsystem { // объявления переменных } Если класс Employee объявлен в обоих пространствах имен, вы можете указать нужное пространство имен, написав UserInterfaceSubsystem::Employee или Data- baseSubsystem::Employee. В Java с той же целью можно использовать пакеты. Программируя на языке, не поддерживающем пространства имен или пакеты, вы все же можете принять конвенции именования для разделения глобального про- странства имен. Скажем, вы можете дополнить глобальные классы префиксами, определяющими подсистему. Класс Employee из подсистемы пользовательского интерфейса можно было бы назвать uiEmployee, а тот же класс из подсистемы доступа к БД — dbEmployee. Это позволило бы свести к минимуму риск конфлик- тов в глобальном пространстве имен. Спецификаторы вычисляемых значений Многие программы включают переменные, содержащие вычисляемые значения: суммы, средние величины, максимумы и т. д. Дополняя такое имя спецификатором вроде Total, Sum, Average, Max, Min, Record, String или Pointer, укажите его в конце имени. У такого подхода несколько достоинств. Во-первых, при этом самая значимая часть имени переменной, определяющая наибольшую часть его смысла, располагается в самом начале имени, из-за чего становится более заметной и читается первой. Во-вторых, приняв эту конвенцию, вы предотвратите путаницу, возможную при наличии в одной программе имен totalRevenue и revenueTotal. Эти имена семан- тически эквивалентны, и конвенция не позволила бы использовать их как разные. В-третьих, набор имен вроде revenueTotal, expenseTotal, revenueAverage и expen- seAverage обладает приятной глазу симметрией, тогда как набор имен totalRevenue, ГЛАВА 11 Сила имен переменных 257 expenseTotal, revenueAverage и averageExpense упорядоченным не кажется. Наконец, согласованность имен облегчает чтение и сопровождение программы. Исключение из этого правила — позиция спецификатора Num. При расположе- нии в начале имени спецификатор Num обозначает общее число: например, num- Customers — это общее число заказчиков. Если же он указан в конце имени, то определяет индекс: так, customerNum — это номер текущего заказчика. Другим признаком данного различия является буква s в конце имени numCustomers. Од- нако даже в этом случае спецификатор Num очень часто приводит к замешатель- ству, поэтому лучше всего полностью исключить проблему, применив Count или Total для обозначения общего числа заказчиков и Index для ссылки на конкретно- го заказчика. Таким образом, переменные, определяющие общее число заказчи- ков и номер конкретного заказчика, получили бы имена customerCount и customer- Index соответственно. Антонимы, часто встречающиеся в именах переменных Используйте антонимы последовательно. Это делает код бо- лее согласованным и облегчает его чтение. Пары вроде begin/ end понять и запомнить легко. Пары, не соответствующие общепринятым антонимам, запоминаются сложнее и вызы- вают замешательство. В число антонимов, часто используе- мых в именах переменных, входят: 쐽 begin/end; 쐽 first/last; 쐽 locked/unlocked; 쐽 min/max; 쐽 next/previous; 쐽 old/new; 쐽 opened/closed; 쐽 visible/invisible; 쐽 source/target; 쐽 source/destination; 쐽 up/down. 11.2. Именование конкретных типов данных При именовании конкретных типов данных следует руководствоваться не толь- ко общими, но и специфическими соображениями. Ниже описаны принципы именования индексов циклов, переменных статуса, временных переменных, бу- левых переменных, перечислений и именованных констант. Именование индексов циклов Принципы именования индексов циклов возникли потому, что циклы относятся к самым популярным конструкциям. Как правило, в качестве индексов циклов используют пере- менные i, j и k: Перекрестная ссылка Аналогич- ный список антонимов, исполь- зуемых в именах методов, см. в подразделе «Дисциплиниро- ванно используйте антонимы» раздела 7.3. Перекрестная ссылка О циклах см. главу 16. 258 ЧАСТЬ III Переменные Пример простого имени индекса цикла (Java) for ( i = firstItem; i < lastItem; i++ ) { data[ i ] = 0; } Если же переменную предполагается использовать вне цикла, ей следует присво- ить более выразительное имя. Например, переменную, хранящую число записей, прочитанных из файла, можно было бы назвать recordCount: Пример удачного описательного имени индекса цикла (Java) recordCount = 0; while ( moreScores() ) { score[ recordCount ] = GetNextScore(); recordCount++; } // строки, в которых используется переменная recordCount Если цикл длиннее нескольких строк, смысл переменной i легко забыть, поэтому в подобной ситуации лучше присвоить индексу цикла более выразительное имя. Так как код очень часто приходится изменять, модернизировать и копировать в другие программы, многие опытные программисты вообще не используют име- на вроде i. Одна из частых причин увеличения циклов — их вложение в другие циклы. Если у вас есть несколько вложенных циклов, присвойте индексам более длинные имена, чтобы сделать код более понятным: Пример удачного именования индексов вложенных циклов (Java) for ( teamIndex = 0; teamIndex < teamCount; teamIndex++ ) { for ( eventIndex = 0; eventIndex < eventCount[ teamIndex ]; eventIndex++ ) { score[ teamIndex ][ eventIndex ] = 0; } } Тщательный выбор имен индексов циклов позволяет избежать путаницы индек- сов — использования i вместо j и наоборот. Кроме того, это облегчает понима- ние операций над массивами: команда score[ teamIndex ][ eventIndex ] более ин- формативна, чем score[ i ][ j ]. Не присваивайте имена i, j и k ничему, кроме индексов простых циклов: наруше- ние этой традиции только запутает других программистов. Чтобы избежать по- добных проблем, просто подумайте о более описательных именах, чем i, j и k. Именование переменных статуса Переменные статуса характеризуют состояние программы. Ниже рассмотрен один принцип их именования. ГЛАВА 11 Сила имен переменных 259 Старайтесь не присваивать переменной статуса имя flag Наоборот, рас- сматривайте флаги как переменные статуса. Имя флага не должно включать фраг- мент flag, потому что он ничего не говорит о сути флага. Ради ясности флагам сле- дует присваивать выразительные значения, которые лучше сравнивать со значени- ями перечислений, именованных констант или глобальных переменных, выступа- ющих в роли именованных констант. Вот примеры неудачных имен флагов: Примеры загадочных флагов (C++) if ( flag ) ... if ( statusFlag & 0x0F ) ... if ( printFlag == 16 ) ... if ( computeFlag == 0 ) ... flag = 0x1; statusFlag = 0x80; printFlag = 16; computeFlag = 0; Команды вида statusFlag = 0x80 будет совершенно непонятны, пока вы не пояс- ните в коде или в документации, что такое statusFlag и 0x80. Вот более понятный эквивалентный фрагмент: Примеры более грамотного использования переменных статуса (C++) if ( dataReady ) ... if ( characterType & PRINTABLE_CHAR ) ... if ( reportType == ReportType_Annual ) ... if ( recalcNeeded == True ) ... dataReady = true; characterType = CONTROL_CHARACTER; reportType = ReportType_Annual; recalcNeeded = false; Очевидно, что команда characterType = CONTROL_CHARACTER выразительнее, чем statusFlag = 0x80. Аналогично условие if ( reportType == ReportType_Annual ) понятнее, чем if ( printFlag == 16 ). Второй фрагмент показывает, что данный подход приме- ним к перечислениям и предопределенным именованным константам. Вот как с помощью именованных констант и перечислений можно было бы задать исполь- зуемые в нашем примере значения: Объявление переменных статуса (C++) // возможные значения переменной CharacterType const int LETTER = 0x01; const int DIGIT = 0x02; const int PUNCTUATION = 0x04; const int LINE_DRAW = 0x08; const int PRINTABLE_CHAR = ( LETTER | DIGIT | PUNCTUATION | LINE_DRAW ); 260 ЧАСТЬ III Переменные const int CONTROL_CHARACTER = 0x80; // возможные значения переменной ReportType enum ReportType { ReportType_Daily, ReportType_Monthly, ReportType_Quarterly, ReportType_Annual, ReportType_All }; Если вам трудно понять какой-то фрагмент кода, подумайте о переименовании переменных. В отличие от детективных романов код программ не должен содер- жать загадок. Его нужно просто читать. Именование временных переменных Временные переменные служат для хранения промежуточных результатов вычис- лений и служебных значений программы. Обычно им присваивают имена temp, x или какие-нибудь другие столь же неопределенные и неописательные имена. В целом использование временных переменных говорит о том, что программист еще не полностью понял проблему. Кроме того, с переменными, официально получившими «временный» статус, программисты обычно обращаются небреж- нее, чем с другими переменными, что повышает вероятность ошибок. Относитесь к «временным» переменным с подозрением Часто значение нуж- но на некоторое время сохранить. Однако в том или ином смысле временными являются почти все переменные. Называя переменную временной, подумайте, до конца ли вы понимаете ее реальную роль. Рассмотрим пример: Пример неинформативного имени «временной» переменной (C++) // Вычисление корней квадратного уравнения. // Предполагается, что дискриминант (b^2-4*a*c) неотрицателен. temp = sqrt( b^2 - 4*a*c ); root[0] = ( -b + temp ) / ( 2 * a ); root[1] = ( -b - temp ) / ( 2 * a ); Значение выражения sqrt( b^2 - 4 * a * c ) вполне разумно сохранить в перемен- ной, особенно если учесть, что оно используется позднее. Но имя temp ничего не говорит о роли переменной. Лучше поступить так: Пример замены «временной» переменной на реальную переменную (C++) // Вычисление корней квадратного уравнения. // Предполагается, что дискриминант (b^2-4*a*c) неотрицателен. discriminant = sqrt( b^2 - 4*a*c ); root[0] = ( -b + discriminant ) / ( 2 * a ); root[1] = ( -b - discriminant ) / ( 2 * a ); По сути это тот же код, только в нем использована переменная с точным описа- тельным именем. |