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

**Структураванае праграмаваньне** — гэта парадыгма праграмаваньня, накіраваная на паляпшэньне яснасьці, якасьці і часу распрацоўкі кампутарнае праграмы шляхам шырокага выкарыстаньня структураваных канструкцый кіраваньня патокам для выбару, як <code>if..then..else..endif</code>, і паўтарэньня, як <code>while</code> і <code>for</code>, блёкавых структур і падпраграм.

Зьявілася ў канцы 1950-х гадоў з мовамі праграмаваньня ALGOL 58 і ALGOL 60, прычым апошняя ўключала падтрымку блёкавых структур. Фактары, якія спрыялі яго папулярнасьці і шырокаму прызнаньню спачатку ў акадэмічных колах, а потым і сярод спецыялістаў-практыкаў, уключаюць адкрыцьцё ў 1966 г. таго, што цяпер вядома як тэарэма аб структураванай праграме, і публікацыю ўплывовага адкрытага ліста «Шкоднае сцьвярджэньне GOTO» у 1968 г. галяндскім інфарматыкам Эдсгерам Дэйкстрам, які ўвёў тэрмін «структураванае праграмаваньне».

Структураванае праграмаваньне часьцей за ўсё выкарыстоўваецца з адхіленьнямі, якія дазваляюць больш выразныя праграмы ў некаторых асобных выпадках, напрыклад, калі неабходна выканаць апрацоўку выключэньняў.

## Элемэнты

### Структуры кіраваньня
Згодна з тэарэмаю аб структураванай праграме усе праграмы разглядаюцца як складзеныя з трох структур кіраваньня:

* «Паслядоўнасць» — упарадкаваныя інструкцыі або падпраграмы, якія выконваюцца паслядоўна.
* «Выбар» — выконваецца адна або шэраг інструкцый у залежнасьці ад стану праграмы. Звычайна для гэтага выкарыстоўваюцца такія ключавыя словы, як <code>if..then..else..endif</code>. Умова павінна праўдзіцца хаця бы ў адным выпадку, і кожная ўмова павінна мець толькі адзін пункт выхаду.
* «Ітэрацыя» — інструкцыя або блёк выконваецца, пакуль праграма не дасягне пэўнага стану, або апэрацыі не будуць прымененыя да кожнага элемэнта калекцыі. Ключавыя словы <code>while</code>, <code>repeat</code>, <code>for</code> або <code>do..until</code> звычайна ўжываюцца для гэтага. Часта рэкамендуецца, каб кожны цыкль меў адзіны пункт ўваходу (а ў арыгінальным структураваным праграмаваньні таксама адзіны пункт выхаду, што забясьпечваюць некаторыя мовы).

### Падпраграмы
Падпраграмы — часткі кода, якія можна выклікаць, такія як працэдуры, функцыі або мэтады. Выкарыстоўваюцца для таго, каб на паслядоўнасць інструкцый можна было спасылацца адной інструкцыяй.

### Блёкі
Блёкі выкарыстоўваюцца для таго, каб аб’яднаць групу інструкцый ў адну інструкцыю. Мовы *з блёчнай структураю* маюць сынтакс для групаваньня структур нейкім фармальным спосабам, напрыклад, канструкцыя `if..fi`, як у ALGOL 68, або блёк кода ў межах `BEGIN..END`, як у PL/I і Pascal, водступы пропускамі, як у Python, або фігурныя дужкі `{...}` у C і многіх пазьнейшых мовах.

## Структураваныя мовы праграмаваньня
Структураванае праграмаваньне магчымае на любой мове праграмаваньня, хоць лепш выкарыстоўваць нешта накшталт працэдурнае мовы праграмаваньня. Некаторыя з моў, якія першапачаткова выкарыстоўваліся для структураванага праграмаваньня, уключаюць: ALGOL, Pascal, PL/I, Ada і RPL. Большасць новых працэдурных моў праграмаваньня маюць функцыі для заахвочваньня структураванага праграмаваньня, а часам наўмысна выключаюць некаторыя функцыі, асабліва GOTO, у спробе ўскладніць неструктураванае праграмаваньне.

*Структураванае праграмаваньне* (часам вядомае як модульнае праграмаванне) прымушае, каб лягічная структуру праграмы была прапісаная, што робіць праграму больш эфэктыўнай і прасцейшай для разуменьня і мадыфікацыі.

