stringtranslate.com

Структурированное программирование

Структурное программирование — это парадигма программирования , направленная на повышение ясности, качества и сокращения времени разработки компьютерной программы за счет широкого использования структурированных конструкций потока управления выбора ( if/then/else ) и повторения ( while и for ), блочных структур. и подпрограммы .

Он возник в конце 1950-х годов с появлением языков программирования АЛГОЛ 58 и АЛГОЛ 60 , [1] причем последний включал поддержку блочных структур. Факторами, способствующими его популярности и широкому признанию, сначала в академических кругах, а затем среди практиков, являются открытие в 1966 году того, что сейчас известно как теорема о структурированной программе , [2] и публикация влиятельной книги « Перейти к утверждению, считающемуся вредным ». открытое письмо в 1968 году голландского учёного-компьютерщика Эдсгера В. Дейкстры , который ввёл термин «структурное программирование». [3]

Структурное программирование чаще всего используется с отклонениями, которые позволяют использовать более понятные программы в некоторых конкретных случаях, например, когда необходимо выполнить обработку исключений .

Элементы

Структуры управления

Согласно теореме о структурированной программе , все программы рассматриваются как состоящие из трех структур управления :

Графическое представление трех основных шаблонов — последовательности, выбора и повторения — с использованием диаграмм NS (синий) и блок-схем (зеленый).

Подпрограммы

Подпрограммы ; вызываемые модули, такие как процедуры, функции, методы или подпрограммы, используются для того, чтобы можно было ссылаться на последовательность с помощью одного оператора.

Блоки

Блоки используются для того, чтобы группы операторов можно было рассматривать как один оператор. Языки с блочной структурой имеют синтаксис для включения структур некоторым формальным способом, например, оператор if, заключенный в квадратные скобки, if..fiкак в ALGOL 68 , или раздел кода, заключенный в квадратные скобки BEGIN..END, как в PL/I и Pascal , отступы через пробелы , как в Python , или фигурные скобки {...}C и многих более поздних языков .

Структурированные языки программирования

Структурное программирование можно выполнять на любом языке программирования, хотя предпочтительнее использовать что-то вроде процедурного языка программирования . [ нужна цитация ] [ необходимы разъяснения ] Некоторые из языков, первоначально использовавшихся для структурного программирования, включают: ALGOL , Pascal , PL/I , Ada и RPL , но большинство новых языков процедурного программирования с тех пор включали функции для поощрения структурного программирования, а иногда и намеренно. опущены функции, в частности GOTO, чтобы усложнить неструктурированное программирование . Структурное программирование (иногда известное как модульное программирование ) обеспечивает логическую структуру написанной программы, чтобы сделать ее более эффективной, легкой для понимания и модификации.

История

Теоретическая основа

Теорема о структурированной программе обеспечивает теоретическую основу структурного программирования. Он утверждает, что трех способов объединения программ — последовательности, выбора и итерации — достаточно, чтобы выразить любую вычислимую функцию . Это наблюдение возникло не в рамках движения за структурированное программирование; этих структур достаточно для описания командного цикла центрального процессора , а также работы машины Тьюринга . Следовательно, в этом смысле процессор всегда выполняет «структурированную программу», даже если инструкции, которые он считывает из памяти, не являются частью структурированной программы. Однако авторы обычно приписывают этот результат статье Бема и Якопини 1966 года, возможно, потому, что Дейкстра сам цитировал эту статью. [4] Теорема о структурированной программе не рассматривает, как писать и анализировать полезно структурированную программу. Эти вопросы рассматривались в конце 1960-х и начале 1970-х годов при большом вкладе Дейкстры , Роберта В. Флойда , Тони Хоара , Оле-Йохана Даля и Дэвида Грайса .

Дебаты

П. Дж. Плаугер , один из первых последователей структурного программирования, описал свою реакцию на теорему о структурированной программе:

Мы, новообращенные, размахивали этой интересной новостью перед носом неперестроившихся программистов на языке ассемблера, которые продолжали выдвигать извилистые кусочки логики и говорили: «Держу пари, что я не могу это структурировать». Ни доказательство Бема и Якопини, ни наши неоднократные успехи в написании структурированного кода не привели их ни на день раньше, чем они были готовы убедить себя. [5]

