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

  • ЧАСТЬ IV

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по


    Скачать 5.88 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    АнкорСовершенный код
    Дата31.03.2023
    Размер5.88 Mb.
    Формат файлаpdf
    Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
    ТипРуководство
    #1028502
    страница56 из 106
    1   ...   52   53   54   55   56   57   58   59   ...   106
    ГЛАВА 19 Общие вопросы управления
    443
    // Обрабатываем транзакцию в зависимости от ее типа.
    if ( transaction.Type == TransactionType_Deposit ) {
    // Обрабатываем транзакциювклад.
    if ( transaction.AccountType == AccountType_Checking ) {
    if ( transaction.AccountSubType == AccountSubType_Business )
    MakeBusinessCheckDep( transaction.AccountNum, transaction.Amount );
    else if ( transaction.AccountSubType == AccountSubType_Personal )
    MakePersonalCheckDep( transaction.AccountNum, transaction.Amount );
    else if ( transaction.AccountSubType == AccountSubType_School )
    MakeSchoolCheckDep( transaction.AccountNum, transaction.Amount );
    }
    else if ( transaction.AccountType == AccountType_Savings )
    MakeSavingsDep( transaction.AccountNum, transaction.Amount );
    else if ( transaction.AccountType == AccountType_DebitCard )
    MakeDebitCardDep( transaction.AccountNum, transaction.Amount );
    else if ( transaction.AccountType == AccountType_MoneyMarket )
    MakeMoneyMarketDep( transaction.AccountNum, transaction.Amount );
    else if ( transaction.AccountType == AccountType_Cd )
    MakeCDDep( transaction.AccountNum, transaction.Amount );
    }
    else if ( transaction.Type == TransactionType_Withdrawal ) {
    // Обрабатываем снятие денег.
    if ( transaction.AccountType == AccountType_Checking )
    MakeCheckingWithdrawal( transaction.AccountNum, transaction.Amount );
    else if ( transaction.AccountType == AccountType_Savings )
    MakeSavingsWithdrawal( transaction.AccountNum, transaction.Amount );
    else if ( transaction.AccountType == AccountType_DebitCard )
    MakeDebitCardWithdrawal( transaction.AccountNum, transaction.Amount );
    }
    Вот транзакция перевода — TransactionType_Transfer.
    else if ( transaction.Type == TransactionType_Transfer ) {
    MakeFundsTransfer(
    transaction.SourceAccountType,
    transaction.TargetAccountType,
    transaction.AccountNum,
    transaction.Amount
    );
    }
    else {
    // Обрабатываем неизвестный тип транзакции.
    LogTransactionError( “Unknown Transaction Type”, transaction );
    }
    }
    Этот код сложен, но бывает и хуже. Он имеет всего четыре уровня вложенности,
    содержит комментарии и логические отступы, а его функциональная декомпози#
    ция достаточно адекватна, особенно для типа транзакции
    TransactionType_Transfer.
    И все же вы можете улучшить этот код, вынеся содержимое внутренних
    if#прове#
    рок в отдельные методы.
    >

    444
    ЧАСТЬ IV Операторы
    Пример правильно вложенного кода после декомпозиции на методы (C++)
    while ( !TransactionsComplete() ) {
    // Читаем транзакционную запись.
    transaction = ReadTransaction();
    // Обрабатываем транзакцию в зависимости от ее типа.
    if ( transaction.Type == TransactionType_Deposit ) {
    ProcessDeposit(
    transaction.AccountType,
    transaction.AccountSubType,
    transaction.AccountNum,
    transaction.Amount
    );
    }
    else if ( transaction.Type == TransactionType_Withdrawal ) {
    ProcessWithdrawal(
    transaction.AccountType,
    transaction.AccountNum,
    transaction.Amount
    );
    }
    else if ( transaction.Type == TransactionType_Transfer ) {
    MakeFundsTransfer(
    transaction.SourceAccountType,
    transaction.TargetAccountType,
    transaction.AccountNum,
    transaction.Amount
    );
    }
    else {
    // Обрабатываем неизвестный тип транзакции.
    LogTransactionError(“Unknown Transaction Type”, transaction );
    }
    }
    Код новых методов просто был изъят из исходного фраг#
    мента и оформлен в виде новых методов (они здесь не по#
    казаны). Такой код имеет несколько преимуществ. Во#пер#
    вых, двухуровневая вложенность делает структуру проще и понятнее. Во#вторых, вы можете читать, исправлять и отла#
    живать более короткий цикл
    while, помещающийся на од#
    ном экране — не требуется переходить между экранами или страницами напечатанного текста. В#третьих, при вынесе#
    нии функциональности в методы
    ProcessDeposit() и ProcessWithdrawal() приобре#
    таются все остальные преимущества модульности. В#четвертых, теперь легко можно увидеть, что этот код может быть преобразован в оператор
    case, что еще более упростит чтение:
    Перекрестная ссылка Этот спо- соб функциональной декомпози- ции особенно прост, если вы из- начально строили методы по ме- тодике, описанной в главе 9.
    О принципах функциональной де- композиции см. подраздел «Раз- деляй и властвуй» раздела 5.4.

    ГЛАВА 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).

    1   ...   52   53   54   55   56   57   58   59   ...   106


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