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

  • Резюме причин создания методов

  • 7.2. Проектирование на уровне методов

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

  • ГЛАВА 7

  • Описывайте все, что метод выполняет

  • Избегайте невыразительных и неоднозначных глаголов

  • Не используйте для дифференциации имен методов исключитель

  • Не ограничивайте длину имен методов искусственными правилами

  • Для именования процедуры используйте выразительный глагол, дополняя

  • Дисциплинированно используйте антонимы

  • Определяйте конвенции именования часто используемых операций

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


    Скачать 7.6 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    Дата18.05.2023
    Размер7.6 Mb.
    Формат файлаpdf
    Имя файлаCode_Complete.pdf
    ТипРуководство
    #1139697
    страница22 из 104
    1   ...   18   19   20   21   22   23   24   25   ...   104
    ГЛАВА 7 Высококачественные методы
    163
    else
    DeviceUnitsToPoints = 0
    end if
    End Function
    Если бы в коде по-прежнему использовалась первоначальная строка, мне пришлось бы повторить проверку десять раз, добавив в общей сложности 30 строк кода.
    Создание простого метода позволило уменьшить это число до 3.
    Резюме причин создания методов
    Вот список разумных причин создания методов:

    снижение сложности;

    формирование понятной промежуточной абстракции;

    предотвращение дублирования кода;

    поддержка наследования;

    сокрытие очередности действий;

    сокрытие операций над указателями;

    улучшение портируемости;

    упрощение сложных булевых проверок;

    повышение быстродействия.
    Кроме того, разумными причинами создания методов можно считать многие из причин создания классов:

    изоляция сложности;

    сокрытие деталей реализации;

    ограничение влияния изменений;

    сокрытие глобальных данных;

    создание центральных точек управления;

    облегчение повторного использования кода;

    выполнение специфического вида рефакторинга.
    7.2. Проектирование на уровне методов
    Идею связности впервые представили Уэйн Стивенс, Гленфорд Майерс и Ларри
    Константайн (Stevens, Myers, and Constantine, 1974). На уровне проектирования классов ее практически вытеснили более современные концепции, такие как аб- стракция и инкапсуляция, однако на уровне проектирования отдельных методов эвристический принцип связности по-прежнему полезен.
    В случае методов связность характеризует соответствие выполняемых в методе операций единой цели. Некоторые программисты предпочитают использовать термин «сила»
    (strength): насколько сильно связаны операции в методе? На- пример, метод
    Cosine() (косинус) имеет одну четко опреде- ленную цель и потому обладает прекрасной связностью. Метод
    CosineAndTan()
    (косинус и тангенс) имеет меньшую связность, потому что он выполняет сразу
    Перекрестная ссылка О связно- сти см. подраздел «Стремитесь к максимальной связности»
    раздела 5.3.

    164
    ЧАСТЬ II Высококачественный код две функции. Наша цель в том, чтобы каждый метод эффективно решал одну за- дачу и больше ничего не делал.
    Вознаграждением будет более высокая надежность кода. В одном иссле- довании 450 методов было обнаружено, что дефекты отсутствовали в 50%
    методов, обладающих высокой связностью, и только в 18% методов с низкой связностью (Card, Church, and Agresti, 1986). Другое исследование 450
    методов (это просто совпадение, хотя и весьма необычное) показало, что в срав- нении с методами, имеющими самое низкое отношение «сопряжение/связность»
    (coupling-to-cohesion), методы с максимальным отношением «сопряжение/связ- ность» содержали в 7 раз больше ошибок, а исправление этих методов было в 20
    раз более дорогим (Selby and Basili, 1991).
    Обсуждение связности обычно касается нескольких ее уровней. Понять эти кон- цепции важнее, чем запомнить специфические термины. Используйте концепции как средства, помогающие сделать методы максимально связными.
    Функциональная связность — самый сильный и лучший вид связности; она име- ет место, когда метод выполняет одну и только одну операцию. Примерами мето- дов, обладающих высокой связностью, являются методы
    sin() (синус), GetCusto-
    merName() (получить фамилию заказчика), EraseFile() (удалить файл), Calculate-
    LoanPayment() (вычислить плату за кредит) и AgeFromBirthdate() (определить воз- раст по дате рождения). Конечно, такая оценка связности предполагает, что эти методы соответствуют своим именам — иначе они имеют неудачные имена, а об их связности нельзя сказать ничего определенного.
    Ниже описаны другие виды связности, которые обычно считаются менее эффек- тивными.

    Последовательная связность (sequential cohesion) наблюдается в том случае,
    когда метод содержит операции, которые обязательно выполняются в опре- деленном порядке, используют данные предыдущих этапов и не формируют в целом единую функцию.
    Примером метода с последовательной связностью является метод, вычисляю- щий по дате рождения возраст сотрудника и срок до его ухода на пенсию. Если метод вычисляет возраст и затем использует этот результат для нахождения срока до ухода сотрудника на пенсию, он имеет последовательную связность.
    Если метод находит возраст сотрудника, после чего в абсолютно другом вы- числении определяет срок до ухода на пенсию, применяя те же данные о дате рождения, он имеет только коммуникационную связность.
    Как сделать такой метод функционально связным? Создать два отдельных ме- тода: метод, вычисляющий по дате рождения возраст сотрудника, и метод,
    определяющий по дате рождения срок до ухода сотрудника на пенсию. Вто- рой метод мог бы вызывать метод нахождения возраста. Оба этих метода име- ли бы функциональную связность. Другие методы могли бы вызывать любой из них или оба.

    Коммуникационная связность (communicational cohesion) имеет место, когда вы- полняемые в методе операции используют одни и те же данные и не связаны между собой иным образом. Если метод печатает отчет, после чего заново инициализи-

    ГЛАВА 7 Высококачественные методы
    165
    рует переданные в него данные, он имеет коммуникационную связность: две опе- рации объединяет только то, что они обращаются к одним и тем же данным.
    Чтобы повысить связность этого метода, выполняйте повторную инициализа- цию данных около места их создания, которое не должно находиться в мето- де печати отчета. Разделите операции на два метода: первый будет печатать отчет, а второй — выполнять повторную инициализацию данных неподалеку от кода, создающего или изменяющего данные. Вызовите оба этих метода вместо первоначального метода, имевшего коммуникационную связность.

    Временная связность (temporal cohesion) наблюдается, когда операции объе- диняются в метод на том основании, что все они выполняются в один интер- вал времени. Типичные примеры — методы
    Startup() (запуск программы) Comp-
    leteNewEmployee() (прием нового сотрудника на работу) и Shutdown() (завер- шение программы). Временную связность порой считают неприемлемой, по- скольку иногда она связана с плохими методиками программирования, таки- ми как включение слишком разнообразного кода в метод
    Startup().
    Для устранения этой проблемы рассматривайте методы с временной связнос- тью как способы организации других событий. Так, метод
    Startup() мог бы читать конфигурационный файл, инициализировать вспомогательный файл, настра- ивать менеджер памяти и выводить первоначальное окно программы. Чтобы сделать метод с временной связностью максимально эффективным, не выпол- няйте в нем конкретных операций непосредственно, а вызывайте для их вы- полнения другие методы. Тогда всем будет ясно, что суть метода — согласова- ние действий, а не их выполнение.
    Этот пример поднимает вопрос выбора имени, описывающего такой метод с адекватным уровнем абстракции. Вы могли бы назвать метод
    ReadConfigFileIn-
    itScratchFileEtc() (прочитать конфигурационный файл, инициализировать вспо- могательный файл и т. д.), но из этого следовало бы, что он имеет только слу- чайную связность. Если же вы назовете метод
    Startup(), будет очевидно, что он имеет одну цель и поэтому обладает функциональной связностью.
    Остальные виды связности обычно неприемлемы. Они приводят к созданию плохо организованного кода, который трудно отлаживать и изменять. Метод с плохой связностью лучше переписать, чем тратить время и средства на поиск проблем.
    Однако знание того, чего следует избегать, может пригодиться, поэтому ниже я привел описания плохих видов связности.

    Процедурная связность (procedural cohesion) имеет место, когда операции в методе выполняются в определенном порядке. В качестве примера можно при- вести метод, получающий фамилию сотрудника, затем его адрес, а после это- го номер телефона. Порядок этих операций важен только потому, что он со- ответствует порядку, в котором пользователя просят ввести данные. Остальные данные о сотруднике получает другой метод. В данном случае операции вы- полняются в определенном порядке и не объединены больше ничем, поэтому метод имеет процедурную связность.
    Для достижения лучшей связности поместите разные операции в отдельные методы. Сделайте так, чтобы вызывающий метод решал одну задачу, причем пол- ностью: пусть он соответствует имени
    GetEmployee() (получить данные о со-

    166
    ЧАСТЬ II Высококачественный код труднике), а не
    GetFirstPartOfEmployeeData() (получить первую часть данных о сотруднике). Вероятно, при этом придется изменить и методы, получающие остальные данные. Довольно часто достижение функциональной связности требует изменения двух или более первоначальных методов.

    Логическая связность (logical cohesion) имеет место, когда метод включает несколько операций, а выбор выполняемой операции осуществляется на ос- нове передаваемого в метод управляющего флага. Этот вид связности называ- ется логическим потому, что операции метода объединены только управляю- щей «логикой» метода: крупным оператором
    if или рядом блоков case. Какой- нибудь другой по-настоящему «логической» связи между операциями нет. По- скольку определяющим атрибутом логической связности является отсутствие отношений между операциями, возможно, лучше было бы назвать ее «нелогич- ной связностью».
    В качестве примера такого метода можно привести метод
    InputAll(), принима- ющий в зависимости от полученного флага фамилии клиентов, данные карт учета времени сотрудников или инвентаризационные данные. Другие приме- ры — методы
    ComputeAll(), EditAll(), PrintAll() и SaveAll(). Главная проблема с ними в том, что передавать флаг для управления работой метода нецелесообразно.
    Вместо метода, выполняющего одну из трех операций в зависимости от полу- ченного флага, лучше создать три метода, выполняющих по одной операции.
    Если операции используют некоторый одинаковый код или общие данные, код следует переместить в метод более низкого уровня, а методы упаковать в класс.
    Однако логически связный метод вполне приемлем, если его код состоит исключительно из ряда операторов
    if или case
    и вызовов других методов. Если единственная роль метода
    — координация выполнения команд и сам он не выполня- ет действий, это обычно удачное проектное решение. Такие методы еще называют «обработчиками событий». Обработ- чики часто используются в интерактивных средах, таких как
    Apple Macintosh, Microsoft Windows и других средах с GUI.

    При
    случайной связности (coincidental cohesion) каких- либо ясных отношений между выполняемыми в методе опе- рациями нет. Этот вариант можно еще называть «отсутстви- ем связности» или «хаотичной связностью». Низкокачествен- ный метод C++, приведенный в начале этой главы, имеет случайную связность.
    Случайную связность трудно преобразовать в более приемлемый вид связнос- ти — как правило, методы со случайной связностью нужно проектировать и реализовать заново.
    Никакой из этих терминов не является магическим или священным. Изу- чайте идеи, а не терминологию. Стремитесь создавать методы с функци- ональной связностью — это возможно почти всегда.
    Перекрестная ссылка Связность такого метода может быть удов- летворительной, однако при этом возникает один вопрос проектирования более высоко- го уровня: использовать ли опе- раторы case вместо полиморф- ного метода? См. также подраз- дел «Замена условных операто- ров (особенно многочисленных блоков case) на вызов полимор- фного метода» раздела 24.3.

    ГЛАВА 7 Высококачественные методы
    167
    7.3. Удачные имена методов
    Имя метода должно ясно описывать все, что он делает. Со- веты по выбору удачных имен методов приведены ниже.
    Описывайте все, что метод выполняет Опишите в имени метода все выходные данные и все побочные эффекты. Если метод вычис- ляет сумму показателей в отчете и открывает выходной файл, имя
    ComputeReport-
    Totals() не будет адекватным. ComputeReportTotalsAndOpenOutputFile() — имя адек- ватное, но слишком длинное и несуразное. Создавая методы с побочными эффек- тами, вы получите много длинных несуразных имен. Выход из этого положения
    — не использование менее описательных имен, а создание ясных методов без по- бочных эффектов.
    Избегайте невыразительных и неоднозначных глаголов Некоторые глаголы могут описывать практически любое действие. Имена вроде
    HandleCalculation(),
    PerformServices(), OutputUser(), ProcessInput() и DealWithOutput() не говорят о ра- боте методов почти ничего. В лучшем случае по этим именам можно догадаться,
    что методы имеют какое-то отношение к вычислениям, сервисам, пользователям,
    вводу и выводу соответственно. Исключением было бы использование глагола
    «handle» в специфическом техническом смысле обработки события.
    Иногда единственным недостатком метода является невыразительность его имени; сам метод при этом может быть спроектирован очень хоро- шо. Если имя
    HandleOutput() заменить на FormatAndPrintOutput(), роль метода станет очевидной.
    В других случаях невыразительность глагола в имени метода может объясняться аналогичным поведением метода. Неясная цель — невыразительное имя. Если это так, лучше всего выполнить реструктуризацию метода и всех родственных мето- дов, чтобы все они получили более четкие цели и более выразительные имена,
    точно их описывающие.
    Не используйте для дифференциации имен методов исключитель-
    но номера Один разработчик написал весь свой код в форме единствен- ного объемного метода. Затем он разбил код на фрагменты по 15 строк и создал методы
    Part1, Part2 и т. д. После этого он создал один высокоуровневый метод, вызывающий каждую часть кода. Подобный способ создания и именова- ния методов глуп до невозможности (и столь же редок, надеюсь). И все же про- граммисты иногда используют номера для дифференциации таких методов, как
    OutputUser, OutputUser1 и OutputUser2. Номера в конце каждого из этих имен ни- чего не говорят о различиях представляемых методами абстракций, поэтому та- кие имена нельзя признать удачными.
    Не ограничивайте длину имен методов искусственными правилами Ис- следования показывают, что оптимальная длина имени переменной равняется в среднем 9–15 символам. Как правило, методы сложнее переменных, поэтому и адекватные имена методов обычно длиннее. В то же время к именам методов ча- сто присоединяются имена объектов, что по сути предоставляет методам часть имени «бесплатно». Главной задачей имени метода следует считать как можно более ясное и понятное описание сути метода, поэтому имя может иметь любую длину,
    удовлетворяющую этой цели.
    Перекрестная ссылка Об имено- вании переменных см. главу 11.

    168
    ЧАСТЬ II Высококачественный код
    Для именования функции используйте описание воз-
    вращаемого значения Функция возвращает значение, и это следует должным образом отразить в ее имени. Так,
    имена
    cos(), customerId.Next(), printer.IsReady() и pen.Current-
    Color() ясно указывают, что возвращают функции, и потому являются удачными.
    Для именования процедуры используйте выразительный глагол, дополняя
    его объектом Процедура с функциональной связностью обычно выполняет опе- рацию над объектом. Имя должно отражать выполняемое процедурой действие и объект, над которым оно выполняется, что приводит нас к формату «глагол +
    объект». Примеры удачных имен процедур —
    PrintDocument(),CalcMonthlyRevenues(),
    CheckOrderInfo() и RepaginateDocument().
    В случае объектно-ориентированных языков имя объекта в имя процедуры вклю- чать не нужно, потому что объекты и так входят в состав вызовов, принимающих вид
    document. Print(), orderInfo. Check() и monthlyRevenues. Calc(). Имена вида docu-
    ment . PrintDocument() страдают от избыточности и могут стать в производных классах неверными. Если
    Check — класс, производный от класса Document, суть вы- зова
    check. Print() кажется очевидной: печать чека. В то же время вызов check.Print-
    Document() похож на печать записи чековой книжки или ежемесячной выписки со счета, но никак не чека.
    Дисциплинированно используйте антонимы Приме- нение конвенций именования, подразумевающих использо- вание антонимов, поддерживает согласованность имен, что облегчает чтение кода. Антонимы вроде first/last понятны всем. Пары вроде FileOpen() и _lclose() несимметричны и вызывают замешательство. Вот некоторые антонимы, попу- лярные в программировании:
    add/remove increment/decrement open/close begin/end insert/delete show/hide create/destroy lock/unlock source/target first/last min/max start/stop get/put next/previous up/down get/set old/new
    Определяйте конвенции именования часто используемых операций При работе над некоторыми системами важно различать разные виды операций. Са- мым легким и надежным способом определения этих различий часто оказывает- ся конвенция именования.
    В одном из моих проектов каждый объект имел уникальный идентификатор. Мы не потрудились выработать конвенцию именования методов, возвращающих иден- тификатор объекта, и в итоге получили такие имена, как:
    employee.id.Get()
    dependent.GetId()
    supervisor()
    candidate.id()
    Класс
    Employee предоставлял доступ к объекту id, который в свою очередь вклю- чал метод
    Get(). Класс Dependent предоставлял для этой цели метод GetId(). Разра-
    Перекрестная ссылка О разли- чии между процедурами и функ- циями см. раздел 7.6.
    Перекрестная ссылка Похожий список антонимов, используе- мых в именах переменных, см.
    в подразделе «Антонимы, час- то встречающиеся в именах переменных» раздела 11.1.

    1   ...   18   19   20   21   22   23   24   25   ...   104


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