Структурне програмування — методологія розробки програмного забезпечення, в основі якої лежить подання програми у вигляді ієрархічної структури блоків. Запропонована в 1970-х роках Е. Дейкстрой та ін.
Згідно з цією методологією будь-яка програма будується без використання оператора goto з трьох базових управляючих структур: послідовність, розгалуження, цикл; крім того, використовуються підпрограми. При цьому розробка програми ведеться покроково, методом «зверху вниз».
Методологія структурного програмування з'явилася як наслідок зростання складності розв'язуваних завдань на комп'ютерах, і відповідно, ускладнення програмного забезпечення. У 1970-ті роки обсяги і складність програм досягли такого рівня, що традиційна (неструктурована) розробка програм перестала задовольняти потреб практики. Програми ставали занадто складними, щоб їх можна було нормально супроводжувати. Тому потрібна систематизація процесу розробки та структури програм.
Методологія структурної розробки програмного забезпечення була визнана «самою сильною формалізацією 70-х років».
На думку Бертрана Мейєра, «Революція в поглядах на програмування, розпочата Дейкстрой, привела до руху, відомому як структурне програмування, яке запропонувало систематичний, раціональний підхід до конструювання програм. Структурне програмування стало основою всього, що зроблено в методології програмування, включаючи і об'єктне програмування
Мета структурного програмування — підвищити продуктивність праці програмістів, в тому числі при розробці великих і складних програмних комплексів, скоротити число помилок, спростити налагодження, модифікацію і супровід програмного забезпечення.
Така мета була поставлена у зв'язку із зростанням складності програм і нездатністю розробників і керівників великих програмних проектів впоратися з проблемами, що виникли у 1960 – 1970 роки у зв'язку з розвитком програмних засобів
Структурне програмування покликане, зокрема, усунути безлад і помилки в програмах, викликані труднощами читання коду, несистематизованим, незручним для сприйняття та аналізу вихідним текстом програми. Такий текст нерідко характеризують як «спагетті-код».
Спагетті-код (spaghetti code) — погано спроектована, слабко структурована, заплутана і складна для розуміння програма, що містить багато операторів goto (особливо переходів тому), виключень і інших конструкцій, що погіршують структурованість[]. Найпоширеніший антипаттерн програмування.
Спагетті-код названий так тому, що хід виконання програми схожий на тарілку спагетті, тобто звивистий і заплутаний. Іноді називається «кенгуру-код» (kangaroo code) з-за безлічі інструкцій jump.
В даний час термін застосовується не тільки до випадків зловживання goto, але і до будь-якого «многосвязному» коду, в якому один і той же невеликий фрагмент виповнюється у великій кількості різних ситуацій і виконує багато різних логічних функцій[].
Спагетті-код може бути налагоджений і працювати правильно і з високою продуктивністю, але він дуже складний у супроводі та розвитку[]. Доробка спагетті-коду для додавання нової функціональності іноді несе значний потенціал внесення нових помилок. З цієї причини стає практично неминучим рефакторинг (code refactoring) — головне ліки від спагетті
Починаючи з 1970-х років оператор безумовного переходу goto опинився в центрі систематичної і всезростаючої критики. Неправильне і необережне використання оператора goto у вихідному тексті програми призводить до отримання заплутаного, неудобочитаемого «спагетті-коду». За текстом такого коду практично неможливо зрозуміти порядок виконання і взаємозалежність фрагментів.
Вперше ця точка зору була відображена у статті Эдсгера Дейкстри «Оператор Go To вважається шкідливим»[]. Дейкстра зауважив, що якість програмного коду назад пропорційно кількості операторів goto в ньому. Стаття придбала широку популярність, в результаті чого погляди на використання оператора goto були суттєво переглянуті. У роботі «Нотатки по структурному програмуванню»[] Дейкстра обґрунтував той факт, що для коду без goto набагато легше перевірити формальну коректність.
Код з goto важко форматувати, так як він може порушувати ієрархічність виконання (парадигму структурного програмування) і тому відступи, покликані відображати структуру програми, не завжди можуть бути виставлені правильно. Крім того, оператор goto заважає оптимізації компіляторами керуючих структур[].
Деякі способи застосування goto можуть створювати проблеми з логікою виконання програми: Якщо деяка змінна ініціалізується (отримує значення) в одному місці і потім використовується далі, то перехід в точку після ініціалізації, але до використання, призведе до того, що буде вибрано значення, яке знаходилося в пам'яті, виділеної під змінну, до моменту виділення (і яке, як правило, є довільним і випадковим). Передача управління всередину тіла циклу призводить до пропуску коду ініціалізації циклу або первісної перевірки умови. Аналогічно, передача управління всередину процедури або функції призводить до пропуску її початкової частини, у якій виробляється ініціалізація (виділення пам'яті під локальні змінні).
Доводи проти оператора goto виявилися настільки серйозними, що в структурному програмуванні його стали розглядати як вкрай небажаний. Це знайшло відображення при проектуванні нових мов програмування. Наприклад, goto заборонений в Java і Ruby. У ряді сучасних мов він все ж залишений з міркувань ефективності в тих рідкісних випадках, коли застосування goto виправдано. Так, goto зберігся в Пекло — одному з найбільш продуманих з точки зору архітектури мов за всю історію[].
Проте в мовах високого рівня, де цей оператор зберігся, на його використання, як правило, накладаються жорсткі обмеження, що перешкоджають використанню найбільш небезпечних методів його застосування: наприклад, забороняється передавати управління ззовні циклу, процедури або функції всередину. Стандарт мови C++ забороняє обхід ініціалізації змінної з допомогою goto.
Теорему сформулювали і довели італійські математики Коррадо Бем (Corrado Böhm) і Джузеппе Якопини (Giuseppe Jacopini). Вони опублікували її в 1965 році на італійській мові і в 1966 році англійською[]. Поряд з теоремою, у статті Бема і Якопини описувалися методи перетворення неструктурних алгоритмів в структурних на прикладі створеного Бемом мови програмування P". Мова P" — перший повний по Тьюрингу мова програмування без оператора goto.
Теорема Бема-Якопини написана складною мовою і в незвичних позначеннях. Якщо використовувати сучасну термінологію і позначення, вона прийме вигляд:
Будь-яка програма, задана у вигляді блок-схеми, може бути представлена за допомогою трьох керуючих структур: послідовність — позначається: f THEN g, розгалуження — позначається: IF p THEN ELSE f g, цикл — позначається: WHILE p DO f,
де f, g — блок-схеми з одним входом і одним виходом, р — умова, THEN, IF, ELSE, WHILE, DO — ключові слова[].
Пояснення. Формула f THEN g означає наступне: спочатку виконується програма f, потім виконується програма g.
Як зазначає Харлан Міллс (Harlan Mills), дана теорема різко контрастує зі звичайною (у 1960 – 1970 роки) практикою програмування, коли спостерігалося масове використання операторів переходу goto[].
Бем і Якопини не вживали термін «структурне програмування». Тим не менш, доведену ними теорему (і її подальші варіації у різних авторів) згодом стали називати «Теорема про структурному програмуванні», «Структурної теорема» (Structure theorem[]), «Теорема про структуруванні»
Становлення і розвиток структурного програмування пов'язане з ім'ям Эдсгера Дейкстри[].
Принцип 1. Слід відмовитися від використання оператора безумовного переходу goto.
Принцип 2. Будь-яка програма будується з трьох базових керуючих конструкцій: послідовність, розгалуження, цикл. Послідовність — одноразове виконання операцій в тому порядку, в якому вони записані в тексті програми. Бертран Мейер пояснює: «Послідовне з'єднання: використовуйте вихід одного елемента як вхід до іншого, подібно до того, як електрики з'єднують вихід опору з входом конденсатора»[]. Розгалуження — одноразове виконання однієї з двох або більше операцій, в залежності від виконання заданої умови. Цикл — багаторазове виконання однієї і тієї ж операції до тих пір, поки виконується задана умова (умова продовження циклу).
Принцип 3. У програмі базові керуючі конструкції можуть бути вкладені один в одного довільним чином. Ніяких інших засобів управління послідовністю виконання операцій не передбачається.
Принцип 4. Повторювані фрагменти програми можна оформити у вигляді підпрограм (процедур і функцій). Таким же чином (у вигляді підпрограм) можна оформити логічно цілісні фрагменти програми, навіть якщо вони не повторюються. У цьому випадку в тексті основної програми, замість поміщеного в підпрограму фрагмента, вставляється інструкція «Виклик підпрограми». При виконанні такої інструкції працює викликана підпрограма. Після цього продовжується виконання основної програми, починаючи з інструкції, наступної за командою «Виклик підпрограми». Бертран Мейер пояснює: «Перетворіть елемент, можливо, з внутрішніми елементами, в підпрограму, яка характеризується одним входом і одним виходом в потоці управління»[].
Принцип 5. Кожну логічно закінчену групу інструкцій слід оформити як блок (block). Блоки є основою структурного програмування. Блок — це логічно згрупована частину вихідного коду, наприклад, набір інструкцій, записаних поспіль у вихідному коді програми. Поняття блок означає, що до блоку інструкцій слід звертатися як до єдиної інструкції. Блоки служать для обмеження області видимості змінних і функцій. Блоки можуть бути порожніми або вкладеними один в інший. Межі блоку строго визначені. Наприклад, в if-інструкції блок обмежений кодом BEGIN..END (у мові Паскаль) або фігурними дужками {...} (у мові C) або відступами (у мові Пітон).
Спочатку пишеться текст основної програми, в якому, замість кожного зв'язного логічного фрагмент тексту, що вставляється виклик підпрограми, яка буде виконувати цей фрагмент. Замість справжніх, які працюють підпрограм, у програму вставляються фіктивні частини — заглушки, які, кажучи спрощено, нічого не роблять.
Якщо говорити точніше, заглушка задовольняє вимогам інтерфейсу замінного фрагмента (модуля), але не виконує його функцій, або виконує їх частково. Потім заглушки замінюються або доопрацьовуються до справжніх повнофункціональних фрагментів (модулів) згідно з планом програмування. На кожній стадії процесу реалізації вже створена програма повинна працювати неправильно по відношенню до більш низького рівня. Отримана програма перевіряється і відладжується[].
Після того, як програміст переконається, що підпрограми викликаються в правильній послідовності (тобто загальна структура програми верна), підпрограми-заглушки послідовно замінюються на реально працюють, причому розробка кожної підпрограми ведеться тим же методом, що і основної програми. Розробка закінчується тоді, коли не залишиться ні однієї заглушки.
Така послідовність гарантує, що на кожному етапі розробки програміст одночасно має справу з досяжним і зрозумілим йому безліччю фрагментів, і може бути впевнений, що загальна структура усіх більш високих рівнів програми вірна.
При супроводженні та внесення змін в програму з'ясовується, які саме процедури потрібно внести зміни.Вони вносяться, не зачіпаючи частини програми, безпосередньо не пов'язані з ними. Це дозволяє гарантувати, що при внесенні змін і виправлення помилок не вийде з ладу якась частина програми, що знаходиться в даний момент поза зоною уваги програміста[].
Слід також врахувати, що в «Передмові» до книги «Структурне програмування»[] Тоні Хоар (Tony Hoare) зазначає, що принципи структурного програмування в рівній мірі можуть застосовуватися при розробці програм як «зверху вниз», так і «знизу вгору»]
Слідування принципам структурного програмування зробило тексти програм, навіть досить великих, нормально читаються. Серйозно полегшилось розуміння програм, з'явилася можливість розробки програм в нормальному виробничому режимі, коли програму може без особливих труднощів зрозуміти не тільки її автор, але й інші програмісти. Це дозволило розробляти досить великі для того часу програмні комплекси силами колективів розробників, і супроводжувати ці комплекси протягом багатьох років, навіть в умовах неминучих змін у складі персоналу. Структурне програмування дозволяє значно скоротити кількість варіантів побудови програми з однієї і тієї ж специфікації, що значно знижує складність програми і, що ще важливіше, полегшує розуміння її іншими розробниками. У структурованих програми логічно пов'язані оператори знаходяться візуально ближче, а слабо пов'язані — далі, що дозволяє обходитися без блок-схем та інших графічних форм зображення алгоритмів (по суті, сама програма є власної блок-схемою). Сильно спрощується процес тестування і налагодження структурованих програм.
Структурне програмування значно підвищує ясність і читабельність (readability) програм[]. Едвард Йордан (Edward Yourdon) пояснює:
Поведінка багатьох неструктурних програм часто ближче до броунівського руху, ніж до скільки-небудь організованому процесу. Всяка спроба прочитати лістинг приводить людину у відчай тим, що в такій програмі звичайно виконуються декілька операторів, після чого управління передається в точку кількома сторінками нижче. Там виконуються ще кілька операторів і управління знову передається в якусь випадкову точку. Тут виконуються ще якісь оператори і т. д. Після кількох таких передач читач забуває, з чого все почалося. І втрачає хід думки.
Структурним програмами, навпаки, властива тенденція до послідовним організації і виконанню[].
Поліпшення читабельності структурних програм пояснюється тим, що відсутність оператора goto дозволяє читати програму зверху до низу без розривів, викликаних передачами управління. У підсумку можна відразу одним оком) виявити умови, необхідні для модифікації того чи іншого фрагмента програми
Р-технологія виробництва програм, або «технологія двовимірного програмування»[] була створена в Інституті кібернетики імені В. М. Глушкова[]. Графічна система Р-технології програмування закріплена в стандартах ГОСТ 19.005-85[], ГОСТ Р ІСО/МЕК 8631-94[] і міжнародному стандарті ІЅО 8631Н.
Автор Р-технології програмування доктор фізико-математичних наук професор Ігор Вельбицький запропонував переглянути саме поняття «структура програми». На його думку, «структура — поняття багатовимірне. Тому відображення цього поняття з допомогою лінійних текстів (послідовності операторів) зводить практично нанівець переваги структурного підходу. Величезні асоціативні можливості зорового апарату та апарату мислення людини використовуються практично вхолосту — для розпізнавання структурних образів у вигляді однакової послідовності символів»].
Методологія двовимірного структурного програмування істотно відрізняється від класичного одновимірного (текстового) структурного програмування[].
Ідеї структурного програмування розроблялися, коли комп'ютерна графіка фактично ще не існувала і основним інструментом алгоритмиста і програміста був одномірний (лінійний або ступінчастий) текст. До появи комп'ютерної графіки методологія класичного структурного програмування була найкращим рішенням[].
З появою комп'ютерної графіки ситуація змінилася. Використовуючи виражальні засоби графіки, з'явилася можливість видозмінити, розвинути та доповнити три типи базових (текстових) керівників структурних конструкцій, а також повністю відмовитися від ключових слів if, then, else, case, switch, break, while, do, repeat, until, for, foreach, continue, loop, exit, when, last і т. д. і замінити їх на керуючу графіком, тобто використовувати двовимірне структурне програмування[].
Важливою проблемою є складність сучасного програмування і пошук шляхів її подолання. На думку кандидата технічних наук, доцента Євгена Пишкіна, вивчення структурного програмування виключно як інструмент розробки текстів програм, побудованих на базі основної «структурної тріади» (лінійна послідовність, розгалуження і цикл), є недостатнім і, по суті, зводить нанівець аналіз переваг структурного підходу[]. У процесі подолання істотної складності програмного забезпечення найважливішим інструментом є візуалізація проектування і програмування[].
Принцип 6. Всі перераховані конструкції повинні мати один вхід і один вихід. Довільні керуючі конструкції (такі, як у страві спагетті) можуть мати довільне число входів і виходів. Обмеживши себе керуючими конструкціями з одним входом і одним виходом, ми отримуємо можливість побудови довільних алгоритмів будь-якої складності за допомогою простих і надійних механізмів[].
Принцип 7. Розробка програми ведеться покроково, методом «зверху-вниз» (top–down method)[].