Дональд Кнут принял принцип, согласно которому программы должны быть написаны с учетом доказуемости, но он не согласился с отменой оператора GOTO и по состоянию на 2018 год продолжал использовать его в своих программах. [6] В своей статье 1974 года «Структурное программирование с операторами Goto» [7] он привел примеры, в которых, по его мнению, прямой переход приводит к более четкому и эффективному коду без ущерба для доказуемости. Кнут предложил более слабое структурное ограничение: должна быть возможность нарисовать блок-схему программы со всеми прямыми ветвями слева, всеми обратными ветвями справа и без пересечений друг друга. Многие из тех, кто разбирается в компиляторах и теории графов, выступают за разрешение только приводимых потоковых графов [ когда они определены как? ] . [ ВОЗ? ]

Теоретики структурного программирования приобрели главного союзника в 1970-х годах после того, как исследователь IBM Харлан Миллс применил свою интерпретацию теории структурного программирования при разработке системы индексирования для исследовательского файла The New York Times . Проект имел большой инженерный успех, и менеджеры других компаний ссылались на него в поддержку внедрения структурированного программирования, хотя Дейкстра раскритиковал то, насколько интерпретация Миллса отличалась от опубликованной работы. [8]

Даже в 1987 году еще можно было поднять вопрос о структурном программировании в журнале по информатике. Фрэнк Рубин сделал это в том же году, направив открытое письмо под названием «ПЕРЕХОД Считается вредным», считается вредным». [9] Последовали многочисленные возражения, в том числе ответ Дейкстры, в котором резко критиковалась как Рубин, так и уступки, на которые пошли другие писатели, отвечая ему.

Исход

К концу 20 века почти все ученые-компьютерщики были убеждены, что изучать и применять концепции структурного программирования полезно. Языки программирования высокого уровня, в которых изначально отсутствовали структуры программирования, такие как FORTRAN , COBOL и BASIC , теперь имеют их.

Распространенные отклонения

Хотя сейчас goto в значительной степени заменен структурированными конструкциями выбора (if/then/else) и повторения ( while и for), лишь немногие языки имеют чисто структурированную структуру. Наиболее распространенным отклонением, встречающимся во многих языках, является использование оператора возврата для досрочного выхода из подпрограммы. Это приводит к появлению нескольких точек выхода вместо одной точки выхода, требуемой структурным программированием. Существуют и другие конструкции для обработки случаев, которые неудобны в чисто структурированном программировании.

Ранний выход

Наиболее распространенным отклонением от структурного программирования является ранний выход из функции или цикла. На уровне функций это returnутверждение. На уровне циклов это breakоператор (завершить цикл) или continueоператор (завершить текущую итерацию, перейти к следующей итерации). В структурированном программировании их можно воспроизвести, добавив дополнительные ветки или тесты, но для результатов из вложенного кода это может существенно усложнить работу. C — ранний и яркий пример таких конструкций. В некоторых новых языках также есть «помеченные разрывы», которые позволяют выйти не только из самого внутреннего цикла. Исключения также допускают досрочный выход, но имеют дополнительные последствия и поэтому рассматриваются ниже.

Множественные выходы могут возникнуть по разным причинам, чаще всего либо потому, что у подпрограммы больше нет работы (если она возвращает значение, то вычисление завершено), либо возникли «исключительные» обстоятельства, которые не позволяют ей продолжить работу, поэтому требуется Обработка исключений.

Наиболее распространенной проблемой при раннем выходе является то, что операторы очистки или заключительные операторы не выполняются — например, выделенная память не освобождается или открытые файлы не закрываются, что приводит к утечкам памяти или утечкам ресурсов . Это необходимо делать на каждом участке возврата, поскольку он хрупкий и может легко привести к ошибкам. Например, на более поздних стадиях разработки разработчик может пропустить оператор возврата, а действие, которое должно быть выполнено в конце подпрограммы (например, оператор трассировки ), может выполняться не во всех случаях. Языки без оператора return, такие как стандартный Pascal и Seed7 , не имеют этой проблемы.

Большинство современных языков обеспечивают поддержку на уровне языка для предотвращения таких утечек; [10] см. подробное обсуждение в разделе «Управление ресурсами» . Чаще всего это делается с помощью защиты от завершения, которая гарантирует, что определенный код будет гарантированно запущен при выходе из блока; это структурированная альтернатива блоку очистки и файлу goto. Чаще всего это называют try...finally,и считают частью обработки исключений . В случае нескольких returnоператоров введение try...finally,без исключений может выглядеть странно. Существуют различные методы инкапсуляции управления ресурсами. Альтернативный подход, встречающийся в основном в C++, — это Resource Acquisition Is Initialization , который использует обычное развертывание стека (освобождение переменных) при выходе из функции для вызова деструкторов локальных переменных для освобождения ресурсов.