## Гісторыя

### Тэарэтычныя асновы
Тэарэма аб структураванай праграме забясьпечвае тэарэтычную аснову структураванага праграмаваньня. У ёй сцьвярджаецца, што канструкцый трох тыпаў (паслядоўнасьці, выбару і ітэрацыі) дастаткова для вызначэньня любое вылічальнае функцыі. Гэтае назіраньне ўзнікла раней за структураванае праграмаваньне так, як вышэй названых структур дастаткова для апісаньня іншых зьяў, напрыклад, цыкля інструкцый цэнтральнага працэсара або машыны Т’юрынга. Такім чынам, працэсар заўсёды, у гэтым сэнсе, выконвае «структураваную праграму», нават калі інструкцыі, якія ён чытае з памяці, не з’яўляюцца часткаю структураванае праграмы. Аднак аўтарства тэрміну звычайна прыпісваюць Бёма і Якапіні, магчыма таму, што Дэйкстра сам цытаваў іх працу 1966 года. Тэарэма аб структураванай праграме не датычыцца таго, як напісаць і прааналізаваць структураваную праграму, гэтыя пытаньні разглядалася ў канцы 1960-х і пачатку 1970-х гадоў у працах Дэйкстры, Роберта У. Флойда, Тоні Хоара, Оле-Ёхана Даля і Дэвіда Грыса.

### Дэбаты
Адзін з першых прыхільнікаў структураванага праграмаваньня Філіп Дж. Пладжэр апісаў сваю рэакцыю на тэарэму аб структураванай праграме так:Мы, новазьвернутыя, размахвалі гэтай цікавай навіною пад носам у нерэканструяваных праграмістаў асэмблера, якія працягвалі ламаць галовы і казаць: «Не магу структураваць гэта». Ні доказы Бёма й Якапіні, ні нашы неаднаразовыя посьпехі ў напісаньні структураванага кода не паўплывалі на іх, пераканаць сябе маглі толькі яны самі.Дональд Кнут прыняў прынцып, што праграмы павінны пісацца з улікам доказнасьці, але ён не пагадзіўся з забаронаю канструкцыі GOTO, і працягваў выкарыстоўваць яе ў сваіх праграмах. У сваёй працы 1974 г. «Структураванае праграмаваньне з GOTO» ён прывёў прыклады, дзе, на яго думку, прамы скачок прыводзіць да больш выразнага і эфэктыўнага кода бяз шкоды для доказнасьці. Кнут прапанаваў больш свабоднае структурнае абмежаваньне: павінна быць магчымасьць намаляваць блёк-схему праграмы з усімі прамымі галінамі злева, усімі зваротнымі галінамі справа і без галін, якія перасякаюць адна адну.

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

Яшчэ ў 1987 годзе можна было падняць пытаньне аб структураваным праграмаваньні ў часопісах па інфарматыцы. Фрэнк Рубін зрабіў гэта ў тым годзе з адкрытым лістом пад назваю «'GOTO лічыцца шкодным' лічыцца шкодным». Услед рушылі шматлікія пярэчаньні, у тым ліку адказ Дэйкстры, які рэзка крытыкаваў як Рубіна, так і іншых аўтараў, зрабіўшых таму саступкі.

### Вынік
Да канца 20-га стагодьдзя амаль усе зацікаўленыя навукоўцы былі перакананыя, што карысна вывучаць і прымяняць канцэпцыі структураванага праграмаваньня. Мовы праграмаваньня высокага ўзроўню, у якіх першапачаткова не хапала структуры праграмаваньня, такія як Fortran, COBOL і BASIC, цяпер іх маюць.

## Распаўсюджаныя адхіленьні
У той час як goto цяпер у значнай ступені заменены структураванымі канструкцыямі выбару (if/then/else) і паўтарэньня (while і for), нешматлікія мовы з’яўляюцца чыста структураванымі. Найбольш распаўсюджаным адхіленьнем характэрным для многіх моў, з’яўляецца выкарыстаньне return для раньняга выхаду з падпраграмы. Гэта прыводзіць да некалькіх пунктаў выхаду замест аднаго, неабходнага ў структураваным праграмаваньні. Ёсць і іншыя канструкцыі, нязручныя ў чыста структураваным праграмаваньні.

