Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 854
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
ГЛАВА 13 Нестандартные типы данных
331
Используйте методы доступа вместо глобальных данных
Все, что вы можете сделать с глобальными данными, вы можете сделать лучше, используя методы доступа. Применение методов доступа — основ- ная технология реализации абстрактных типов данных и достижения со- крытия информации. Даже если вы не хотите создавать полноценный абстракт- ный тип данных, вы все равно можете использовать методы доступа для центра- лизации управления данными и для защиты от изменений.
Преимущества методов доступа
Использование методов доступа имеет несколько преимуществ.
쐽
Вы получаете централизованный контроль над данными. Если позднее вы об- наружите более подходящую реализацию структуры, вам не придется изменять код везде, где она упоминается. Изменения не всколыхнут всю вашу програм- му — они останутся внутри методов доступа.
쐽
Вы можете быть уверены, что все ссылки на переменную изолированы. Добавляя элемент в стек с помощью таких выражений, как
stack.array[ stack.top ] = newElement, вы легко можете забыть проверить переполнение стека и допустить серьезную ошибку. Используя же методы доступа (скажем,
PushStack( newElement )), вы можете написать проверку переполнения стека в методе
PushStack(). Провер- ка будет выполняться автоматически, и вы сможете про нее забыть.
쐽
Вы автоматически получаете главные преимущества со- крытия информации. Методы доступа представляют со- бой примеры сокрытия информации, даже если вы и не намеревались использовать их в этих целях. Вы можете изменять содержимое метода доступа, не затрагивая остальную часть программы. Эти методы позволяют вам отремонтировать интерьер вашего дома, оставив фасад прежним, так что ваши друзья смогут его узнать.
쐽
Методы доступа легко преобразуются в абстрактные типы данных. Одно из преимуществ этих методов в том, что вы можете создавать более высокий уро- вень абстракции, чем при использовании глобальных данных напрямую. На- пример, вместо кода
if lineCount > MAX_LINES вы сможете, используя метод до- ступа, написать
if PageFull(). Это небольшое изменение документирует цель проверки
if lineCount прямо в коде программы. Оно дает небольшой выигрыш в читабельности, но постоянное внимание к таким деталям и создает разли- чие между красиво написанным ПО и наскоро слепленным кодом.
Как использовать методы доступа
Здесь представлена краткая версия теории и практики методов доступа: Скройте данные в классе. Объявите их с помощью ключевого слова
static или аналогично- го, чтобы гарантировать их существование в единственном экземпляре. Напиши- те методы, позволяющие получать и изменять данные. Потребуйте, чтобы код вне класса использовал эти методы, а не данные напрямую.
Например, если у вас есть глобальная статусная переменная
g_globalStatus, опи- сывающая общее состояние программы, вы можете создать два метода доступа:
Перекрестная ссылка Об изоля- ции данных см. раздел 8.5.
Перекрестная ссылка О сокры- тии информации см. подраздел
«Скрывайте секреты (к вопро- су о сокрытии информации)»
раздела 5.3.
332
ЧАСТЬ III Переменные
globalStatus. Get() и globalStatus. Set(), каждый из которых делает то, что сказано в ее названии. Эти методы обращаются к переменной, спрятанной внутри класса,
заменяющего
g_globalStatus. Остальная часть программы может получать все пре- имущества бывшей глобальной переменной, вызывая
globalStatus.Get() и global-
Status.Set().
Если язык не поддерживает классы, вы все равно можете создавать методы доступа для манипуляции глобальными данными. Однако вам придется устанавливать ограничения доступа к глобальным данным с помощью стандартов ко- дирования, а не встроенных средств языка.
Вот несколько основных принципов применения методов доступа для сокрытия глобальных переменных в языках, не имеющих встроенной поддержки этого.
Требуйте, чтобы весь код обращался к данным через методы доступа Хо- рошим соглашением будет начинать имена всех глобальных переменных с пре- фикса
g_, и в дальнейшем требовать, чтобы никакой код не обращался к перемен- ным с префиксом
g_ напрямую, кроме методов доступа к этим переменным. Весь остальной код работает с этими данными через методы доступа.
Не валите все глобальные данные в одну кучу Если вы сложите все глобаль- ные данные в одну большую кучу и напишете для них методы доступа, вы решите проблему глобальных данных, но утратите некоторые преимущества абстрактных типов данных и сокрытия информации. Раз уж вы пишете методы доступа, обду- майте, к каким классам принадлежит каждая глобальная переменная, и затем упа- куйте данные и методы доступа к ним в этот класс.
Управляйте доступом к глобальным переменным с помощьюблокировок
По аналогии с управлением параллельным доступом к многопользовательской ба- зой данных, блокировка требует, чтобы перед вызовом или обновлением значения глобальной переменной ее помечали для изменений (check out). После использо- вания переменную можно освободить (check in). Если пока она занята (т. е. поме- чена для изменений), другая часть программы попытается к ней обратиться, про- цедура блокировки выводит сообщение об ошибке или генерирует исключение.
Такое описание механизма блокировок опускает многие тонкости в написании кода, полностью поддерживающего параллельное выполнение. По этой причине упрощенные схемы блокировок вроде этой наиболее полезны на стадии разработки. Пока схема тщательно не продумана, она ско- рее всего не будет достаточно надежна для работы в про- мышленной версии. При вводе программы в эксплуатацию такой код должен быть заменен на более безопасный и вы- полняющий более элегантные действия, чем вывод сообщений об ошибках. Так,
при обнаружении ситуации, когда несколько частей программы пытаются забло- кировать одну и ту же глобальную переменную, он мог бы записать сообщение об ошибке в файл.
Перекрестная ссылка Ограниче- ние доступа к глобальным пе- ременным, даже если ваш язык не поддерживает это напрямую,
— пример программирования с использованием языка, а не на языке (см. раздел 34.4).
Перекрестная ссылка О плани- ровании различий между рабо- чей и промышленной версиями программы см. подраздел «За- планируйте удаление отладоч- ных средств» раздела 8.6, а так- же раздел 8.7.
ГЛАВА 13 Нестандартные типы данных
333
Такой способ защиты во время разработки довольно легко реализовать, если вы используете методы доступа. Но это было бы затруднительно сделать, если бы вы обращались к данным напрямую.
Встройте уровень абстракции в методы доступа Разрабатывайте методы доступа в области определения задачи, а не на уровне деталей реализации. Этот подход позволяет улучшить читабельность, а также страхует от изменения дета- лей реализации.
Сравните пары выражений в табл. 13-1:
Табл. 13-1. Обращение к глобальным данным напрямую
и с помощью метода доступа
Непосредственное использование
Обращение к глобальным данным через
глобальных данных
методы доступа
node = node.next
account = NextAccount( account )
node = node.next
employee = NextEmployee( employee )
node = node.next
rateLevel = NextRateLevel( rateLevel )
event = eventQueue[ queueFront ]
event = HighestPriorityEvent()
event = eventQueue[ queueBack ]
event = LowestPriorityEvent()
Смысл первых трех примеров в том, что абстрактный метод доступа гораздо ин- формативнее общей структуры. Если вы используете структуры напрямую, вы одновременно делаете слишком многое: во-первых, показываете, что выполняет структура (переход к следующему элементу в связном списке), а во-вторых — что происходит по отношению к сущности, которую она представляет (выбор номе- ра счета, следующего работника или процентной ставки). Это слишком тяжелая ноша для простой операции присваивания в структуре данных. Сокрытие инфор- мации за абстрактными методами доступа позволяет коду самому говорить за себя и заставляет читать программу на уровне области определения задачи, а не на уровне деталей реализации.
Выполняйте доступ к данным на одном и том же уровне абстракции Если вы используете метод доступа для выполнения какого-то действия со структурой,
все остальные действия должны производиться с помощью таких методов. Если вы считываете данные с помощью метода доступа, то и записывайте их с помощью метода. Если вы вызываете
InitStack() для инициализации стека и PushStack() для добавления в него элементов, то вы создали целостное представление данных. Если же вы извлекаете элементы с помощью выражения
value = array[ stack.top ], то это представление данных противоречиво. Противоречивость усложняет код для по- нимания. Создайте метод
PopStack() и используйте вместо value = array[ stack.top ].
В примерах выражений в табл. 13-1. две операции с очере- дями событий происходят параллельно. Добавление в оче- редь — наиболее сложная из этих двух операций в таблице и потребует нескольких строк кода для поиска места вставки события, сдвига остальных элементов очереди для выделе- ния места новому событию, и установки нового начала или конца очереди. Удаление события из очереди по сложности будет примерно та- ким же. Если во время кодирования сложные операции будут помещены в мето-
Перекрестная ссылка Примене- ние методов доступа для оче- реди событий предполагает не- обходимость создания класса
(см. главу 6).
334
ЧАСТЬ III Переменные ды, а в остальных будет применяться прямой доступ к данным, это создаст безоб- разное, нераспараллеливаемое использование структуры. Теперь сравните пары выражений в табл. 13-2:
Табл. 13-2. Распараллеливаемое и нераспараллеливаемое
применение сложных данных
Нераспараллеливаемое
Распараллеливаемое использование
использование сложных данных
сложных данных
event = EventQueue[ queueFront ]
event = HighestPriorityEvent()
event = EventQueue[ queueBack ]
event = LowestPriorityEvent()
AddEvent( event )
AddEvent( event )
eventCount = eventCount - 1
RemoveEvent( event )
Может показаться, что эти принципы стоит применять только в больших програм- мах, однако методы доступа показали себя как эффективный способ решения проблем с глобальными данными. В качестве бонуса они делают код более чита- бельным и добавляют гибкость.
Как уменьшить риск использования глобальных данных
Как правило, глобальные данные должны быть переменными класса, который не был правильно спроектирован или разработан. В редких случаях данные действи- тельно должны быть глобальными, но доступ к ним может осуществляться посред- ством оболочки методов доступа, что позволит минимизировать потенциальные проблемы. В крохотном числе оставшихся вариантов вам действительно необхо- димы глобальные данные. В этих случаях вы можете рассматривать принципы,
перечисленные ниже, как прививки, дающие возможность пить воду в зарубежной поездке: они болезненны, но увеличивают шансы остаться здоровым.
Разработайте соглашения по именованию, которые
сделают глобальные переменные очевидными Вы мо- жете избежать некоторых ошибок, просто сделав очевидным факт, что вы работаете с глобальными данными. Если вы ис- пользуете глобальные переменные для нескольких целей
(например, как переменные и как замену именованных кон- стант), убедитесь, что ваши соглашения по именованию делают различия между этими типами использования.
Создайте хорошо аннотированный список всех глобальных переменных
Если соглашение по именованию указывает, что данная переменная является гло- бальной, будет полезно показать, что эта переменная делает. Список глобальных переменных — один из наиболее полезных инструментов, который может иметь программист, работающий с вашей программой.
Не храните промежуточных результатов в глобальных переменных Если вам нужно вычислить новое значение глобальной переменной, присвойте ей окон- чательный результат в конце вычислений, а не храните в ней результаты проме- жуточных расчетов.
Перекрестная ссылка О согла- шениях по именованию глобаль- ных переменных см. подраздел
«Идентифицируйте глобальные переменные» раздела 11.4.
1 ... 38 39 40 41 42 43 44 45 ... 104