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

  • Пример неправильного решения: вычисления факториала с помощью рекурсии (Java)

  • Пример правильного решения: использование итераций для вычисления факториала (Java:)

  • 17.3. Оператор goto

  • Аргументы против goto

  • Аргументы в защиту goto

  • Воображаемая дискуссия по поводу goto

  • Пример кода, который должен легко переписываться без goto (C++)

  • Пример предположительно эквивалентного кода, переписанного без goto (C++)

  • Пример действительно эквивалентного кода, переписанного без goto (C++)

  • Обработка ошибок и операторы goto

  • Пример кода с goto , который обрабатывает ошибки и освобождает ресурсы (Visual Basic)

  • Переписать с помощью вложенных операторов

  • Код, избавившийся от goto с помощью вложенных if (Visual Basic)

  • Переписать код с использованием статусной переменной

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

  • Код, избавившийся от goto с помощью статусной переменной (Visual Basic)

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


    Скачать 5.88 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    АнкорСовершенный код
    Дата31.03.2023
    Размер5.88 Mb.
    Формат файлаpdf
    Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
    ТипРуководство
    #1028502
    страница50 из 106
    1   ...   46   47   48   49   50   51   52   53   ...   106
    ГЛАВА 17 Нестандартные управляющие структуры
    389
    Пример неправильного решения: вычисления
    факториала с помощью рекурсии (Java)
    int Factorial( int number ) {
    if ( number == 1 ) {
    return 1;
    }
    else {
    return number * Factorial( number  1 );
    }
    }
    Не считая медленного выполнения и непредсказуемого использования памяти,
    рекурсивный вариант функции трудней для понимания, чем итеративный вариант:
    Пример правильного решения: использование
    итераций для вычисления факториала (Java:)
    int Factorial( int number ) {
    int intermediateResult = 1;
    for ( int factor = 2; factor <= number; factor++ ) {
    intermediateResult = intermediateResult * factor;
    }
    return intermediateResult;
    }
    Из этого примера можно усвоить три урока. Первый: учебники по ВычТеху не оказывают миру услугу своими примерами рекурсии. Второй, и более важный:
    рекурсия — гораздо более мощный инструмент, чем можно предположить из сби#
    вающих с толку примеров расчета факториала и чисел Фибоначчи. Третий — са#
    мый важный: вы должны рассмотреть альтернативные варианты перед использо#
    ванием рекурсии. Иногда один способ работает лучше, иногда — другой. Но прежде чем выбрать какой#то один, надо рассмотреть оба.
    17.3. Оператор goto
    Вы могли думать, что дебаты вокруг
    goto утихли, но корот#
    кая экскурсия по современным репозиториям исходного кода,
    таким как
    SourceForge.net, показывает, что goto все еще жив#
    здоров и глубоко укоренился на сервере вашей компании. Более того, современ#
    ные эквиваленты обсуждения
    goto до сих пор возникают под разными личинами,
    включая дебаты о множественных возвратах из методов, множественных выходах из цикла, именованных выходах из цикла, обработке ошибок и исключений.
    Аргументы против goto
    Основной аргумент против
    goto состоит в том, что код без goto — более качествен#
    ный. Знаменитое письмо Дейкстры «Go To Statement Considered Harmful» («Обо#
    снование пагубности оператора go to») в мартовском номере «Communications of the ACM» 1968 г. положило начало дискуссии. Дейкстра отметил, что качество кода http://cc2e.com/1785

    390
    ЧАСТЬ IV Операторы обратно пропорционально количеству
    goto, использованных программистом.
    В последующих работах Дейкстра утверждал, что корректность кода, не содержа#
    щего
    goto, доказать легче.
    Код с операторами
    goto трудно форматировать. Для демонстрации логической структуры используются отступы, а
    goto влияет на логическую структуру. Однако использовать отступы, чтобы показать логику
    goto и места его перехода, сложно или даже невозможно.
    Применение
    goto препятствует оптимизации, выполняемой компилятором. Неко#
    торые виды оптимизации зависят от порядка выполнения нескольких выражений подряд. Безусловный переход
    goto усложняет анализ кода и уменьшает возмож#
    ность оптимизации кода компилятором. Таким образом, даже если применение
    goto увеличивает эффективность на уровне исходного кода, суммарный эффект из#за невозможности оптимизации может уменьшиться.
    Сторонники операторов
    goto иногда приводят довод, что они делают программу быстрее и проще. Но код, содержащий
    goto, обычно не самый быстрый и корот#
    кий из всех возможных. Изумительная классическая статья Дональда Кнута «Struc#
    tured Programming with go to Statements» («Структурное программирование и операторы go to») содержит несколько примеров, в которых применение
    goto
    приводит к более медленному и объемному коду (Knuth, 1974).
    На практике применение операторов
    goto приводит к нарушению принципа, что нормальный ход алгоритма должен быть строго сверху вниз. Даже если
    goto при аккуратном использовании не сбивают с толку, как только они появляются, они начинают распространяться по коду, как термиты по разрушающемуся дому. Если разрешен хотя бы один
    goto, вместе с пользой в код проникает и вред, так что лучше вообще запретить использование этого оператора.
    В целом опыт двух десятилетий, прошедших с публикации письма Дейкстры по#
    казал всю недальновидность создания кода, перегруженного операторами
    goto.
    В своем обзоре литературы Бен Шнейдерман (Ben Shneiderman) сделал вывод, что факты свидетельствуют в пользу Дейкстры и нам лучше обходиться без
    goto (1980),
    а многие современные языки, включая Java, даже не содержат такой оператор.
    Аргументы в защиту goto
    Сторонники
    goto ратуют за осторожное применение оператора при определенных обстоятельствах, а не за неразборчивое употребление. Большинство аргументов против
    goto говорит именно о неразборчивом его использовании. Дискуссия о goto
    вспыхнула, когда Fortran был наиболее популярным языком. Fortran не имел при#
    личных циклов, и в отсутствие хорошего совета по поводу создания цикла с помо#
    щью
    goto программисты написали кучу спагетти#кода. Такой код, несомненно, кор#
    релировал с выпуском низкокачественных программ, но это имело отдаленное отношение к аккуратному использованию
    goto, позволяющему заполнить пробел в возможностях, предоставляемых современными языками программирования.
    Правильно расположенный
    goto способен помочь избавиться от дублирования кода.
    Такой код создает проблемы, если две его части модифицируются по#разному. Дуб#
    лированный код увеличивает размер исходного и выполняемого файлов. Отри#

    ГЛАВА 17 Нестандартные управляющие структуры
    391
    цательный эффект применения
    goto перевешивается недостатками дублирован#
    ного кода.
    Оператор
    goto может пригодиться в методе, который сна#
    чала распределяет ресурсы, выполняет с ними какие#то опе#
    рации, а потом освобождает эти ресурсы. Используя
    goto, вы можете выполнять очистку в одном месте. Оператор
    goto
    уменьшает вероятность того, что вы забудете освободить ресурсы при обнаружении ошибки.
    Порой
    goto позволяет создать более быстрый и короткий код. Вышеупомянутая статья Кнута 1974 года рассматривает несколько вариантов, в которых
    goto дает ощутимое преимущество.
    Хорошее программирование не означает исключение всех
    goto. Систематическая декомпозиция, усовершенствование и разумный выбор управляющих структур обычно автоматически приводит к программам, не содержащим
    goto. Стремление к коду без
    goto — это не цель, а результат, и бесполезно заострять внимание ис#
    ключительно на устранении
    goto.
    Десятилетия исследований операторов
    goto не смогли про#
    демонстрировать их вредоносность. В обзоре литературы
    Б.А. Шейл (B.A. Sheil) сделал вывод, что нереалистичные тес#
    товые условия, плохой анализ данных и неубедительные ре#
    зультаты не подкрепляют заявления Шнейдермана и др., что число ошибок в коде пропорционально количеству
    goto
    (1981). Шейл не зашел так далеко, чтобы утверждать, что ис#
    пользование
    goto — хорошая идея, он лишь показал, что эк#
    спериментальные данные против этих операторов неубеди#
    тельны.
    И, наконец, операторы
    goto входят во множество современ#
    ных языков, включая Visual Basic, C++ и Ada — наиболее тщательно продуманный язык программирования в истории. Ada создавался уже после того, как были при#
    ведены все аргументы с обеих сторон дискуссии по
    goto, и после всестороннего рассмотрения вопроса разработчики Ada решили включить в него
    goto.
    Воображаемая дискуссия по поводу goto
    Отличительная особенность большинства обсуждений
    goto — поверхностность.
    Спорщик, утверждающий, что «
    goto — это зло», приводит тривиальный фрагмент кода, содержащий операторы
    goto, а затем показывает, как легко его можно пере#
    писать без
    goto. Это доказывает главным образом то, что тривиальный код можно легко написать и без
    goto.
    Спорщик, утверждающий: «Я не могу жить без
    goto», — обычно приводит случай,
    в котором исключение
    goto выливается в дополнительное сравнение или дубли#
    рование кода. Это доказывает в основном то, что есть случаи, в которых
    goto по#
    зволяет выполнить на одно сравнение меньше — незначительная выгода для со#
    временных компьютеров.
    Перекрестная ссылка О примене- нии операторов goto в коде, ис- пользующем ресурсы, см. ниже подраздел «Обработка ошибок и операторы goto». Об исключениях см. также раздел 8.4.
    Факты свидетельствуют лишь о том, что намеренно хаотичная управляющая структура ухудша- ет производительность [програм- миста]. Эти эксперименты не предоставили практически ни- какого доказательства полезно- го эффекта какого-то конкрет- ного способа структурирования управляющей логики.
    Б.А. Шейл

    392
    ЧАСТЬ IV Операторы
    Большинство учебников также не помогает. Они приводят простой пример пере#
    писывания некоторого кода без
    goto, как будто это все объясняет. Вот обманчи#
    вый пример тривиального фрагмента кода из такого учебника:
    Пример кода, который должен легко переписываться без goto (C++)
    do {
    GetData( inputFile, data );
    if ( eof( inputFile ) ) {
    goto LOOP_EXIT;
    }
    DoSomething( data );
    } while ( data != 1 );
    LOOP_EXIT:
    Книга быстро заменяет этот фрагмент кодом без
    goto:
    Пример предположительно эквивалентного кода, переписанного без goto (C++)
    GetData( inputFile, data );
    while ( ( !eof( inputFile ) ) && ( ( data != 1 ) ) ) {
    DoSomething( data );
    GetData( inputFile, data )
    }
    Этот так называемый «простой» пример содержит ошибку. В случае, когда пере#
    менная
    data равна %1, преобразованный код отслеживает %1 и выходит из цикла до выполнения
    DoSomething(). Исходный код выполняет DoSomething() до того, как
    %1 обнаружена. Автор книги по программированию, пытаясь показать, как легко можно кодировать без
    goto, преобразовал собственный же пример некорректно.
    Но ему не стоит расстраиваться — другие книги содержат похожие ошибки. Даже профессионалы сталкиваются с трудностями при преобразовании кода, исполь#
    зующего
    goto.
    Вот более точная реорганизация кода без
    goto:
    Пример действительно эквивалентного кода, переписанного без goto (C++)
    do {
    GetData( inputFile, data );
    if ( !eof( inputFile )) {
    DoSomething( data );
    }
    } while ( ( data != 1 ) && ( !eof( inputFile ) ) );
    Даже при правильном преобразовании кода этот пример все же искусственный,
    потому что он показывает тривиальный вариант использования
    goto. Это не тот случай, когда толковые программисты выбирают
    goto в качестве предпочтитель#
    ной формы управления.
    В наши дни уже тяжело добавить что#нибудь стоящее к теоретическим дебатам вокруг
    goto. Однако на что обычно не обращают внимания, так это на ситуации, в кото#

    ГЛАВА 17 Нестандартные управляющие структуры
    393
    рых программист, полностью представляя себе альтернативы без
    goto, все же ре#
    шает использовать его для улучшения читабельности и качества сопровождения.
    Следующие разделы представляют случаи, в которых некоторые опытные програм#
    мисты приводят доводы в пользу
    goto. В обсуждении рассматриваются примеры кода с операторами
    goto и кода, переписанного без их использования, и оцени#
    ваются достоинства и недостатки этих версий.
    Обработка ошибок и операторы goto
    Создание высокоинтерактивного кода заставляет обращать особое внимание на обработку ошибок и освобождение ресурсов в случае возникновения ошибки.
    Следующий пример стирает группу файлов. Метод сначала получает группу фай#
    лов для удаления, затем находит каждый файл, открывает его, перезаписывает, а затем удаляет. Метод проверяет возникновение ошибок на каждом шаге.
    Пример кода с goto, который обрабатывает
    ошибки и освобождает ресурсы (Visual Basic)
    ‘ Этот метод стирает группу файлов.
    Sub PurgeFiles( ByRef errorState As Error_Code )
    Dim fileIndex As Integer
    Dim fileToPurge As Data_File
    Dim fileList As File_List
    Dim numFilesToPurge As Integer
    MakePurgeFileList( fileList, numFilesToPurge )
    errorState = FileStatus_Success fileIndex = 0
    While ( fileIndex < numFilesToPurge )
    fileIndex = fileIndex + 1
    If Not ( FindFile( fileList( fileIndex ), fileToPurge ) ) Then errorState = FileStatus_FileFindError
    Здесь используется GoTo.
    GoTo END_PROC
    End If
    If Not OpenFile( fileToPurge ) Then errorState = FileStatus_FileOpenError
    Здесь используется GoTo.
    GoTo END_PROC
    End If
    If Not OverwriteFile( fileToPurge ) Then errorState = FileStatus_FileOverwriteError
    >
    >

    394
    ЧАСТЬ IV Операторы
    Здесь используется GoTo.
    GoTo END_PROC
    End If if Not Erase( fileToPurge ) Then errorState = FileStatus_FileEraseError
    Здесь используется GoTo.
    GoTo END_PROC
    End If
    Wend
    Здесь находится метка GoTo.
    END_PROC:
    DeletePurgeFileList( fileList, numFilesToPurge )
    End Sub
    Этот метод — типичный пример обстоятельств, при которых опытные програм#
    мисты решают использовать
    goto. Похожее случается, когда методу надо выделить и освободить такие ресурсы, как соединения с базами данных, память или вре#
    менные файлы. Альтернативой
    goto в таких ситуациях обычно является дублиро#
    вание кода для очистки ресурсов. В подобных случаях программист может срав#
    нить нежелательность применения
    goto с головной болью от сопровождения дуб#
    лированного кода и решить, что
    goto — меньшее зло.
    Вы можете переписать предыдущий пример без
    goto несколькими способами, и все они будут иметь как плюсы, так и минусы. Далее приведены возможные стра#
    тегии преобразования:
    Переписать с помощью вложенных операторов if При перезаписи с помощью вложенных
    if располагайте блоки if
    так, чтобы следующая проверка условия выполнялась, только если предыдущая завершилась успешно. Это стандартный,
    приводимый в учебниках подход к удалению операторов
    goto. Рассмотрим метод, переписанный с помощью стандарт#
    ного подхода:
    Код, избавившийся от goto с помощью вложенных if (Visual Basic)
    ‘ Этот метод стирает группу файлов.
    Sub PurgeFiles( ByRef errorState As Error_Code )
    Dim fileIndex As Integer
    Dim fileToPurge As Data_File
    Dim fileList As File_List
    Dim numFilesToPurge As Integer
    MakePurgeFileList( fileList, numFilesToPurge )
    errorState = FileStatus_Success fileIndex = 0
    Перекрестная ссылка Этот ме- тод также можно переписать,
    используя операторы break и без goto. Об этом подходе см.
    подраздел «Досрочное заверше- ние цикла» раздела 16.2.
    >
    >
    >

    ГЛАВА 17 Нестандартные управляющие структуры
    395
    Условие While изменено — добавлена проверка errorState.
    While ( fileIndex < numFilesToPurge And errorState = FileStatus_Success )
    fileIndex = fileIndex + 1
    If FindFile( fileList( fileIndex ), fileToPurge ) Then
    If OpenFile( fileToPurge ) Then
    If OverwriteFile( fileToPurge ) Then
    If Not Erase( fileToPurge ) Then errorState = FileStatus_FileEraseError
    End If
    Else ‘ невозможно перезаписать файл errorState = FileStatus_FileOverwriteError
    End If
    Else ‘ невозможно открыть файл errorState = FileStatus_FileOpenError
    End If
    Else ‘ файл не найден
    Эта строка расположена через 13 строк после условия If, к которому она относится.
    errorState = FileStatus_FileFindError
    End If
    Wend
    DeletePurgeFileList( fileList, numFilesToPurge )
    End Sub
    Тому, кто привык программировать без
    goto, возможно, будет легче читать этот код, чем первоначальную версию. И если вы используете данный вариант, вам не придется предстать перед судом противников
    goto.
    Основной недостаток этого подхода с вложенными
    if в том,
    что уровень вложенности глубок, даже слишком. Для пони#
    мания кода вам нужно держать в голове весь набор вложен#
    ных
    if одновременно. Более того, расстояние между кодом обработки ошибок и кодом, ее инициирующим, слишком велико: например, выражение, присваивающее переменной
    errorState значение
    FileStatus_FileFindError, на 13 строк отстоит от соответствующей проверки if.
    В варианте с
    goto ни одно выражение не отстоит более чем на четыре строки от условия, которое его вызывает. И вам нет нужды держать в голове всю структуру одновременно. По сути вы можете игнорировать все предыдущие условия, выпол#
    ненные успешно, и сосредоточиться на следующей операции. В этом случае вер#
    сия с
    goto гораздо удобнее для чтения и сопровождения, чем с вложенными if.
    Переписать код с использованием статусной переменной Чтобы перепи#
    сать код с использованием статусной переменной (также называемой перемен#
    ной состояния), создайте переменную, которая будет показывать, не находится ли метод в состоянии ошибки. В нашем случае метод уже содержит статусную пере#
    менную
    errorState, так что вы можете использовать ее.
    Перекрестная ссылка Об отсту- пах и других вопросах размет- ки кода см. главу 31. Об уров- нях вложенности см. раздел 19.4.
    >
    >

    396
    ЧАСТЬ IV Операторы
    Код, избавившийся от goto с помощью статусной переменной (Visual Basic)
    ‘ Этот метод стирает группу файлов.
    Sub PurgeFiles( ByRef errorState As Error_Code )
    Dim fileIndex As Integer
    Dim fileToPurge As Data_File
    Dim fileList As File_List
    Dim numFilesToPurge As Integer
    MakePurgeFileList( fileList, numFilesToPurge )
    errorState = FileStatus_Success fileIndex = 0
    Условие While изменено — добавлена проверка errorState.
    While ( fileIndex < numFilesToPurge ) And ( errorState = FileStatus_Success )
    fileIndex = fileIndex + 1
    If Not FindFile( fileList( fileIndex ), fileToPurge ) Then errorState = FileStatus_FileFindError
    End If
    Проверяется статусная переменная.
    If ( errorState = FileStatus_Success ) Then
    If Not OpenFile( fileToPurge ) Then errorState = FileStatus_FileOpenError
    End If
    End If
    Проверяется статусная переменная.
    If ( errorState = FileStatus_Success ) Then
    If Not OverwriteFile( fileToPurge ) Then errorState = FileStatus_FileOverwriteError
    End If
    End If
    Проверяется статусная переменная.
    If ( errorState = FileStatus_Success ) Then
    If Not Erase( fileToPurge ) Then errorState = FileStatus_FileEraseError
    End If
    End If
    Wend
    DeletePurgeFileList( fileList, numFilesToPurge )
    End Sub
    Преимущество подхода со статусной переменной в том, что он позволяет избе#
    жать глубоко вложенных структур
    if%then%else, используемых в предыдущем при#
    мере, и тем самым легче для понимания. Кроме того, он помещает действия, сле#
    >
    >
    >
    >

    1   ...   46   47   48   49   50   51   52   53   ...   106


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