### Раньні выхад
Найбольш распаўсюджаным адхіленьнем ад структураванага праграмаваньня з’яўляецца **раньні выхад** з функцыі або цыкля. На ўзроўні функцый гэта сцьвярджэньне `return`. На ўзроўні цыкляў гэта `break` (завяршэньне цыкля) або `continue` (завяршэньне бягучае ітэрацыі, пераход да наступнае ітэрацыі). У структураваным праграмаваньні іх можна рэплікаваць шляхам даданьня дадатковых галін або тэстаў, але для вяртаньня з укладзенага кода гэта можа значна прыбавіць складанасьці. C з’яўляецца раньнім і яркім прыкладам гэтых канструкцый. Некаторыя новыя мовы таксама маюць «найменаваныя перапынкі», якія дазваляюць больш, чым проста выйсьце з самага ўнутранага цыкля. Выключэньні таксама дазваляюць датэрміновы выхад, але маюць дадатковыя наступствы, і таму разглядаюцца ніжэй.

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

Самая распаўсюджаная праблема раньняга выхаду заключаецца ў тым, што не выконваюцца ачыстка або фінальныя інструкцыі, напрыклад, выдзеленая памяць не вызваляецца або адкрытыя файлы не зачыняюцца, што выклікае ўцечку памяці або рэсурсаў. Гэта трэба рабіць на кожным месцы выхаду з падпраграмы, што патрабуе дадатковай увагі і можа лёгка прывесьці да памылак. Напрыклад, распрацоўшчык можа прапусьціць інструкцыю return, і дзеяньне, якое павінна быць выканана ў канцы падпраграмы (напрыклад, трасіроўка), можа выконвацца не ва ўсіх выпадках. Мовы без інструкцыі return, такія як стандартны Pascal і Seed7, не маюць гэтае праблемы.

Большасьць сучасных моў маюць сродкі, каб прадухіліць такія ўцечкі. Часцей за ўсё гэта робіцца з дапамогаю абароны ад размотваньня, якая гарантуе, што пэўны код будзе выкананы, калі выкананьне выходзіць за межы блёка; гэта структурная альтэрнатыва блёку ачысткі і `goto`. Канструкцыя `try...` `finally`, якая лічыцца часткаю апрацоўкі выключэньняў, можа быць ужытая для гэтага. Існуюць розныя мэтады кіраваньня рэсурсамі. Альтэрнатыўны падыход, які сустракаецца ў асноўным у C++, вядомы пад назваю «Атрыманьне рэсурса ёсьць ініцыялізацыя». Пры гэтым выкарыстоўваецца звычайнае размотваньне стэка пры выхадзе з функцыі для выкліку дэструктараў лякальных зьменных для вызваленьня рэсурсаў.

Кент Бэк, Марцін Фаўлер разам з сааўтарамі ў сваіх кнігах па рэфактарынгу сцьвярджалі, што ўмоўныя інструкцыі глыбокага ўзроўню ўкладзенасьці могуць быць цяжэйшымі для разуменьня, чым пэўны тып больш плоськіх структур з выкарыстаньнем некалькіх выхадаў анатаваных ахоўным кодам. У іх кнізе 2009 года сьцьвярджаецца, што «адзіны пункт выхаду — гэта сапраўды нязручнае правіла. Яснасць — ключавы прынцып: калі мэтад больш зразумелы з адным пунктам выхаду, выкарыстоўвайце адзін пункт выхаду — у адваротным выпадку не». Яны прапануюць гатовае рашэньне для пераўтварэньня функцыі, якая складаецца толькі з укладзеных умоўных інструкцый, у паслядоўнасць інструкцый вяртаньня з ахоўным кодам (або выкідаў), за якімі ідзе адзіны неабаронены блёк кода для звычайнага выпадку, у той час як абароненыя код павінны мець справу з менш распаўсюджанымі выпадкамі (або з памылкамі). Герб Сатэр і Андрэй Александрэску таксама сьцьвярджаюць у сваёй кнізе па C++ за 2004 г., што адзіны пункт выхаду з’яўляецца састарэлым патрабаваньнем.