Кент Бек , Мартин Фаулер и соавторы в своих книгах по рефакторингу утверждали , что вложенные условные выражения может быть труднее понять, чем определенный тип более плоской структуры, использующей множественные выходы, предопределенные защитными предложениями . В их книге 2009 года прямо говорится, что «одна точка выхода на самом деле бесполезное правило. Ясность — это ключевой принцип: если метод более понятен с одной точкой выхода, используйте одну точку выхода; в противном случае не делайте этого». Они предлагают кулинарное решение для преобразования функции, состоящей только из вложенных условных операторов, в последовательность защищенных операторов return (или throw), за которыми следует один незащищенный блок, который предназначен для хранения кода для общего случая, в то время как защищенные операторы предполагается иметь дело с менее распространенными (или с ошибками). [11] Херб Саттер и Андрей Александреску в своей книге советов по C++ 2004 года также утверждают, что единая точка выхода является устаревшим требованием. [12]

В своем учебнике 2004 года Дэвид Уотт пишет, что «часто желательны потоки управления с одним входом и множеством выходов». Используя понятие секвенсора Теннента , Ватт единообразно описывает конструкции потока управления, встречающиеся в современных языках программирования, и пытается объяснить, почему определенные типы секвенсоров предпочтительнее других в контексте потоков управления с несколькими выходами. Ватт пишет, что неограниченные переходы (секвенсоры переходов) плохи, потому что пункт назначения перехода не является самоочевидным для читателя программы до тех пор, пока читатель не найдет и не исследует фактическую метку или адрес, который является целью перехода. Напротив, Уотт утверждает, что концептуальная цель возвращаемого секвенсора ясна из его собственного контекста, без необходимости изучения его назначения. Ватт пишет, что класс секвенсоров, известный как escape-секвенсоры , определяемый как «секвенсор, который завершает выполнение текстовой команды или процедуры», включает в себя как выходы из циклов (включая многоуровневые разрывы), так и операторы возврата. Уотт также отмечает, что, хотя секвенсоры переходов (гото) были несколько ограничены в таких языках, как C, где целью должен быть внутренний локальный блок или охватывающий внешний блок, одного этого ограничения недостаточно, чтобы сделать назначение переходов в C самостоятельным. -описывая, и поэтому они все еще могут создавать « спагетти-код ». Ватт также исследует, чем секвенсоры исключений отличаются от секвенсоров escape и jump; это объясняется в следующем разделе этой статьи. [13]

В отличие от вышесказанного, Бертран Мейер написал в своем учебнике 2009 года, что инструкции типа breakи continue«просто старые люди gotoв овечьей шкуре», и настоятельно рекомендовал воздержаться от их использования. [14]

Обработка исключений

Основываясь на ошибке кодирования, возникшей в результате катастрофы Ariane 501 , разработчик программного обеспечения Джим Бонанг утверждает, что любые исключения, вызванные функцией, нарушают парадигму единого выхода, и предлагает запретить все межпроцедурные исключения. Бонанг предлагает, чтобы все C++, соответствующие одному выходу, были написаны следующим образом:

