Главная страница
Навигация по странице:

  • Пример сокрытия информации

  • Барьеры, препятствующие сокрытию информации

  • Избыточное распространение информации

  • Ошибочное представление о данных класса как о глобальных данных

  • Кажущееся снижение производительности

  • Перекрестная ссылка

  • Важность сокрытия информации

  • Определите области вероятных изменений

  • Определите элементы, изменение которых кажется вероятным.

  • Отделите элементы, изменение которых кажется вероятным.

  • Изолируйте элементы, изменение которых кажется вероятным.

  • Дополнительные сведения

  • ГЛАВА 5

  • Нестандартные возможности языка

  • Сложные аспекты проектирования и конструирования

  • Руководство по стилю программирования и конструированию по


    Скачать 7.6 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    Дата18.05.2023
    Размер7.6 Mb.
    Формат файлаpdf
    Имя файлаCode_Complete.pdf
    ТипРуководство
    #1139697
    страница14 из 104
    1   ...   10   11   12   13   14   15   16   17   ...   104
    ЧАСТЬ
    II
    Высококачественный код
    Рис. 5-9. Хороший интерфейс класса похож на верхушку айсберга:
    большую часть класса он оставляет скрытой
    Как и любой другой аспект проектирования, разработка интерфейса класса — итеративный процесс. Если приемлемый интерфейс класса не удается создать с первого раза, сделайте еще несколько попыток, пока он не стабилизируется. Если интерфейс не стабилизируется, попробуйте другой подход.
    Пример сокрытия информации
    Допустим, вы пишете программу, каждый объект которой должен иметь уникаль- ный идентификатор, хранящийся в переменной-члене
    id. Один подход к проек- тированию может заключаться в применении целочисленных идентификаторов и хранении максимального на данный момент идентификатора в глобальной переменной
    g_maxId. При создании новых объектов вы можете — скажем, в кон- структоре каждого объекта — просто выполнять команду
    id = ++g_maxId, что гарантирует уникальность идентификаторов, и требует абсолютно минимального кода при создании каждого объекта. Разве это может привести к каким-нибудь неприятностям?
    Может. Что, если вы захотите зарезервировать диапазоны идентификаторов для определенных целей? Что, если для повышения защищенности программы вы за- хотите назначать идентификаторы в другом порядке? А если вы захотите повтор- но задействовать идентификаторы уничтоженных объектов? Или включить в про- грамму диагностический тест, проверяющий, не превысило ли число идентифи- каторов допустимый предел? Если, назначая идентификаторы, вы распространите команды
    id = ++g_maxId по всей программе, вам придется изменить каждую из них. Кроме того, этот подход небезопасен в многопоточной среде.
    Способ генерации новых идентификаторов является тем аспектом проектирования, который следует скрыть. Применив команду
    ++g_maxId, вы раскроете сведения о том, что новый идентификатор создается просто путем увеличения переменной
    g_maxId. Если же вместо этого вы используете команды id = NewId(), вы скроете

    ГЛАВА
    5 Проектирование при конструировании
    91
    информацию о способе создания новых идентификаторов. Сам метод
    NewId() может состоять из единственной строки
    return ( ++g_maxId ) или ее эквивален- та, однако, если вы позднее решите зарезервировать определенные диапазоны идентификаторов для специфических целей или повторно использовать старые идентификаторы, вам придется изменить только метод
    NewId(), но не десятки команд
    id = NewId(). Какими бы сложными ни были изменения метода NewId(), они не повлияют ни на какую другую часть программы.
    Допустим теперь, что вам понадобилось изменить тип идентификатора с цело- численного на строковый. Если по всей программе у вас разбросаны объявления вроде
    int id, метод NewId() не поможет. В этом случае вам тоже придется просмо- треть всю программу и внести десятки или сотни изменений.
    Итак, тип идентификатора — это тоже секрет, который следует скрыть. Показывая, что идентификаторы — целые числа, вы поощряете программистов выполнять над ними такие операции, как
    >, < и =. Программируя на C++, вы могли бы не объявлять идентификаторы как
    int, а назначить им при помощи директивы typedef пользо- вательский тип
    IdType соответствующий тому же int. Или же вы могли бы создать простой класс
    IdType. Повторю еще раз: сокрытие аспектов проектирования по- зволяет значительно уменьшить объем кода, затрагиваемого изменениями.
    Сокрытие информации полезно на всех уровнях проектирования: от при- менения именованных констант вместо литералов до создания типов данных и проектирования классов, методов и подсистем.
    Две категории секретов
    Связанные с сокрытием информации секреты относятся к двум общим категориям:

    секреты, которые скрывают сложность, позволяя программистам забыть о ней при работе над остальными частями программы;

    секреты, которые скрывают источники изменений с целью локализации ре- зультатов возможных изменений.
    В число источников сложности входят сложные типы данных, файловые струк- туры, булевы тесты, запутанные алгоритмы и т. д. Источники изменений будут описаны немного позднее.
    Барьеры, препятствующие сокрытию информации
    В некоторых случаях скрыть информацию невозможно, однако большинство барьеров, препятствующих сокрытию информации, является умственными и обусловлены привы- канием к другим методикам.
    Избыточное распространение информации Зачастую сокрытию информации препятствует избыточное распро- странение информации по системе. Так, жесткое кодирование литерала
    100 во многих местах программы децентрализует ссылки на него. Лучше скрыть эту информацию в одном месте — скажем, при помощи константы
    MAX_EMPLOYEES, для изменения значения которой придется изменить только одну строку кода.
    Дополнительные сведения От- дельные фрагменты этого раз- дела взяты из статьи «Designing
    Software for Ease of Extension and
    Contraction» (Parnas, 1979).

    92
    ЧАСТЬ
    II
    Высококачественный код
    Еще один пример избыточного распространения информации — распределение по системе кода взаимодействия с пользователями. При этом в случае изменения способа взаимодействия — например, при замене графического интерфейса на интерфейс командной строки — придется изменить почти весь код. Лучше сконцен- трировать взаимодействие с пользователями в одном классе (пакете, подсистеме), который можно было бы изменить, не влияя на всю систему.
    В качестве другого примера приведу повсеместное исполь- зование глобального элемента данных, такого как массив данных о сотрудниках, поддерживающий до 1000 элементов.
    Если программа будет обращаться к глобальным данным напрямую, информация о реализации элемента данных — скажем, то, что это массив, способный включать до 1000 элементов, — распространится по всей программе. Если программа будет обращаться к данным только через методы доступа, детали реа- лизации будут известны только этим методам.
    Круговая зависимость Более тонким барьером, мешающим сокрытию инфор- мации, является круговая зависимость, когда, например, метод класса A вызывает метод класса B, а метод класса B — метод класса A.
    Избегайте таких зависимостей: они осложняют тестирование системы, не по- зволяя протестировать ни один из классов, пока не будет реализована хотя бы часть второго класса.
    Ошибочное представление о данных класса как о глобальных данных
    Если вы добросовестный программист, возможна еще одна преграда на пути к эффективному сокрытию информации: вы можете рассматривать данные класса как глобальные данные, избегая их из-за соответствующих проблем. Всем известно, что дорога в ад программирования вымощена глобальными переменными, однако использовать данные класса гораздо безопаснее.
    Глобальные данные имеют два главных недостатка: методы, обращающиеся к гло- бальным данным, не знают о том, что другие методы тоже обращаются к этим данным, или же методы знают об этом, но не знают, что именно другие методы делают с глобальными данными. Данные класса этих недостатков не имеют. Не- посредственный доступ к данным класса ограничен несколькими методами этого же класса, которые знают и о том, что другие методы также работают с данными, и о том, что это за методы.
    Конечно, это предполагает, что система включает грамотно спроектированные не- большие классы. Если программа использует огромные классы, включающие десятки методов, различие между данными класса и глобальными данными стирается, и данные класса приобретают многие недостатки, характерные для глобальных данных.
    Кажущееся снижение производительности Наконец,
    отказ от сокрытия информации может объясняться стремле- нием избежать снижения производительности и на уровне архитектуры, и на уровне кода. В обоих случаях волноваться не о чем. При проектировании архитектуры сокрытие информации не конфликтует с производительностью. Помня и о сокрытии информации, и о производитель- ности, вы сможете достичь обеих целей.
    Перекрестная ссылка О доступе к глобальным данным при по- мощи интерфейсов классов см. подраздел «Используйте мето- ды доступа вместо глобальных данных» раздела 13.3.
    Перекрестная ссылка О повы- шении производительности на уровне кода см. главы 25 и 26.

    ГЛАВА
    5 Проектирование при конструировании
    93
    Производительность на уровне кода вызывает еще больше беспокойств. Разработ- чикам кажется, что опосредованный доступ к данным снизит производительность программы в период выполнения из-за дополнительных затрат на создание объ- ектов, вызовы методов и т. д. Эти волнения преждевременны. Пока вы не оцените производительность системы и не найдете узкие места, лучшим способом под- готовки к повышению производительности на уровне кода является модульное проектирование. Позже, определив в коде «горячие точки», вы оптимизируете отдельные классы и методы, не затрагивая остальную часть системы.
    Важность сокрытия информации
    Сокрытие информации относится к тем немногим теоретическим под- ходам, польза которых уже долгое время неоспоримо подтверждается на практике (Boehm, 1987a). Было обнаружено, что крупные программы, использующие сокрытие информации, вчетверо легче модифицировать, чем про- граммы, его не использующие (Korson and Vaishnavi, 1986). Более того, сокрытие информации — один из основных принципов и структурного, и объектно-ори- ентированного проектирования.
    Сокрытие информации обладает уникальной эвристической силой, уникальной способностью подталкивать разработчиков к эффективным проектным решениям.
    Традиционное объектно-ориентированное проектирование предоставляет мощные эвристические средства моделирования мира в терминах объектов, но объектный подход не помог бы вам догадаться, что идентификатор следует объявить как
    IdType, а не как
    int. Разработчик, использующий объектно-ориентированный подход, спросил бы: «Рассматривать ли идентификатор как объект?» В зависимости от принятых в проекте стандартов кодирования утвердительный ответ мог бы означать, что программист должен написать конструктор, деструктор, операторы копирования и присваивания, закомментировать все это и сохранить в системе управления конфигурацией. Но скорее всего программист решил бы: «Нет, не стоит создавать целый класс ради какого-то идентификатора. Использую просто
    int».
    Смотрите: эффективный вариант проектирования — простое сокрытие типа дан- ных идентификатора — даже не был рассмотрен! Если бы вместо этого разработ- чик спросил: «Не скрыть ли информацию об идентификаторе?» — он, возможно, решил бы объявить собственный тип
    IdType как синоним int. Различие между объек- тно-ориентированным проектированием и сокрытием информации в этом при- мере не сводится к простому несоответствию явных правил и предписаний. При- нятое в соответствии с принципом сокрытия информации решение прекрасно согласуется с объектно-ориентированным подходом. Вместо этого различие от- носится к области эвристики: размышление над сокрытием информации может указать на такие варианты проектирования, которые при использовании объект- но-ориентированного подхода остались бы незамеченными.
    Сокрытие информации может пригодиться при проектировании открытого ин- терфейса класса. Теория и практика проектирования классов во многом расхо- дятся, и многие разработчики, решая, чт у
    включить в открытый интерфейс клас- са, думают прежде всего об удобном интерфейсе, а это обычно приводит к рас- крытию почти всей информации об устройстве класса. Опыт подсказывает мне,

    94
    ЧАСТЬ
    II
    Высококачественный код что некоторые программисты скорее раскрыли бы все закрытые данные класса, чем написали 10 дополнительных строк для защиты его секретов.
    Вопрос «Что этот класс должен скрывать?» обнажает самую суть проблемы про- ектирования интерфейса. Если функцию или данные можно включить в открытый интерфейс класса, не раскрыв его секретов, сделайте это. В противном случае воздержитесь от такого решения.
    Размышление о том, чт у
    скрыть, способствует принятию удачных решений на всех уровнях проектирования. Оно подталкивает к применению именованных констант вместо чисел на уровне конструирования, помогает выбирать удачные имена ме- тодов классов и их параметров и указывает на грамотные варианты декомпозиции и реализации взаимодействия классов и подсистем на уровне системы.
    Почаще задавайте себе вопрос «Что мне скрыть?», и вы удивитесь, сколь- ко проблем проектирования растает на ваших глазах
    Определите области вероятных изменений
    Исследования показали, что всем лучшим проектировщикам свойственно предвосхищать изменения (Glass, 1995). Обес- печение легкости адаптации программы к возможным изме- нениям относится к самым сложным аспектам проектирова- ния. Его цель заключается в изоляции нестабильных областей, позволяющей ограничить следствия изменений одним мето- дом, классом или пакетом. Вот как проходит подготовка к изменениям.
    1. Определите элементы, изменение которых кажется вероятным. Если вы выработали адекватные требования, они включают список потенциальных изменений и оценки вероятности каждого из них. В этом случае определить вероятные изменения легко. Если требования не описывают потенциальные изменения, ниже вы найдете список областей, которые меняются чаще всего независимо от типа проекта.
    2. Отделите элементы, изменение которых кажется вероятным. Создайте отдельный класс для каждого нестабильного компонента, определенного в п. 1, или разработайте классы, включающие несколько нестабильных компонентов, изменение которых скорее всего будет одновременным.
    3. Изолируйте элементы, изменение которых кажется вероятным. Спро- ектируйте интерфейсы между классами так, чтобы они не зависели от по- тенциальных изменений. Спроектируйте интерфейсы так, чтобы изменения ограничивались только внутренними частями классов. Изменение класса должно оставаться незаметным для любых других классов. Интерфейс класса должен защищать его секреты.
    Ниже описано несколько областей, изменяющихся чаще всего.
    Бизнес-правила Необходимость изменения ПО часто объясняется изменениями бизнес-правил. Оно и понятно: конгресс может изменить систему налогообложения, проф- союзы — пересмотреть условия контрактов и т. д. Если вы соблюдаете принцип сокрытия информации, логика, осно- ванная на этих правилах, не будет распространена на всю
    Дополнительные сведения Под- ход, описанный в этом разде- ле, взят из статьи «Designing
    Software for Ease of Extension and
    Contraction» (Parnas, 1979).
    Перекрестная ссылка Один из самых эффективных способов предвосхищения изменений — табличное управление (см. гла- ву 18).

    ГЛАВА
    5 Проектирование при конструировании
    95
    программу. Она будет скрыта в одном темном уголке системы, пока не придет время ее изменить.
    Зависимости от оборудования Примерами модулей, зависимых от оборудова- ния, могут служить интерфейсы между программой и разными типами мониторов, принтеров, клавиатур, дисководов, звуковых плат и сетевых устройств. Изолируй- те зависимости от оборудования в отдельной подсистеме или отдельном классе. Это облегчает адаптацию программы к новой аппаратной среде, а также помогает раз- рабатывать ПО для нестабильных версий устройств. Вы можете разработать ПО, моделирующее взаимодействие с конкретным устройством, и создать подсистему аппаратного интерфейса, использующую эту модель, пока устройство нестабиль- но или недоступно. Когда устройство будет готово к работе, подсистему интерфейса можно будет отключить от модели и подключить к устройству.
    Ввод-вывод На чуть более высоком в сравнении с аппаратными интерфейсами уровне проектирования частой областью изменений является ввод-вывод. Если ваше приложение создает собственные файлы данных, его усложнение вполне может потребовать изменения формата файлов. Аспекты формата ввода-вывода данных, относящиеся к пользовательскому уровню, такие как позиционирование и число полей на странице, их последовательность и т. д., изменяются не менее часто.
    В общем, анализ всех внешних интерфейсов на предмет возможных изменений —
    благоразумная идея.
    Нестандартные возможности языка Большинство версий языков поддер- живает нестандартные расширения, облегчающие работу программистов. Расшире- ния — палка о двух концах, потому что в другой среде — будь то другая аппаратная платформа, реализация языка другим производителем или новая версия языка, выпущенная тем же производителем, — они могут оказаться недоступны.
    Если вы применяете нестандартные расширения языка, скройте работу с ними в отдельном классе, чтобы его можно было заменить при адаптации приложения к другой среде. Аналогично, используя библиотечные методы, доступные не во всех средах, скройте их за интерфейсом, поддерживающим все нужные среды.
    Сложные аспекты проектирования и конструирования Скрывайте слож- ные аспекты проектирования и конструирования, потому что их частенько при- ходится реализовывать заново. Отделите их и минимизируйте влияние, которое может оказать их неудачное проектирование или конструирование на остальные части системы.
    Переменные статуса Переменные статуса характеризуют состояние програм- мы и изменяются чаще, чем большинство других видов данных. Так, разработчи- ки, определившие переменную статуса ошибки как булеву переменную, вполне могут позднее прийти к выводу, что для этого лучше было бы использовать пере- числение со значениями
    ErrorType_None, ErrorType_Warning и ErrorType_Fatal.
    Использование переменных статуса можно сделать более гибким и понятным минимум двумя способами.

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

    96
    ЧАСТЬ
    II
    Высококачественный код программы, а не масштабной ревизии всех фрагментов кода, выполняющих проверку переменной.

    Вместо непосредственной проверки переменной используйте методы досту- па. Так вы сохраните возможность реализации более сложного механизма определения состояния. Например, если вы захотите проверять комбинацию переменной статуса ошибки и переменной текущего функционального состо- яния, вам будет легко реализовать это, если проверка будет скрыта в методе, и гораздо сложнее, если механизм проверки будет жестко закодирован во мно- гих местах программы.
    1   ...   10   11   12   13   14   15   16   17   ...   104


    написать администратору сайта