У сваім падручніку 2004 г. Дэвід Уат піша, што «патокі кіраваньня з адным уваходам і некалькімі выхадамі часта пажаданыя». Выкарыстоўваючы паняцьце сэквэнсара з фрэймворка Тэнанта, Уат апісвае канструкцыі кіраваньня патокам сучасных моў праграмаваньня, і спрабуе растлумачыць, чаму некаторыя тыпы сэквэнсараў пераважней іншых у кантэксьце патокаў з некалькімі выхадамі. Уат піша, што неабмежаваныя goto (сэквэнсары пераходаў) дрэнныя, таму што пункт прызначэньня пераходу не з’яўляецца для чытача праграмы зразумелым, пакуль чытач не знойдзе і не вывучыць фактычную метку або адрас, які з’яўляецца мэтаю пераходу. Наадварот, Уат сцвярджае, што канцэптуальны намер сэквэнсара вяртаньня ясны з яго ўласнага кантэксту без неабходнасьці вывучэньня яго прызначэньня. Уат піша, што клас сэквэнсараў, вядомы як *сэквэнсар выхаду*, вызначаецца як «сэквэнсар, які завяршае выкананьне ахопленае тэкстам каманды або працэдуры», уключае як перапыненьні цыкляў (у тым ліку шматузроўневыя перапыненьні), так і інструкцыі вяртаньня. Уат таксама адзначае, што ў той час як сэквэнсары пераходаў (goto) некалькі абмежаваныя ў такіх мовах, як C, дзе пункт прызначэньня павінны быць унутры лякальнага блёка або ахопліваючага вонкавага блёка, аднаго гэтага абмежаваньня недастаткова, каб зрабіць намер goto у C зразумелым, і таму яны ўсё яшчэ могуць прыводзіць да «спагэцьці-кода». Уат таксама вывучае, чым сэквэнсары выключэньняў адрозніваюцца ад сэквэнсараў выхаду і пераходу; што тлумачыцца ў наступным раздзеле гэтага артыкула.

У адрозненне ад вышэйсказанага, Бертран Мэяр піша ў сваім падручніку 2009 года, што такія інструкцыі, як `break` і `continue`, «гэта проста старая `goto` у авечай шкуры», і настойліва не раіць іх выкарыстоўваць.

### Апрацоўка выключэньняў
Грунтуючыся на праграмнай памылцы, якая прывяла да катастрофы Ariane 501, распрацоўшчык праграмнага забеспячэньня Джым Бонанг сьцьвярджае, што любыя выключэньні, выкінутыя па-за межы функцыі, парушаюць парадыгму адзінага выхаду, і прапануе забараніць усе міжпрацэдурныя выключэньні. Бонанг прапануе, каб увесь код на C++ пісаўся наступным чынам:

```cpp
bool MyCheck1() throw() {
  bool success = false;
  try {
    // Зрабіць нешта, што можа выкінуць выключэньне
    if (!MyCheck2()) {
      throw SomeInternalException();
    }
    // Іншы код, такі ж як вышэй
    success = true;
  } catch (...) {
    // Усе выключэньні перахватываюцца і апрацоўваюцца
  }
  return success;
}
```

Пітар Рычы таксама адзначае, што, у прынцыпе, нават адзіночны `throw` непасрэдна перад `return` у функцыі з’яўляецца парушэннем прынцыпу адзінага выхаду, але сьцьвярджае, што правілы Дэйкстры былі напісаны яшчэ да таго, як апрацоўка выключэньняў стала парадыгмаю у мовах праграмаваньня, таму ён прапануе дазволіць любую колькасць пунктаў выкідваньня выключэньняў у дадатак да адзінага пункту выхаду. Ён адзначае, што рашэньні, якія абгортваюць выключэньні дзеля стварэньня адзінага выхаду, павялічваюць глыбіню ўкладзенасьці і, такім чынам, складаней для разуменьня, і нават абвінавачвае тых, хто прапануе прымяняць такія рашэньні да моў праграмаваньня, якія падтрымліваюць выключэньні, ва ўдзеле ў культавым мысленьні.