bool MyCheck1 () throw () { bool Success = false ; try { // Делаем что-то, что может вызвать исключения. if ( ! MyCheck2 ()) { throw SomeInternalException (); } // Другой код, аналогичный приведенному выше. успех = правда ; } catch (...) { // Все исключения перехватываются и протоколируются. } вернуть успех ; }                            

Питер Ричи также отмечает, что, в принципе, даже единственное throwправо перед returnфункцией in представляет собой нарушение принципа единственного выхода, но утверждает, что правила Дейкстры были написаны в то время, когда обработка исключений не стала парадигмой в языках программирования, поэтому он предлагает разрешить любое количество точек броска в дополнение к одной точке возврата. Он отмечает, что решения, которые оборачивают исключения ради создания единого выхода, имеют более высокую глубину вложенности и, следовательно, их труднее понять, и даже обвиняет тех, кто предлагает применять такие решения к языкам программирования, которые поддерживают исключения, в вовлечении в карго-культ мышления . . [15]

Дэвид Ватт также анализирует обработку исключений в рамках секвенсоров (представленных в этой статье в предыдущем разделе о ранних выходах). Ватт отмечает, что ненормальная ситуация (обычно иллюстрируемая арифметическими переполнениями или ошибками ввода/вывода, например, файл не найден) является ошибки, которая «обнаружена в каком-то программном модуле низкого уровня, но [для которой] обработчик более естественно расположен в программном модуле высокого уровня». Например, программа может содержать несколько вызовов чтения файлов, но действие, которое необходимо выполнить, когда файл не найден, зависит от значения (назначения) рассматриваемого файла для программы, и поэтому процедура обработки этой ненормальной ситуации не может быть создана. находится в низкоуровневом системном коде. Уоттс далее отмечает, что введение тестирования флагов состояния в вызывающем объекте, как это повлечет за собой структурное программирование с одним выходом или даже секвенсоры возврата (с несколькими выходами), приводит к ситуации, когда «код приложения имеет тенденцию загромождаться проверками флагов состояния» и что «программист может по забывчивости или лениво не проверять флаг состояния. Фактически, нештатные ситуации, представленные флагами состояния, по умолчанию игнорируются!» Он отмечает, что в отличие от тестирования флагов состояния, исключения имеют противоположное поведение по умолчанию , вызывая завершение работы программы, если только программист явно не обработает исключение каким-либо образом, возможно, добавив код, чтобы намеренно игнорировать его. Основываясь на этих аргументах, Уотт приходит к выводу, что секвенсоры перехода или escape-секвенсоры (обсуждаемые в предыдущем разделе) не так подходят, как специальный секвенсор исключений с семантикой, описанной выше. [16]

В учебнике Лаудена и Ламберта подчеркивается, что обработка исключений отличается от конструкций структурного программирования, таких как whileциклы, поскольку передача управления «настраивается в другой точке программы, чем та, где происходит фактическая передача. , может не быть синтаксического указания на то, что управление действительно будет передано». [17] Профессор информатики Арвинд Кумар Бансал также отмечает, что в языках, которые реализуют обработку исключений, даже структуры управления, такие как for, которые имеют свойство единственного выхода при отсутствии исключений, больше не имеют его при наличии исключений, потому что исключение может преждевременно вызвать досрочный выход в любой части структуры управления; например, если init()выдается исключение в for (init(); check(); increm()), то обычная точка выхода после check() не достигается. [18] Ссылаясь на многочисленные предыдущие исследования других авторов (1999–2004 гг.) и свои собственные результаты, Уэстли Веймер и Джордж Некула написали, что серьезная проблема с исключениями заключается в том, что они «создают скрытые пути потока управления, о которых программистам трудно рассуждать». . [19]

Необходимость ограничить код точками единственного выхода появляется в некоторых современных средах программирования, ориентированных на параллельные вычисления , таких как OpenMP . Различные параллельные конструкции OpenMP, такие как parallel do, не допускают раннего выхода изнутри наружу параллельной конструкции; это ограничение включает в себя все виды выходов, от breakисключений C++, но все они разрешены внутри параллельной конструкции, если цель перехода также находится внутри нее. [20]

Множественный вход

Реже подпрограммы допускают множественный ввод. Чаще всего это всего лишь повторный вход в сопрограмму (или генератор /полусопрограмму), где подпрограмма передает управление (и, возможно, значение), но затем может быть возобновлена ​​с того места, где она была остановлена. Существует ряд распространенных применений такого программирования, особенно для потоков (особенно ввода/вывода), конечных автоматов и параллелизма. С точки зрения выполнения кода выход из сопрограммы ближе к структурированному программированию, чем возврат из подпрограммы, поскольку подпрограмма фактически не завершилась и продолжит работу при повторном вызове — это не досрочный выход. Однако сопрограммы означают, что состояние выполнения имеют несколько подпрограмм, а не один стек вызовов подпрограмм, и, таким образом, вводят другую форму сложности.

Подпрограммы очень редко разрешают вход в произвольную позицию в подпрограмме, так как в этом случае состояние программы (например, значения переменных) не инициализировано или неоднозначно, и это очень похоже на переход.

Государственные машины

Некоторые программы, особенно парсеры и протоколы связи , имеют ряд состояний , которые следуют друг за другом таким образом, что его нелегко свести к базовым структурам, и некоторые программисты реализуют изменения состояний с помощью перехода к новому состоянию. Этот тип переключения состояний часто используется в ядре Linux. [ нужна цитата ]

Однако можно структурировать эти системы, сделав каждое изменение состояния отдельной подпрограммой и используя переменную для указания активного состояния (см. батут ). В качестве альтернативы их можно реализовать с помощью сопрограмм, которые обходятся без батута.

Смотрите также

Рекомендации

Цитаты

  1. ^ Кларк, Лесли Б. Уилсон, Роберт Г.; Роберт, Кларк (2000). Сравнительные языки программирования (3-е изд.). Харлоу, Англия: Аддисон-Уэсли. п. 20. ISBN 9780201710120. Архивировано из оригинала 26 ноября 2015 года . Проверено 25 ноября 2015 г.{{cite book}}: CS1 maint: несколько имен: список авторов ( ссылка )
  2. ^ Бём и Якопини 1966.
  3. ^ Дейкстра 1968, с. 147, «Необузданное использование оператора перехода к немедленному последствию приводит к тому, что становится ужасно трудно найти значимый набор координат, в которых можно описать ход процесса. ... Оператор перехода в его нынешнем виде слишком примитивен. , это слишком сильное приглашение испортить свою программу».
  4. ^ Дейкстра 1968.
  5. ^ Плаугер, П.Дж. (12 февраля 1993 г.). Целевое программирование, Очерки дизайна программного обеспечения (1-е изд.). Прентис-Холл. п. 25. ISBN 978-0-13-721374-0.
  6. ^ DLS • Дональд Кнут • Ответы на все вопросы. YouTube . Университет Ватерлоо. 15 ноября 2018 г. 48 минут . Проверено 24 июля 2022 г.
  7. ^ Дональд Э. Кнут (декабрь 1974 г.). «Структурное программирование с операторами перехода» (PDF) . Вычислительные опросы . 6 (4): 261–301. дои : 10.1145/356635.356640. S2CID  207630080. Архивировано из оригинала (PDF) 23 октября 2013 г.
  8. ^ В EWD1308, «Что привело к созданию «Заметок по структурированному программированию»».от 10 июня 2001 года Дейкстра пишет: «Очевидно, IBM не понравилась популярность моего текста; она украла термин «Структурированное программирование» и под своей эгидой Харлан Д. Миллс упростил первоначальную концепцию, упразднив оператор goto. "
  9. ^ Фрэнк Рубин (март 1987 г.). «Перейти к считается вредным» считается вредным» (PDF) . Коммуникации АКМ . 30 (3): 195–196. дои : 10.1145/214748.315722. S2CID  6853038. Архивировано из оригинала (PDF) 20 марта 2009 г.
  10. ^ Старейшина, Мэтт; Джексон, Стив; Либлит, Бен (октябрь 2008 г.). Сэндвичи с кодом (PDF) (Технический отчет). Университет Висконсина-Мэдисона . 1647.
  11. ^ Джей Филдс; Шейн Харви; Мартин Фаулер; Кент Бек (2009). Рефакторинг: Ruby Edition . Пирсон Образование. стр. 274–279. ISBN 978-0-321-60350-0.
  12. ^ Херб Саттер; Андрей Александреску (2004). Стандарты кодирования на C++: 101 правило, рекомендации и передовой опыт . Пирсон Образование. ISBN 978-0-13-265442-5. Пример 4: Один въезд, один выход («SESE»). Исторически сложилось так, что некоторые стандарты кодирования требовали, чтобы каждая функция имела ровно один выход, то есть один оператор возврата. Такое требование устарело в языках, поддерживающих исключения и деструкторы, где функции обычно имеют множество неявных выходов.
  13. ^ Ватт и Финдли 2004, стр. 215–221.
  14. ^ Бертран Мейер (2009). Прикосновение к классу: учимся хорошо программировать с объектами и контрактами . Springer Science & Business Media. п. 189. ИСБН 978-3-540-92144-8.
  15. ^ «Один вход, один выход, должно ли это все еще быть применимо в объектно-ориентированных языках?» Блог MVP Питера Ричи . 7 марта 2008 г. Архивировано из оригинала 14 ноября 2012 г. Проверено 15 июля 2014 г.
  16. ^ Ватт и Финдли 2004, стр. 221–222.
  17. ^ Кеннет К. Лауден; Кеннет А. Ламберт (2011). Языки программирования: принципы и практика (3-е изд.). Cengage Обучение. п. 423. ИСБН 978-1-111-52941-3.
  18. ^ Арвинд Кумар Бансал (2013). Введение в языки программирования . ЦРК Пресс. п. 135. ИСБН 978-1-4665-6514-2.
  19. ^ Веймер, В. и Некула, GC (2008). «Исключительные ситуации и надежность программ» (PDF) . Транзакции ACM в языках и системах программирования . 30 (2). 8:27. дои : 10.1145/1330017.1330019. S2CID  3136431. Архивировано из оригинала (PDF) 23 сентября 2015 г.
  20. ^ Рохит Чандра (2001). Параллельное программирование в OpenMP . Морган Кауфманн. п. 45. ИСБН 978-1-55860-671-5.

Источники

Внешние ссылки