Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 853
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
delete вы может установить указатель в null.
쐽
Исходите из того, что данные не являются персистентными. Так, если при воз- врате из метода переменная имеет определенное значение, не предполагайте,
что оно будет таким же при следующем вызове метода. Это правило неакту- ально, если вы используете специфические возможности языка, гарантирую- щие неизменность значения переменной, такие как ключевое слово
static языков
C++ и Java.
쐽
Выработайте привычку объявлять и инициализировать все данные перед их использованием. Если видите обращение к данным, но не можете найти по- близости команду их инициализации, отнеситесь к этому с подозрением!
Перекрестная ссылка Отладоч- ный код легко включать в ме- тоды доступа (см. подраздел
«Преимущества методов досту- па» раздела 13.3).
246
ЧАСТЬ III Переменные
10.6. Время связывания
Одним из аспектов инициализации, серьезно влияющим на удобство сопровож- дения и изменения программы, является «время связывания» — момент, когда переменная и ее значение связываются вместе (Thimbleby, 1988). Связываются ли они при написании кода? При его компиляции? При загрузке? При выполнении программы? В другое время?
Иногда выгоднее использовать как можно более позднее время связывания. В целом чем позже вы выполняете связывание, тем более гибким будет ваш код. В следую- щем примере связывание выполняется максимально рано — при написании кода:
Пример связывания во время написания кода (Java)
titleBar.color = 0xFF; // 0xFF — шестнадцатеричное значение синего цвета
Значение
0xFF связывается с переменной titleBar.color во время написания кода,
поскольку
0xFF — литерал, жестко закодированный в программе. Как правило, это неудачное решение, потому что при изменении одного значения
0xFF может утратиться его соответствие литералам
0xFF, используемым в других фрагментах с той же целью.
В следующем примере связывание переменной выполняется чуть позднее, во время компиляции кода:
Пример связывания во время компиляции (Java)
private static final int COLOR_BLUE = 0xFF;
private static final int TITLE_BAR_COLOR = COLOR_BLUE;
titleBar.color = TITLE_BAR_COLOR;
В данном случае
TITLE_BAR_COLOR является именованной константой — выраже- нием, вместо которого компилятор подставляет конкретное значение при ком- пиляции. Этот подход почти всегда лучше, чем жесткое кодирование. Он облег- чает чтение кода, потому что имя константы
TITLE_BAR_COLOR лучше характери- зует представляемое ей значение, чем
0xFF. Он облегчает изменение цвета заго- ловка окна (title bar), так как изменение константы будет автоматически отраже- но во всех местах, где она используется. При этом он не приводит к снижению быстродействия программы в период выполнения.
Вот пример еще более позднего связывания — в период выполнения:
Пример связывания в период выполнения (Java)
titleBar.color = ReadTitleBarColor();
ReadTitleBarColor() — это метод, который во время выполнения программы чита- ет значение из реестра Microsoft Windows, файла свойств Java или подобного места.
Этот код более понятен и гибок, чем код с жестко закодированным значением.
Чтобы изменить значение
titleBar.color; вам не нужно изменять программу: доста- точно просто изменить содержание файла, из которого метод
ReadTitleBarColor()
ГЛАВА 10 Общие принципы использования переменных
247
читает значение цвета. Так часто делают при разработке интерактивных прило- жений, предоставляющих возможность настройки их параметров.
Кроме того, время связывания может определяться тем, когда вызывается метод
ReadTitleBarColor(). Его можно вызывать при загрузке программы, при создании окна или при каждой перерисовке окна: каждый последующий вариант соответ- ствует все более позднему времени связывания.
Итак, в нашем примере переменная может связываться со значением следующим образом (в других случаях детали могут быть несколько иными):
쐽
при написании кода (с использованием магических чисел);
쐽
при компиляции (с использованием именованной константы);
쐽
при загрузке программы (путем чтения значения из внешнего источника, та- кого как реестр Windows или файл свойств Java);
쐽
при создании объекта (например, путем чтения значения при каждом созда- нии окна);
쐽
по требованию (например, посредством чтения значения при каждой перери- совке окна).
В целом чем раньше время связывания, тем ниже гибкость и ниже сложность кода.
Что до первых двух вариантов, то именованные константы по многим причинам предпочтительнее магических чисел, и вы можете получить гибкость, обеспечи- ваемую именованными константами, просто используя хорошие методики про- граммирования. При дальнейшем повышении уровня гибкости повышается и слож- ность нужного для его поддержания кода, а вместе с ней и вероятность ошибок.
Так как эффективность программирования зависит от минимизации сложности,
опытный программист будет обеспечивать такой уровень гибкости, какой нужен для удовлетворения требований, но не более того.
10.7. Связь между типами данных
и управляющими структурами
Между типами данных и управляющими структурами существуют четко опреде- ленные отношения, впервые описанные британским ученым Майклом Джексоном
(Jackson, 1975). В этом разделе мы их вкратце обсудим.
Джексон проводит связи между тремя типами данных и соответствующими управ- ляющими структурами.
Последовательные данные соответствуют последо-
вательности команд Последовательные данные (sequen- tial data) — это набор блоков данных, используемых в оп- ределенном порядке (рис. 10-2). Если у вас есть пять команд подряд, обрабатывающих пять разных значений, они формируют последователь- ность команд. Если бы вам нужно было прочитать из файла последовательные данные (фамилию сотрудника, номер карточки социального обеспечения, адрес,
телефон и возраст), вы включили бы в код последовательность команд.
Перекрестная ссылка О после- довательном порядке выполне- ния команд см. главу 14.
248
ЧАСТЬ III Переменные
Рис. 10-2. Последовательными называются данные, обрабатываемые
в определенном порядке
Селективные данные соответствуют операторам if
и case Вообще селективные данные (selective data) пред- ставляют собой набор, допускающий использование одно- го и только одного элемента данных в каждый конкретный момент времени (рис. 10-3). Соответствующими командами, выполняющими фак- тический выбор данных, являются операторы
if-then-else или case. Так, програм- ма расчета зарплаты должна была бы выполнять разные действия в зависимости от того, какой является оплата труда конкретного сотрудника: сдельной или по- временной. Опять-таки шаблоны кода соответствуют шаблонам данных.
Рис. 10-3. Селективные данные допускают использование только одного
из нескольких элементов
Итеративные данные соответствуют циклам Ите- ративные данные (iterative data) представляют собой дан- ные одного типа, повторяющиеся более одного раза (рис.
10-4). Обычно они хранятся как элементы контейнера, записи файла или элементы массива. Скажем, вы могли бы хранить в файле список номеров карточек соци- ального обеспечения, для чтения которого было бы разумно использовать соот- ветствующий цикл.
Рис. 10-4. Итеративные данные повторяются
Реальные данные могут быть комбинацией последовательных, селективных и итеративных данных. Для описания сложных видов данных подойдет комбина- ция простых.
Перекрестная ссылка Об услов- ных операторах см. главу 15.
Перекрестная ссылка О циклах см. главу 16.
ГЛАВА 10 Общие принципы использования переменных
249
10.8. Единственность цели каждой переменной
Есть несколько тонких способов использования переменных более чем с одной целью, однако подобных тонкостей лучше избегать.
Используйте каждую переменную только с одной целью Иногда есть соблазн вызвать одну переменную в двух разных местах для решения двух разных задач. Обычно в таких случаях переменной приходится присваивать не- удачное имя, соответствующее одной из ее целей, или использовать для решения обеих задач «временную» переменную (как правило, с бесполезным именем
x или
temp). Следующий пример иллюстрирует использование временной переменной с двойной целью:
Пример использования переменной с двойной целью —
плохой подход (C++)
// Вычисление корней квадратного уравнения.
// Предполагается, что дискриминант (b*b-4*a*c) неотрицателен.
temp = Sqrt( b*b - 4*a*c );
root[O] = ( -b + temp ) / ( 2 * a );
root[1] = ( -b - temp ) / ( 2 * a );
// корни меняются местами temp = root[0];
root[0] = root[1];
root[1] = temp;
Вопрос: какие отношения связывают
temp в первых строках кода и
temp в последних? Ответ: никакие. Из-за использо- вания в обоих случаях одной переменной создается впечат- ление, что две задачи связаны, хотя на самом деле это не так. Создание уникальных переменных для каждой цели делает код понятнее. Вот улучшенный вариант:
Пример использования двух переменных для двух целей —
хороший подход (C++)
// Вычисление корней квадратного уравнения.
// Предполагается, что дискриминант (b*b-4*a*c) неотрицателен.
discriminant = Sqrt( b*b - 4*a*c );
root[0] = ( -b + discriminant ) / ( 2 * a );
root[1] = ( -b - discriminant ) / ( 2 * a );
// корни меняются местами oldRoot = root[0];
root[0] = root[1];
root[1] = oldRoot;
Перекрестная ссылка Парамет- ры методов также должны иметь только одну цель. О параметрах методов см. раздел 7.5.
250
ЧАСТЬ III Переменные
Избегайте переменных, имеющих скрытый смысл Другой способ исполь- зования переменной более чем с одной целью заключается в том, что разные зна- чения переменной имеют разный смысл. Ниже я привел несколько примеров.
쐽
Значение переменной
pageCount представляет число отпечатанных страниц,
однако, если оно равно -1, произошла ошибка.
쐽
Если значение переменной
customerId меньше 500 000, оно представ- ляет номер заказчика, больше — вы вычитаете из него 500 000 для опре- деления номера просроченного счета.
쐽
Положительные значения переменной
bytesWritten представляют число байт,
записанных в выходной файл, а отрицательные — номер диска, используемо- го для вывода данных.
Избегайте подобных переменных, имеющих скрытый смысл. Формально это на- зывается «гибридным сопряжением» (hybrid coupling) (Page-Jones, 1988). Перемен- ная разрывается между двумя задачами, а это означает, что для решения одной из задач ее тип не подходит. В одном из наших примеров переменная
pageCount в нормальной ситуации определяет число страниц — это целое число. Однако зна- чение
-1 указывает на ошибку — целое число работает по совместительству буле- вой переменной!
Даже если применение переменных с двойной целью вам понятно, его не пой- мет кто-то другой. Дополнительная ясность, которой можно достигнуть благода- ря использованию двух переменных для хранения двух видов данных, удивит вас.
Никто не упрекнет вас в том, что вы впустую тратите ресурсы компьютера.
Убеждайтесь в том, что используются все объявленные пере-
менные Использование переменной с множественной целью имеет про- тивоположность: переменную можно не использовать вообще. Кард, Черч и Агрести обнаружили, что наличие неиспользуемых переменных коррелирова- ло с более высоким уровнем ошибок (Card, Church, and Agresti, 1986). Выработай- те привычку проверять, что используются все объявленные переменные. Некото- рые компиляторы и утилиты (такие как lint) предупреждают о наличии неисполь- зуемых переменных.
Контрольный список: общие вопросы
использования данных
Инициализация переменных
В каждом ли методе проверяется корректность входных параметров?
Переменные объявляются около места их использова- ния в первый раз?
Инициализировали ли вы переменные при их объявле- нии, если такое возможно?
Если переменные невозможно объявить и инициализи- ровать одновременно, вы инициализировали их около места использования в первый раз?
Правильно ли инициализируются счетчики и аккумуляторы? Выполняется ли их повторная инициализация, если она необходима?
http://cc2e.com/1092
Перекрестная ссылка Конт- рольный список вопросов о специфических типах данных см. в главе 12, а контрольный список вопросов, касающихся именования переменных, —
в главе 11.
ГЛАВА 10 Общие принципы использования переменных
251
Осуществляется ли правильная повторная инициализация переменных в коде,
который выполняется более одного раза?
Код компилируется без предупреждений? (И задали ли вы самый строгий уровень диагностики?)
Если язык поддерживает неявные объявления переменных, постарались ли вы предотвратить возможные проблемы?
Другие общие вопросы использования данных
Все ли переменные имеют как можно меньшую область видимости?
Являются ли обращения к переменным максимально сгруппированными как в плане интервала между обращениями, так и в плане общего времени жизни?
Соответствуют ли управляющие структуры типам данных?
Все ли объявленные переменные используются?
Все ли переменные связываются в подходящее время, т. е. соблюдаете ли вы разумный баланс между гибкостью позднего связывания и соответству- ющей ему повышенной сложностью?
Каждая ли переменная имеет одну и только одну цель?
Не имеют ли какие-нибудь переменные скрытого смысла?
Ключевые моменты
쐽
Неграмотная инициализация данных часто приводит к ошибкам. Описанные в этой главе способы инициализации позволят избежать проблем, связанных с неожиданными первоначальными значениями переменных.
쐽
Минимизируйте область видимости каждой переменной. Группируйте обраще- ния к переменным. Старайтесь делать переменные локальными для методов или классов. Избегайте глобальных данных.
쐽
Располагайте команды, использующие одни и те же переменные, как можно ближе друг к другу.
쐽
Раннее связывание ограничивает гибкость, но минимизирует сложность про- граммы. Позднее связывание повышает гибкость, но за это приходится распла- чиваться повышенной сложностью.
쐽
Используйте каждую переменную исключительно с одной целью.
252
ЧАСТЬ III Переменные
Г Л А В А 1 1
Сила имен переменных
Содержание
쐽
11.1. Общие принципы выбора имен переменных
쐽
11.2. Именование конкретных типов данных
쐽
11.3. Сила конвенций именования
쐽
11.4. Неформальные конвенции именования
쐽
11.5. Стандартизированные префиксы
쐽
11.6. Грамотное сокращение имен переменных
쐽
11.7. Имена, которых следует избегать
Связанные темы
쐽
Имена методов: раздел 7.3
쐽
Имена классов: раздел 6.2
쐽
Общие принципы использования переменных: глава 10
쐽
Размещение объявлений данных: одноименный подраздел раздела 31.5
쐽
Документирование переменных: подраздел «Комментирование объявлений данных» раздела 32.5
Несмотря на всю важность выбора удачных имен для эффективного программи- рования, я не знаю ни одной книги, в которой эта тема обсуждается хотя бы с ми- нимально приемлемым уровнем детальности. Многие авторы посвящают пару абзацев выбору аббревиатур, приводят несколько банальных примеров и ожида- ют, что вы сами о себе позаботитесь. Я рискую быть обвиненным в противопо- ложном: вы получите настолько подробные сведения об именовании переменных,
что никогда не сможете использовать их в полном объеме!
Советы, рассматриваемые в этой главе, касаются преимущественно именования переменных: объектов и элементарных типов данных. Однако их следует учиты- вать и при именовании классов, пакетов, файлов и других сущностей из мира программирования. Об именовании методов см. также раздел 7.3.
http://cc2e.com/1184