Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 804
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
ГЛАВА
5 Проектирование при конструировании
95
программу. Она будет скрыта в одном темном уголке системы, пока не придет время ее изменить.
Зависимости от оборудования Примерами модулей, зависимых от оборудова- ния, могут служить интерфейсы между программой и разными типами мониторов, принтеров, клавиатур, дисководов, звуковых плат и сетевых устройств. Изолируй- те зависимости от оборудования в отдельной подсистеме или отдельном классе. Это облегчает адаптацию программы к новой аппаратной среде, а также помогает раз- рабатывать ПО для нестабильных версий устройств. Вы можете разработать ПО, моделирующее взаимодействие с конкретным устройством, и создать подсистему аппаратного интерфейса, использующую эту модель, пока устройство нестабиль- но или недоступно. Когда устройство будет готово к работе, подсистему интерфейса можно будет отключить от модели и подключить к устройству.
Ввод-вывод На чуть более высоком в сравнении с аппаратными интерфейсами уровне проектирования частой областью изменений является ввод-вывод. Если ваше приложение создает собственные файлы данных, его усложнение вполне может потребовать изменения формата файлов. Аспекты формата ввода-вывода данных, относящиеся к пользовательскому уровню, такие как позиционирование и число полей на странице, их последовательность и т. д., изменяются не менее часто.
В общем, анализ всех внешних интерфейсов на предмет возможных изменений —
благоразумная идея.
Нестандартные возможности языка Большинство версий языков поддер- живает нестандартные расширения, облегчающие работу программистов. Расшире- ния — палка о двух концах, потому что в другой среде — будь то другая аппаратная платформа, реализация языка другим производителем или новая версия языка, выпущенная тем же производителем, — они могут оказаться недоступны.
Если вы применяете нестандартные расширения языка, скройте работу с ними в отдельном классе, чтобы его можно было заменить при адаптации приложения к другой среде. Аналогично, используя библиотечные методы, доступные не во всех средах, скройте их за интерфейсом, поддерживающим все нужные среды.
Сложные аспекты проектирования и конструирования Скрывайте слож- ные аспекты проектирования и конструирования, потому что их частенько при- ходится реализовывать заново. Отделите их и минимизируйте влияние, которое может оказать их неудачное проектирование или конструирование на остальные части системы.
Переменные статуса Переменные статуса характеризуют состояние програм- мы и изменяются чаще, чем большинство других видов данных. Так, разработчи- ки, определившие переменную статуса ошибки как булеву переменную, вполне могут позднее прийти к выводу, что для этого лучше было бы использовать пере- числение со значениями
ErrorType_None, ErrorType_Warning и ErrorType_Fatal.
Использование переменных статуса можно сделать более гибким и понятным минимум двумя способами.
쐽
В качестве переменных статуса примените не булевы переменные, а перечис- ления. Диапазон поддерживаемых переменными статуса состояний часто при- ходится расширять, что в случае перечисления требует лишь перекомпиляции
96
ЧАСТЬ
II
Высококачественный код программы, а не масштабной ревизии всех фрагментов кода, выполняющих проверку переменной.
쐽
Вместо непосредственной проверки переменной используйте методы досту- па. Так вы сохраните возможность реализации более сложного механизма определения состояния. Например, если вы захотите проверять комбинацию переменной статуса ошибки и переменной текущего функционального состо- яния, вам будет легко реализовать это, если проверка будет скрыта в методе, и гораздо сложнее, если механизм проверки будет жестко закодирован во мно- гих местах программы.
1 ... 10 11 12 13 14 15 16 17 ... 104
Размеры структур данных Объявляя массив из 100 элементов, вы раскрыва- ете информацию, которую никто знать не должен. Защищайте право на личную жизнь! Сокрытие информации не всегда требует создания целого класса. Иногда для этого достаточно именованной константы: например,
MAX_EMPLOYEES позво- ляет скрыть число
100.
Предвосхищение изменений разного масштаба
Обдумывая потенциальные изменения системы, проектируйте ее так, чтобы влияние изменений было обратно пропорцио- нально их вероятности. Если вероятность изменения высо- ка, убедитесь, что систему будет легко адаптировать к нему.
С большим влиянием на несколько классов системы можно смириться лишь в случае крайне маловероятных изменений.
Грамотные проектировщики также принимают во внимание цену предвосхищения изменений. Если изменение малове- роятно, но его легко предугадать, рассмотрите его вниматель- нее, чем более вероятное изменение, которое трудно спла- нировать.
Один хороший метод определения областей вероятных изменений подразумевает, что вы должны сначала опреде- лить минимальное подмножество фрагментов программы, необходимых пользователям. Это подмножество составляет ядро системы, и его изменения маловероятны. Затем вы определяете минимальные инкрементные приращения системы. Они могут быть совсем небольшими, даже тривиальными. Вместе с функциональными изменениями рассматривайте также качественные изменения программы: обеспечение безопасности в многопоточной среде, поддержку механизмов локализации и т. д. Эти области потенциальных улучшений являются потенциальными изменениями системы; спроектируйте эти области, используя принципы сокрытия информации. Определив ядро в самом начале, вы поймете, какие компоненты системы на самом деле являются допол- нениями, и сможете с этого момента экстраполировать и скрывать аспекты воз- можных изменений программы.
Поддерживайте сопряжение слабым
Сопряжение характеризует силу связи класса или метода с другими классами или методами. Наша цель — создать классы и методы, имеющие немногочисленные, непосредственные, явные и гибкие отношения с другими классами, что еще на-
Перекрестная ссылка Рассмат- риваемый в этом разделе под- ход к предвосхищению изме- нений не связан с заблаговре- менным проектированием или кодированием (см. подраздел
«Программа содержит код, ко- торый может когда-нибудь по- надобиться» раздела 24.2).
Дополнительные сведения Это обсуждение основано на подхо- де, описанном в статье «On the design and development of prog- ram families» (Parnas, 1976).
ГЛАВА
5 Проектирование при конструировании
97
зывают «слабым сопряжением» (loose coupling)». В контекстах классов и методов концепция сопряжения одна и та же, так что при обсуждении сопряжения буду называть методы и классы «модулями».
Сопряжение модулей должно быть достаточно слабым, чтобы одни модули мог- ли с легкостью использовать другие. Например, железнодорожные вагоны соеди- няются с помощью крюков, которые при столкновении двух вагонов защелкива- ются. Представьте, как бы все усложнилось, если бы вагоны нужно было соеди- нять при помощи болтов, набора тросов или если бы вы могли соединить между собой только определенные типы вагонов. Механизм соединения вагонов эффек- тивен потому, что он максимально прост. Соединения между программными мо- дулями также должны быть как можно проще.
Старайтесь создавать модули, слабо зависящие от других модулей. Отношения модулей должны напоминать отношения деловых партнеров, а не сиамских близ- нецов. Скажем, метод
sin() (синус) сопряжен слабо, так как нужную информа- цию он получает в форме одного значения — угла в градусах. Метод
InitVars
(
var1, var2, var3, ..., varN ) сопряжен жестче, поскольку многие детали его работы становятся известными вызывающему модулю по передаваемым значениям. Два класса, зависящих от того, как каждый из них использует одну глобальную пере- менную, сопряжены еще жестче.
Критерии оценки сопряжения
Ниже описаны критерии, позволяющие оценить сопряжение модулей.
Объем Объем связи характеризует число соединений между модулями. Чем их меньше, тем лучше, поскольку модуль, имеющий более компактный интерфейс, легче связать с другими модулями. Метод, принимающий один параметр, слабее сопряжен с вызывающими его модулями, чем метод, принимающий шесть пара- метров. Класс, имеющий четыре грамотно определенных открытых метода, сла- бее сопряжен с модулями, которые его используют, чем класс, предоставляющий
37 открытых методов.
Видимость Видимостью называют заметность связи между двумя модулями. Про- граммирование не служба в ЦРУ — никто не похвалит вас за удачную маскиров- ку. Оно больше похоже на рекламу: вам следует делать связи между модулями как можно более крикливыми. Передача данных посредством списка параметров формирует очевидную связь, и это удачный вариант. Передача информации дру- гому модулю в глобальных данных является замаскированной и потому неудач- ной связью. Описание связи, осуществляемой через глобальные данные, в доку- ментации делает ее более явной и является чуть более удачным подходом.
Гибкость Гибкость характеризует легкость изменения связи между модулями.
Идеальная связь должна быть как можно гибче. Гибкость частично определяется другими аспектами связанности, но в то же время отличается от них. Положим, у вас есть метод
LookupVacationBenefit(), определяющий длительность отпуска со- трудника на основании даты его приема на работу и должности. Допустим далее, что в другом модуле у вас есть объект
employee (сотрудник), содержащий, поми- мо всего прочего, информацию о должности и дате приема на работу, и что этот модуль передает объект
employee в метод LookupVacationBenefit().
98
ЧАСТЬ
II
Высококачественный код
С точки зрения других критериев, эти два модуля кажутся слабо сопряженными: связь двух модулей посредством объекта
employee очевидна и является единствен- ной. Теперь предположим, что вам нужно использовать модуль
LookupVacation Be -
nefit() из третьего модуля, владеющего информацией о дате приема сотрудника на работу и его должности, но хранит ее не в объекте
employee. В этот момент модуль
LookupVacationBenefit() начинает вести себя гораздо менее дружелюбно, не желая связываться с новым модулем.
Чтобы третий модуль мог обратиться к модулю
LookupVacationBenefit(), он должен знать о существовании класса
Employee. Он мог бы подделать объект employee, используя лишь два поля, но тогда он должен был бы знать внутренние детали работы метода
LookupVacationBenefit(): ему была бы необходима уверенность в том,
что метод
LookupVacationBenefit() использует только два этих поля. Такое реше- ние было бы небрежным и безобразным. Второй вариант мог бы заключаться в таком изменении метода
LookupVacationBenefit(), чтобы вместо объекта employee он принимал должность сотрудника и дату его приема на работу. В обоих случа- ях первоначальный модуль оказывается на самом деле гораздо менее гибким, чем казалось сначала.
Возможен и счастливый конец этой истории: недружелюбный модуль сможет завести друзей, если пожелает быть гибким — если вместо объекта
employee он согласится принимать должность и дату приема сотрудника на работу.
Короче, чем проще вызывать модуль из других модулей, тем слабее он сопряжен, и это хорошо, потому что такой модуль более гибок и прост в сопровождении.
Создавая структуру программы, делите ее на блоки с учетом их взаимосвязанно- сти. Если бы программа была куском дерева, его следовало бы расщепить парал- лельно волокнам.
Виды сопряжения
Самые распространенные виды сопряжения описаны ниже.
Простое сопряжение посредством данных-параметров Два модуля со- пряжены таким способом, если между ними передаются только элементарные типы данных, причем передаются через списки параметров. Этот вид сопряжения нормален и приемлем.
Простое сопряжение посредством объекта Модуль сопряжен с объектом этим способом, если он создает экземпляр данного объекта. С этим видом со- пряжения также все в порядке.
Сопряжение посредством объекта-параметра Два модуля сопряжены друг с другом объектом-параметром, если Объект 1 требует, чтобы Объект 2 передал ему Объект 3. Этот вид сопряжения жестче, чем тот вид, при котором Объект 1 требует от Объекта 2 только примитивных типов данных, потому что Объект 2 должен обладать информацией об Объекте 3.
Семантическое сопряжение Самый коварный тип сопряжения имеет место тогда, когда один модуль использует не какой-то синтаксический элемент друго- го модуля, а некоторые семантические знания о внутренней работе этого модуля.
Некоторые примеры такого вида сопряжения описаны ниже.