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

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

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

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

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

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

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

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

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

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

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

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

  • Переписать с помощью

  • Код, избавившийся от goto с помощью try-finally (Visual Basic)

  • Сравнение рассмотренных подходов

  • Операторы goto и совместное использование кода в блоке else

  • Пример совместного использования кода в блоке else с помощью goto (C++)

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


    Скачать 7.6 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    Дата18.05.2023
    Размер7.6 Mb.
    Формат файлаpdf
    Имя файлаCode_Complete.pdf
    ТипРуководство
    #1139697
    страница49 из 104
    1   ...   45   46   47   48   49   50   51   52   ...   104
    ГЛАВА 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, используемых в предыдущем при- мере, и тем самым легче для понимания. Кроме того, он помещает действия, сле-
    >
    >
    >
    >

    ГЛАВА 17 Нестандартные управляющие структуры
    397
    дующие за проверкой
    if-then-else, ближе к месту самой проверки, чем в случае с вложенными
    if, и совсем не использует блоки else.
    Понимание версии с вложенными
    if требует некоторой умственной гимнастики.
    Вариант со статусной переменной легче для понимания, потому что лучше моде- лирует способ человеческого мышления. Вы ищете файл. Если все в порядке, вы открываете файл. Если все до сих пор в порядке, вы перезаписываете файл. Если все до сих пор в порядке…
    Недостаток этого подхода в том, что использование статусных переменных — не настолько распространенная практика, как хотелось бы. Подробно документируйте их применение, иначе некоторые программисты могут не понять, что вы имели в виду. В данном примере применение хорошо названных перечислимых типов оказывает существенную помощь.
    Переписать с помощью try-finally Некоторые языки, включая Visual Basic и
    Java, предоставляют конструкцию
    try-finally, которая может быть использована для очистки ресурсов в случае ошибки.
    Чтобы переписать пример, используя подход с
    try-finally, поместите код, который должен проверять возможные ошибки, в блок
    try, а код очистки — в блок finally.
    Блок
    try задает область обработки исключений, а finally выполняет любое осво- бождение ресурсов. Блок
    finally будет вызываться всегда независимо от того, бу- дет ли сгенерировано исключение и будет ли это исключение
    перехвачено в ме- тоде
    PurgeFiles().
    Код, избавившийся от goto с помощью try-finally (Visual Basic)
    ‘ Этот метод стирает группу файлов. Исключения передаются вызывающей стороне.
    Sub PurgeFiles()
    Dim fileIndex As Integer
    Dim fileToPurge As Data_File
    Dim fileList As File_List
    Dim numFilesToPurge As Integer
    MakePurgeFileList( fileList, numFilesToPurge )
    Try fileIndex = 0
    While ( fileIndex < numFilesToPurge )
    fileIndex = fileIndex + 1
    FindFile( fileList( fileIndex ), fileToPurge )
    OpenFile( fileToPurge )
    OverwriteFile( fileToPurge )
    Erase( fileToPurge )
    Wend
    Finally
    DeletePurgeFileList( fileList, numFilesToPurge )
    End Try
    End Sub
    Этот подход предполагает, что все вызовы функций в случае ошибки генерируют исключения, а не возвращают коды ошибок.

    398
    ЧАСТЬ IV Операторы
    Преимущество подхода с применением
    try-finally в том, что он проще, чем с goto
    и не использует
    goto. Кроме того, он позволяет избежать глубоко вложенных струк- тур
    if-then-else.
    Ограничением данного варианта с
    try-finally является то, что он должен быть последовательно реализован во всем коде. Если бы предыдущий пример был ча- стью программы, использующей коды ошибок наряду с исключениями, то коду исключения пришлось бы устанавливать код ошибки для всех возможных оши- бок, и это требование сделало бы фрагмент примерно таким же сложным, как и другие варианты.
    Сравнение рассмотренных подходов
    В защиту каждой из четырех приведенных методик есть что сказать. Подход с
    goto позволяет избежать глубокой вложен- ности и ненужных проверок, но, увы, он содержит
    goto. Под- ход с вложенными
    if позволяет обойтись без goto, но его глу- бокая вложенность преувеличивает картину логической слож- ности метода. Подход со статусной переменной избегает
    goto
    и глубокой вложенности, но добавляет дополнительные про- верки. И, наконец, подход с
    try-finally тоже позволяет избе- жать как
    goto, так и глубокой вложенности, но доступен не во всех языках.
    Вариант с
    try-finally наиболее предпочтителен в языках, предоставляющих такую конструкцию и в системах, еще не стандартизовавших какой-то иной подход. Если этот вариант невозможен, то подход со статусной переменной немного предпоч- тительнее, чем
    goto и вложенные if, так как он читабельнее и лучше моделирует задачу, однако это не делает его лучшим во всех ситуациях.
    Все эти методики работают хорошо, если последовательно применяются ко все- му коду проекта. Рассмотрите все плюсы и минусы, а затем примите решение на уровне проекта о том, какой подход предпочесть.
    Операторы goto и совместное использование
    кода в блоке else
    Одна из возможных ситуаций, в которой некоторые программисты захотят ис- пользовать
    goto, — это случай, когда у вас есть две проверки условия и блок else и вы хотите выполнить код одного из условий и блока
    else. Вот пример варианта,
    который может кого-нибудь подвигнуть к использованию
    goto:
    Пример совместного использования кода
    в блоке else с помощью goto (C++)
    if ( statusOk ) {
    if ( dataAvailable ) {
    importantVariable = x;
    goto MID_LOOP;
    }
    }
    Перекрестная ссылка Полный список методик, которые мож- но применять в аналогичных ситуациях, перечислен в подраз- деле «Сводка методик уменьше- ния глубины вложенности» раз- дела 19.4.

    1   ...   45   46   47   48   49   50   51   52   ...   104


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