Дэвід Уат таксама аналізуе апрацоўку выключэньняў з дапамогаю фрэймворка сэквэнсараў. Уат адзначае, што ненармальная сітуацыя (як правіла, прыкладам якой з’яўляюцца арытмэтычныя перапаўненьні або збоі ўводу/вываду, напрыклад, файл не знойдзены) належыць да тыпу памылак, якія «выяўляюцца ў праграмным блёку нізкага ўзроўню, але апрацоўшчык для якіх больш натуральна знаходзіцца ў праграмным блёку больш высокага ўзроўню». Напрыклад, праграма можа мець некалькі зваротаў да файлаў, але дзеяньне, якое трэба выканаць, калі пэўны файл не знойдзены, залежыць ад мэты звароту да файла, і, такім чынам, працэдура апрацоўкі гэтае ненармальнае сітуацыі не можа быць размешчана ў кодзе нізкага ўзроўню. Далей Уат адзначае, што тэсціраваньне сцягоў стану ў месцы выкліку ў структураваным праграмаваньні з адным або, нават, з некалькімі выхадамі, прыводзіць да сітуацыі, калі «код мае тэндэнцыю загрувашчвацца тэстамі сцягоў стану» і «праграміст можа забыцца або паляніцца зьдейсьніць праверку сцяга стану. Фактычна, ненармальныя сітуацыі, прадстаўленыя сцягамі стану, ігнаруюцца па змаўчаньні!» Ён адзначае, што ў адрозненьні ад тэсціраваньня сцягоў стану, выключэньні маюць супрацьлеглыя паводзіны па змаўчаньні, прымушаючы праграму спыняцца, калі толькі праграміст нейкім чынам не апрацуе выключэньне, магчыма, дадаўшы код, каб наўмысна яго ігнараваць. Грунтуючыся на гэтых аргумэнтах, Уат прыходзіць да высновы, што сэквэнсары пераходу або выхаду не такія прыдатныя, як спецыяльны сэквэнсар выключэньняў з сэмантыкай, разгледжанай вышэй.

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

Неабходнасць абмежаваньня да кода з адным выхадам з’яўляецца ў некаторых сучасных асяродьдзях праграмаваньня, арыентаваных на паралельныя вылічэньні, такіх як OpenMP. Розныя паралельныя канструкцыі з OpenMP, такія як `parallel do`, не дазваляюць раньнія выхады. Гэта абмежаваньне ўключае ўсе спосабы выхаду, ад `break` да выключэньняў C++, але ўсе яны дазволеныя ўнутры паралельнай канструкцыі, калі мэта пераходу таксама знаходзіцца ўнутры яе.

### Множны ўваход
Радзей падпраграмы дазваляюць множны *ўвод.* Часьцей за ўсё гэта толькі *паўторны* ўваход у супраграму (або генэратар/паўсупраграму), дзе выкананьне перапыняецца, але затым можа быць адноўлена з таго месца, дзе яно спынілася. Ёсць шэраг агульных варыянтаў выкарыстаньня такога праграмаваньня, напрыклад, для патокаў уводу/вываду, канцоўных аўтаматаў і паралелізму. З пункту гледжаньня выкананьня кода, перапыненьне супраграмы бліжэй да структураванага праграмавання, чым вяртаньне з падпраграмы, паколькі супраграма фактычна не была завершаная і будзе працягвацца пры паўторным выкліку, што адрозьніваецца ад раньняга выхаду. Аднак супраграмы азначаюць, што некалькі падпраграм маюць стан выкананьня, а не адзіны стэк выклікаў, такім чынам, уводзіцца іншая форма складанасьці.

Вельмі рэдка дазваляецца ўваход у адвольную пазіцыю ў супраграме, бо ў гэтым выпадку стан праграмы (напрыклад, значэньні зьменных) неініцыялізаваныя або неадназначныя, і гэта вельмі падобна на GOTO.

### Канцоўныя аўтаматы
Некаторыя праграмы, у прыватнасьці парсэры й камунікацыйныя пратаколы, маюць шэраг станаў, якія ідуць адзін за адным такім чынам, што іх няпроста зьвесьці да базавых структур, і некаторыя праграмісты рэалізуюць зьмены станаў «скачком» у новы стан.

Тым не менш, можна структураваць гэтыя сыстэмы, зрабіўшы кожную зьмену стану асобнай падпраграмаю і выкарыстоўваючы зьменную для ўказаньня актыўнага стану (гл. трамплін). У якасці альтэрнатывы яны могуць быць рэалізаваныя праз супраграмы, якія абыходзяцца без трамплінаў.

## Крыніцы

 
Катэгорыя:Парадыгмы праграмаваньня