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

  • Пример правильно вложенного кода после декомпозиции и использования оператора case (C++)

  • Используйте более объектно-ориентированный подход

  • ЧАСТЬ IV

  • Пример хорошего кода, использующего полиморфизм и фабрику объекта (C++)

  • Пример хорошего кода для фабрики объекта (C++)

  • Перепроектируйте глубоко вложенный код

  • Сводка методик уменьшения глубины вложенности

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

  • 19.5. Основа программирования: структурное программирование

  • Три компонента структурного программирования В следующих разделах описаны три конструкции, составляющие основу структур- ного программирования.Последовательность

  • Примеры итераций на Visual Basic

  • 19.6. Управляющие структуры и сложность

  • Насколько важна сложность

  • Табл. 19-2. Способы подсчета точек принятия решения в методе

  • Что делать с этим измерением сложности

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

  • Контрольный список: вопросы по управляющим структурам

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


    Скачать 7.6 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    Дата18.05.2023
    Размер7.6 Mb.
    Формат файлаpdf
    Имя файлаCode_Complete.pdf
    ТипРуководство
    #1139697
    страница55 из 104
    1   ...   51   52   53   54   55   56   57   58   ...   104
    ГЛАВА 19 Общие вопросы управления
    445
    Пример правильно вложенного кода после декомпозиции
    и использования оператора case (C++)
    while ( !TransactionsComplete() ) {
    // Читаем транзакционную запись.
    transaction = ReadTransaction();
    // Обрабатываем транзакцию в зависимости от ее типа.
    switch ( transaction.Type ) {
    case ( TransactionType_Deposit ):
    ProcessDeposit(
    transaction.AccountType,
    transaction.AccountSubType,
    transaction.AccountNum,
    transaction.Amount
    );
    break;
    case ( TransactionType_Withdrawal ):
    ProcessWithdrawal(
    transaction.AccountType,
    transaction.AccountNum,
    transaction.Amount
    );
    break;
    case ( TransactionType_Transfer ):
    MakeFundsTransfer(
    transaction.SourceAccountType,
    transaction.TargetAccountType,
    transaction.AccountNum,
    transaction.Amount
    );
    break;
    default:
    // Обрабатываем неизвестный тип транзакции.
    LogTransactionError(“Unknown Transaction Type”, transaction );
    break;
    }
    }
    Используйте более объектно-ориентированный подход Самый прямой под- ход для упрощения именно этого кода в объектно-ориентированной среде состоит в создании абстрактного базового класса
    Transaction и его подклассов Deposit,
    Withdrawal и Transfer.

    446
    ЧАСТЬ IV Операторы
    Пример хорошего кода, использующего полиморфизм (C++)
    TransactionData transactionData;
    Transaction *transaction;
    while ( !TransactionsComplete() ) {
    // Читаем транзакционную запись.
    transactionData = ReadTransaction();
    // Создаем объект транзакции в зависимости от ее типа.
    switch ( transactionData.Type ) {
    case ( TransactionType_Deposit ):
    transaction = new Deposit( transactionData );
    break;
    case ( TransactionType_Withdrawal ):
    transaction = new Withdrawal( transactionData );
    break;
    case ( TransactionType_Transfer ):
    transaction = new Transfer( transactionData );
    break;
    default:
    // Обрабатываем неизвестный тип транзакции.
    LogTransactionError(“Unknown Transaction Type”, transactionData );
    return;
    }
    transaction->Complete();
    delete transaction;
    }
    В системах любого размера оператор
    switch можно заменить вызовом специаль- ного метода фабрики объекта, который может повторно использоваться в любом месте, где нужно создать объект типа
    Transaction. Если бы этот код принадлежал такой системе, то он мог бы стать еще проще:
    Пример хорошего кода, использующего
    полиморфизм и фабрику объекта (C++)
    TransactionData transactionData;
    Transaction *transaction;
    while ( !TransactionsComplete() ) {
    // Читаем транзакционную запись и выполняем транзакцию.
    transactionData = ReadTransaction();
    transaction = TransactionFactory.Create( transactionData );
    transaction->Complete();
    delete transaction;
    }

    ГЛАВА 19 Общие вопросы управления
    447
    Чтобы вам было понятно, код метода
    TransactionFactory-
    .Create() представляет собой простую адаптацию операто- ра
    switch из предыдущего примера:
    Пример хорошего кода для фабрики объекта (C++)
    Transaction *TransactionFactory::Create(
    TransactionData transactionData
    ) {
    // Создаем объект транзакции на основе ее типа.
    switch ( transactionData.Type ) {
    case ( TransactionType_Deposit ):
    return new Deposit( transactionData );
    break;
    case ( TransactionType_Withdrawal ):
    return new Withdrawal( transactionData );
    break;
    case ( TransactionType_Transfer ):
    return new Transfer( transactionData );
    break;
    default:
    // Обрабатываем неизвестный тип транзакции.
    LogTransactionError( “Unknown Transaction Type”, transactionData );
    return NULL;
    }
    }
    Перепроектируйте глубоко вложенный код Некоторые эксперты утверждают,
    что операторы
    case в объектно-ориентированном программировании практически всегда сигнализируют о плохо факторизованном коде и должны использоваться как можно реже (Meyer, 1997). И показанное преобразование из операторов
    case,
    вызывающих методы, к фабрике объекта с вызовами полиморфных методов — один из таких примеров.
    Обобщая, можно сказать, что сложный код — это признак того, что вы недоста- точно хорошо понимаете свою программу, чтобы сделать ее простой. Глубокая вложенность — это знак, предупреждающий о том, что нужно добавить вызов метода или перепроектировать сложную часть кода. Это не значит, что вы обяза- ны переписать весь метод, но у вас должна быть веская причина не делать этого.
    Сводка методик уменьшения глубины вложенности
    Далее перечислены способы, позволяющие уменьшить вложенность. Рядом ука- заны ссылки на разделы этой книги, в которых эти способы обсуждаются:

    повторная проверка части условия (этот раздел);

    конвертирование в блоки
    if-then-else (этот раздел);
    Перекрестная ссылка Дополни- тельные полезные улучшения кода наподобие этого см. в гла- ве 24.

    448
    ЧАСТЬ IV Операторы

    преобразование к оператору
    case (этот раздел);

    факторизация глубоко вложенного кода в отдельный метод (этот раздел);

    использование объектной и полиморфной диспетчеризации (этот раздел);

    изменение кода с целью использования статусной переменной (раздел 17.3);

    использование сторожевых операторов для выхода из метода и пояснения номинального хода алгоритма (раздел 17.1);

    использование исключений (раздел 8.4);

    полное перепроектирование глубоко вложенного кода (этот раздел).
    19.5. Основа программирования:
    структурное программирование
    Термин «структурное программирование» был введен в исторической статье «Struc- tured Programming», представленной Эдсжером Дейкстрой на конференции НАТО
    по разработке ПО в 1969 году (Dijkstra, 1969). С тех самых пор термин «структур- ный» применялся к любой деятельности в области разработки ПО, включая струк- турный анализ, структурный дизайн и структурное валяние дурака. Различные структурные методики не имели между собой ничего общего, кроме того, что все они создавались в то время, когда слово «структурный» придавало им большую значимость.
    Суть структурного программирования состоит в простой идее: программа должна использовать управляющие конструкции с одним входом и одним выходом. Такая конструкция представляет собой блок кода, в котором есть только одно место, где он может начинаться, и одно — где может заканчиваться. У него нет других входов и выходов. Структурное программирование — это не то же самое, что и структур- ное проектирование сверху вниз. Оно относится только к уровню кодирования.
    Структурная программа пишется в упорядоченной, дисциплинированной мане- ре и не содержит непредсказуемых переходов с места на место. Вы можете чи- тать ее сверху вниз, и практически так же она выполняется. Менее дисциплини- рованные подходы приводят к такому исходному коду, который содержит менее понятную и удобную для чтения картину того, как программа выполняется. Меньшая читабельность означает худшее понимание и в конце концов худшее качество программы.
    Главные концепции структурного программирования, касающиеся вопросов исполь- зования
    break, continue, throw, catch, return и других тем, применимы до сих пор.
    Три компонента структурного программирования
    В следующих разделах описаны три конструкции, составляющие основу структур- ного программирования.
    Последовательность
    Последовательность — это набор операторов, выполняющих- ся по порядку. Типичные последовательные операторы содер- жат присваивания и вызовы методов. Вот два примера:
    Перекрестная ссылка Об ис- пользовании последовательно- стей см. главу 14.

    ГЛАВА 19 Общие вопросы управления
    449
    Примеры последовательного кода (Java)
    // Последовательность операторов присваивания.
    a = “1”;
    b = “2”;
    c = “3”;
    // Последовательность вызовов методов.
    System.out.println( a );
    System.out.println( b );
    System.out.println( c );
    Выбор
    Выбор — это такая управляющая конструкция, которая зас- тавляет операторы выполняться избирательно. Наиболее час- тый пример — выражение
    if-then-else. Выполняется либо блок
    if-then, либо else, но не оба сразу. Один из блоков «выбирается» для выполнения.
    Оператор
    case — другой пример управляющего элемента выбора. Оператор switch
    в C++ и Java, оператор
    select — все это примеры case. В каждом случае для выпол- нения выбирается один из вариантов. Концептуально операторы
    if и case похо- жи. Если ваш язык не поддерживает операторы
    case, вы можете эмулировать их с помощью набора
    if. Вот два примера выбора:
    Пример выбора (Java)
    // Выбор в операторе if.
    if ( totalAmount > 0.0 ) {
    // Делаем что-то.
    }
    else {
    // Делаем что-то еще.
    }
    // Выбор в операторе case.
    switch ( commandShortcutLetter ) {
    case ‘a’:
    PrintAnnualReport();
    break;
    case ‘q’:
    PrintQuarterlyReport();
    break;
    case ‘s’:
    PrintSummaryReport();
    break;
    default:
    DisplayInternalError( “Internal Error 905: Call customer support.” );
    }
    Перекрестная ссылка Об исполь- зовании выбора см. главу 15.

    450
    ЧАСТЬ IV Операторы
    Итерация
    Итерация — это управляющая структура, которая заставля- ет группу операторов выполняться несколько раз. Итерацию обычно называют «циклом». К итерациям относятся струк- туры
    For-Next в Visual Basic и while и for в C++ и Java. Этот фрагмент кода содержит примеры итераций на Visual Basic:
    Примеры итераций на Visual Basic
    ‘ Пример итерации в виде цикла For.
    For index = first To last
    DoSomething( index )
    Next
    ’ Пример итерации в виде цикла while.
    index = first
    While ( index <= last )
    DoSomething ( index )
    index = index + 1
    Wend
    ’ Пример итерации в виде цикла с выходом.
    index = first
    Do
    If ( index > last ) Then Exit Do
    DoSomething ( index )
    index = index + 1
    Loop
    Основной тезис структурного программирования гласит, что любая управляющая логика программы может быть реализована с помощью трех конструкций: пос- ледовательности, выбора и итерации (B
    ö
    hm Jacopini, 1966). Программисты иног- да предпочитают языковые конструкции, увеличивающие удобство, но програм- мирование, похоже, развивается во многом благодаря ограничению того, что мы можем делать на наших языках программирования. До введения структурного про- граммирования использовать
    goto представлялось очень удобным, но код, напи- санный таким образом, оказался малопонятным и не поддающимся сопровожде- нию. Я считаю, что использование любых управляющих структур, отличных от этих трех стандартных конструкций, т. е.
    break, continue, return, throw-catch и т. д., дол- жны рассматриваться под критическим углом зрения.
    19.6. Управляющие структуры и сложность
    Одна из причин, по которой столько внимания уделялось управляющим структу- рам, заключается в том, что они вносят большой вклад в общую сложность про- граммы. Неправильное применение управляющих структур увеличивает сложность,
    правильное — уменьшает ее.
    Перекрестная ссылка Об ис- пользовании итераций см. гла- ву 16.

    ГЛАВА 19 Общие вопросы управления
    451
    Одной из единиц измерения «программной сложности» яв- ляется число воображаемых объектов, которые вам прихо- дится одновременно держать в уме, чтобы разобраться в программе. Это умственное жонглирование — один из са- мых сложных аспектов программирования и причина того,
    что программирование требует большей сосредоточенности, чем другие виды деятельности. По этой причине программисты не любят, когда их «ненадолго прерывают» — такие перерывы равносильны просьбе жонглеру продолжать под- кидывать три мяча и подержать вашу сумку с продуктами.
    Интуитивно понятно, что сложность программы во многом определя- ется количеством усилий, требуемых для ее понимания. Том Маккейб (Tom
    McCabe) опубликовал важную статью, утверждающую, что сложность про- граммы определяется ее управляющей логикой (1976). Другие исследователи об- наружили дополнительные факторы, кроме предложенного Маккейбом циклома- тического показателя сложности (например, количество переменных, использу- емых в программе), но они согласны, что управляющая логика — одна из глав- ных составляющих сложности, если не самая главная.
    Насколько важна сложность?
    Исследователи в области вычислительной техники уже на протяжении двух десятилетий осознают важность пробле- мы сложности. Много лет назад Дейкстра предупреждал об опасности сложности: «Компетентный программист полно- стью осознает строго ограниченные размеры своего чере- па, поэтому подходит к задачам программирования со всей возможной скромно- стью» (Dijkstra, 1972). Из этого не следует, что вам нужно увеличить объем ваше- го черепа, чтобы иметь дело с невероятной сложностью. Это предполагает, что вы можете никогда не связываться с чрезмерной сложностью и всегда должны пред- принимать шаги для ее уменьшения.
    Сложность управляющей логики имеет большое значение, потому что она коррелирует с низкой надежностью и частыми ошибками (McCabe, 1976,
    Shen et al., 1985). Вильям Т. Уорд (William T. Ward) сообщал о значитель- ном выигрыше в надежности ПО, полученном в Hewlett-Packard в результате при- менения показателя сложности Маккейба (1989b). Этот показатель использовал- ся для идентификации проблемных участков в одной программе длиной 77 000
    строк. Коэффициент ошибок после выпуска этой программы составил 0,31 ошибку на 1000 строк кода. Коэффициент ошибок в программе длиной 125 000 строк не превышал 0,02 ошибки на 1000 строк кода. Ворд отметил, что из-за своей мень- шей сложности обе программы имели значительно меньше дефектов, чем другие программы в Hewlett-Packard. Моя компания Construx Software в 2000 г. получила похожие результаты при использовании средств измерения сложности для поис- ка проблемных методов.
    Делайте вещи настолько про- стыми, насколько это возмож- но, но не проще.
    Альберт Эйнштейн
    Перекрестная ссылка О сложно- сти см. подраздел «Главный Тех- нический Императив ПО: управ- ление сложностью» раздела 5.2.

    452
    ЧАСТЬ IV Операторы
    Общие принципы уменьшения сложности
    Вы можете бороться со сложностью двумя способами. Во-первых, вы можете улуч- шить свои способности к умственному жонглированию, выполняя специальные упражнения. Но программирование само по себе — уже хорошее упражнение, а люди, похоже, сталкиваются с трудностями при жонглировании б
    ó
    льшим коли- чеством, чем от пяти до девяти воображаемых сущностей (Miller, 1956). Так что потенциал улучшения невелик. Во-вторых, вы можете уменьшить сложность ва- ших программ и количество усилий, прилагаемых для их понимания.
    Как измерить сложность
    У вас, возможно, есть интуитивное ощущение того, что де- лает метод более или менее сложным. Исследователи пыта- ются формализовать свои чувства и приводят несколько способов измерения сложности. Возможно, самый важный способ предложил Том Маккейб, Сложность в нем измеря- ется с помощью подсчета количества «точек принятия решения» в методе
    (табл. 19-2):
    Табл. 19-2. Способы подсчета точек принятия решения в методе
    1. Начните считать с 1 на некотором участке кода.
    2. Добавляйте 1 для каждого из следующих ключевых слов или их эквивалентов:
    if while repeat for and or.
    3. Добавляйте 1 для каждого варианта в операторе
    case.
    Приведем пример:
    if ( ( (status = Success) and done ) or
    ( not done and ( numLines >= maxLines ) ) ) then ...
    В этом фрагменте вы начинаете считать с 1, получаете 2 для
    if, 3 — для and, 4 —
    для
    or и 5 — для and. Таким образом, этот фрагмент содержит всего пять точек принятия решения.
    Что делать с этим измерением сложности
    Посчитав количество точек принятия решения, вы можете использовать это чис- ло для анализа сложности вашего метода:
    0–5
    Этот метод, возможно, в порядке.
    6–10
    Начинайте думать о способах упрощения метода.
    10+
    Вынесите часть кода в отдельный метод и вызывайте его.
    Перенос части метода в другой метод не упрощает программу — он просто пере- мещает точки принятия решения. Но он уменьшает сложность, с которой вам при- ходится иметь дело в каждый момент времени. Поскольку одна из главных целей состоит в минимизации количества элементов, которыми приходится мысленно жонглировать, то уменьшение сложности отдельного метода дает свой результат.
    Дополнительные сведения Опи- санный здесь подход основан на важной статье Тома Маккейба
    «A Complexity Measure» (1976).

    ГЛАВА 19 Общие вопросы управления
    453
    Максимум в 10 точек принятия решения не является абсолютным ограничением.
    Используйте количество этих точек как сигнал, предупреждающий о том, что метод,
    возможно, стоит перепроектировать. Не считайте его неколебимым правилом.
    Оператор
    case со многими вариантами может иметь более 10 элементов, но в зависимости от назначения
    case может быть глупо разбивать его на части.
    Другие виды сложности
    Измерение сложности, предложенное Маккейбом, — не единственный значимый показатель, но он наиболее широко обсуждался в компьютерной литературе и особенно поле- зен при рассмотрении управляющей логики. Другие пока- затели включают количество используемых данных, число уровней вложенности в управляющих конструкциях, число строк кода, число строк между успешными обращениями к переменной («диапа- зон»), число строк, в которых используется переменная («время жизни») и объем ввода и вывода. Некоторые исследователи разработали составные показатели слож- ности, основанные на сочетании перечисленных простых вариантов.
    Контрольный список: вопросы
    по управляющим структурам
     Используют ли выражения идентификаторы true и false,
    а не 1 и 0?
     Сравниваются ли логические значения с true и false неявно?
     Сравниваются ли числовые значения со своими тестовыми значениями явно?
     Выполнено ли упрощение выражений с помощью введения новых логиче- ских переменных, использования логических функций и таблиц решений?
     Составлены ли логические выражения позитивно?
     Сбалансированы ли пары скобок?
     Используются ли скобки везде, где они необходимы для большей ясности?
     Заключены ли логические выражения в скобки целиком?
     Написаны ли условия в соответствии с расположением чисел на числовой прямой?
     Используются ли в программах на Java выражения вида a.equals(b), а не
    a == b там, где это необходимо?
     Очевидно ли применение пустых операторов?
     Выполнено ли упрощение глубоко вложенных выражений с помощью повтор- ной проверки части условия, преобразования в операторы if-then-else или
    case, перемещения части кода в отдельные методы, преобразования с ис- пользованием более обеъктно-ориентированной модели или они были улуч- шены как-то иначе?
     Если метод содержит более 10 точек принятия решения, есть ли хорошая причина, чтобы не перепроектировать его?
    Дополнительные сведения Отлич- ное обсуждение показателей сложности см. в «Software En–
    gineering Metrics and Models»
    (Conte, Dunsmore and Shen, 1986).
    http://cc2e.com/1985

    454
    ЧАСТЬ IV Операторы
    Ключевые моменты

    Упрощение и облегчение чтения логических выражений вносит существенный вклад в качество вашего кода.

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

    Структурное программирование — это простая, но все еще злободневная идея:
    вы можете построить любую программу с помощью комбинации последова- тельностей, выборов и итераций.

    Уменьшение сложности — ключ к написанию высококачественного кода.

    1   ...   51   52   53   54   55   56   57   58   ...   104


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