Programarea structurală este o paradigmă de programare bazată pe reprezentarea unui program sub forma unei structuri de bloc ierarhice . A fost conceptualizat la sfârșitul anilor 1960 - începutul anilor 1970 pe baza teoremei Boehm-Jacopini , care fundamentează matematic posibilitatea organizării structurale a programelor și a lucrării lui Edsger Dijkstra „Despre pericolele operatorului goto” ( ing. Goto considerat dăunător ).
În conformitate cu paradigma, orice program care este construit fără a utiliza instrucțiunea goto constă din trei structuri de control de bază: secvență, ramură , buclă ; în plus, se folosesc subrutine . În același timp, dezvoltarea programului se realizează pas cu pas, folosind metoda „de sus în jos”.
Metodologia de programare structurată a apărut ca urmare a complexității tot mai mari a sarcinilor rezolvate pe computere și, în consecință, a complexității software-ului. În anii 1970, volumul și complexitatea programelor au atins un asemenea nivel încât dezvoltarea tradițională (nestructurată) a programelor nu mai satisface nevoile practicii. Programele au devenit prea complexe pentru a fi întreținute corespunzător. Prin urmare, a fost necesară sistematizarea procesului de dezvoltare și a structurii programelor.
Metodologia dezvoltării software structurale a fost recunoscută drept „cea mai puternică formalizare a anilor ’70”.
Potrivit lui Bertrand Meyer, „Revoluția programării începută de Dijkstra a condus la mișcarea cunoscută sub numele de programare structurată, care a propus o abordare sistematică și rațională a proiectării programelor. Programarea structurată a devenit fundamentul a tot ceea ce se face în metodologia de programare, inclusiv programarea obiectelor ” [1] .
Inițial, ideea de programare structurată a luat naștere în legătură cu operatorul goto și îndoielile cu privire la oportunitatea utilizării sale. Astfel de îndoieli au fost exprimate pentru prima dată de Heinz Zemanek la o întâlnire despre limba Algol la începutul anului 1959, la Copenhaga. Cu toate acestea, acest discurs nu a atras atenția și nu a avut consecințe. Edsger Dijkstra își amintește: „Într-o oarecare măsură mă învinovățesc pentru că nu am putut să apreciez semnificația acestei idei la momentul respectiv” [2] [3] [4] .
Situația s-a schimbat dramatic zece ani mai târziu, când Dijkstra a publicat celebra sa scrisoare Go To Statement Considered Harmful în martie 1968 . Acesta este un document cu adevărat istoric care a avut un impact semnificativ asupra dezvoltării ulterioare a programării.
Soarta documentului în sine este foarte interesantă. Cert este că Dijkstra a dat articolului un titlu complet diferit: „Argumente împotriva declarației GO TO” (A Case against the GO TO Statement).
Cu toate acestea, la momentul publicării, s-a întâmplat ceva de neînțeles - dintr-un motiv oarecare articolul s-a transformat în mod misterios într-o „Scrisoare către editor”, iar titlul anterior a dispărut la fel de misterios. Ce s-a întâmplat de fapt? Dijkstra a explicat transformarea misterioasă a articolului într-o scrisoare doar mulți ani mai târziu, în 2001, cu un an înainte de moartea sa.
Comunicațiile ACM au publicat textul meu intitulat „Declarația GOTO este considerată dăunătoare ” . În anii următori, el a fost adesea citat. Din păcate, acest lucru a fost adesea făcut de oameni care nu au văzut nimic mai mult decât spune titlul. Acest titlu a devenit piatra de temelie a faimei mele...
Cum s-au întâmplat toate acestea? Am depus un articol intitulat „Cazul împotriva declarației GO TO”. Pentru a accelera publicarea, editorul a transformat articolul meu într-o Scrisoare către editor. În același timp, a venit cu un nou titlu pentru articol, pe care l-a inventat el însuși. Editorul a fost Niklaus Wirth [5] [6] .
Scopul programării structurate este de a crește productivitatea programatorilor, inclusiv atunci când se dezvoltă sisteme software mari și complexe, de a reduce numărul de erori, de a simplifica depanarea, modificarea și întreținerea software-ului.
Acest obiectiv a fost stabilit în legătură cu creșterea complexității programelor și cu incapacitatea dezvoltatorilor și managerilor de proiecte software mari de a face față problemelor apărute în anii 1960-1970 în legătură cu dezvoltarea instrumentelor software [7] .
Programarea structurată este concepută, în special, pentru a elimina mizeria și erorile din programe cauzate de dificultăți de citire a codului, nesistematizate, incomod pentru perceperea și analiza codului sursă al programului. Un astfel de text este adesea caracterizat drept „ cod spaghete ”.
Codul spaghetti este un program prost conceput, prost structurat, confuz și greu de înțeles , care conține o mulțime de instrucțiuni goto (în special back jumps), excepții și alte constructe care degradează structura [8] . Unul dintre cele mai faimoase anti -modele de programare .
Codul de spaghete este numit astfel deoarece fluxul programului este ca un castron de spaghete , adică întortocheat și întortocheat. Uneori numit „cod cangur” din cauza numeroaselor instrucțiuni de sărituri .
În zilele noastre, termenul se aplică nu numai cazurilor de abuz de goto, ci și oricărui cod „multi-linked” în care același fragment mic este executat într-un număr mare de situații diferite și îndeplinește multe funcții logice diferite [8] .
Codul spaghetti poate fi depanat și rulat corect și cu performanțe ridicate, dar este extrem de dificil de întreținut și dezvoltat [8] . Rafinarea codului spaghetti pentru a adăuga noi funcționalități are uneori un potențial semnificativ pentru introducerea de noi erori. Din acest motiv, devine aproape inevitabil ca refactorizarea să fie principalul remediu pentru spaghete.
Începând cu anii 1970, operatorul de sărituri necondiționate goto a fost în centrul unei critici sistematice și tot mai mari. Utilizarea incorectă și necugetă a declarației goto în codul sursă al programului duce la un „ cod spaghetti ” confuz și imposibil de citit . Din textul unui astfel de cod, este aproape imposibil de înțeles ordinea execuției și interdependența fragmentelor.
Acest punct de vedere a fost reflectat pentru prima dată în articolul lui Edsger Dijkstra „Operatorul Go To este considerat nociv” [3] [9] . Dijkstra a observat că calitatea codului este invers proporțională cu numărul de instrucțiuni goto din acesta. Articolul a câștigat o publicitate largă, în urma căreia opiniile cu privire la utilizarea operatorului goto au fost revizuite în mod semnificativ. În Notes on Structured Programming [10] , Dijkstra a susținut că este mult mai ușor să verificați corectitudinea formală a codului fără un goto .
Codul cu goto este dificil de format, deoarece poate rupe ierarhia execuției (o paradigmă a programării structurate) și, prin urmare, indentarea, concepută pentru a reflecta structura programului, poate să nu fie întotdeauna setată corect. În plus, instrucțiunea goto împiedică compilatorii să optimizeze structurile de control [11] .
Unele utilizări ale goto pot crea probleme cu logica de execuție a programului:
Argumentele împotriva declarației goto s-au dovedit a fi atât de serioase încât în programarea structurată au început să fie considerate ca fiind extrem de nedorite. Acest lucru s-a reflectat în proiectarea noilor limbaje de programare. De exemplu, goto este ilegal în Java și Ruby . Într-o serie de limbi moderne, este încă lăsat din motive de eficiență în acele cazuri rare în care utilizarea goto este justificată. De exemplu, goto a supraviețuit în Ada , una dintre cele mai sofisticate limbi arhitectural din istorie [12] .
Cu toate acestea, în limbile de nivel înalt în care acest operator a fost păstrat, utilizarea sa, de regulă, este supusă unor restricții severe care împiedică utilizarea celor mai periculoase metode de utilizare: de exemplu, este interzisă trecerea controlului din exterior o buclă, o procedură sau o funcție din interior. Standardul limbajului C++ interzice ocolirea inițializării variabilei cu goto.
Teorema a fost formulată și demonstrată de matematicienii italieni Corrado Böhm și Giuseppe Jacopini. Au publicat-o în 1965 în italiană și în 1966 în engleză [13] . Alături de teoremă, articolul lui Boehm și Jacopini a descris metode de conversie a algoritmilor nestructurali în algoritmi structurali folosind limbajul de programare P′′ creat de Bohm ca exemplu . Limbajul P′′ este primul limbaj de programare Turing complet fără operatorul goto .
Teorema Böhm-Jacopini este scrisă într-un limbaj complex și într-o notație neobișnuită. Dacă folosim terminologia și notația modernă, aceasta va lua forma:
Orice program dat sub forma unei organigrame poate fi reprezentat folosind trei structuri de control:
unde f, g sunt diagrame bloc cu o intrare și o ieșire,
p - stare, THEN, IF, ELSE, WHILE, DO sunt cuvinte cheie [14] .Explicaţie. Formula f ATUNCI g înseamnă următoarele: primul program f este executat, apoi programul g este executat.
După cum notează Harlan Mills , această teoremă contrastează puternic cu practica de programare obișnuită (în anii 1960-1970), când a existat o utilizare masivă a operatorilor goto jump [14] .
Boehm și Jacopini nu au folosit termenul de „programare structurată”. Cu toate acestea, teorema demonstrată de ei (și variațiile ei ulterioare de către diferiți autori) a început ulterior să fie numită „teorema de programare structurală”, „teorema structurală” [14] , „teorema de structurare” [15] .
Formarea și dezvoltarea programării structurate este asociată cu numele de Edsger Dijkstra [10] [16] .
Principiul 1. Ar trebui să încetați să utilizați operatorul de salt necondiționat goto.
Principiul 2. Orice program este construit din trei structuri de control de bază: secvență, ramificare, ciclu.
Principiul 3. Într-un program, structurile de control de bază pot fi imbricate unele în altele într-un mod arbitrar. Nu sunt furnizate alte mijloace de control al secvenței operațiilor.
Principiul 4. Fragmentele repetitive ale programului pot fi aranjate sub formă de subrutine (proceduri și funcții ). În același mod (sub formă de subprograme) se pot aranja logic fragmente integrale ale programului, chiar dacă nu se repetă.
Principiul 5. Fiecare grup logic complet de instrucțiuni ar trebui să fie aranjat ca un bloc . Blocurile sunt fundamentul programării structurate.
Un bloc este o bucată de cod sursă grupată logic, cum ar fi un set de instrucțiuni scrise pe rând în codul sursă al unui program. Conceptul de bloc înseamnă că un bloc de instrucțiuni ar trebui tratat ca o singură instrucțiune. Blocurile servesc la limitarea domeniului variabilelor și funcțiilor. Blocurile pot fi goale sau imbricate unul în celălalt. Limitele blocurilor sunt strict definite. De exemplu, într-o instrucțiune if, blocul este delimitat de cod BEGIN..END(în Pascal) sau acolade {...} (în C) sau indentare (în Python).Principiul 6. Toate structurile enumerate trebuie să aibă o intrare și o ieșire.
Structurile de control arbitrare (cum ar fi într-un vas de spaghete) pot avea un număr arbitrar de intrări și ieșiri. Limitându-ne la structurile de control cu o intrare și o ieșire, obținem capacitatea de a construi algoritmi arbitrari de orice complexitate folosind mecanisme simple și fiabile [17] .Principiul 7. Dezvoltarea programului se realizează pas cu pas, folosind metoda „de sus în jos” (metoda de sus în jos) [18] .
În primul rând, este scris textul programului principal, în care, în locul fiecărui fragment logic de text conectat, este inserat un apel către subrutina care va executa acest fragment. În loc de subrutine reale, de lucru, părți fictive sunt inserate în program - stub -uri , care, pentru a spune simplu, nu fac nimic.
Pentru a fi mai precis, un stub satisface cerințele interfeței fragmentului (modulului) care este înlocuit, dar nu își îndeplinește funcțiile sau le îndeplinește parțial. Stub-urile sunt apoi înlocuite sau actualizate la adevărate fragmente (module) cu funcții complete, conform planului de programare. La fiecare etapă a procesului de implementare, programul deja creat trebuie să funcționeze corect în raport cu nivelul inferior. Programul rezultat este verificat și depanat [19] .
După ce programatorul este convins că subrutinele sunt apelate în ordinea corectă (adică structura generală a programului este corectă), rutinele stub sunt înlocuite secvenţial cu altele reale, iar dezvoltarea fiecărei subrutine se realizează în acelaşi mod. mod ca program principal. Dezvoltarea se termină atunci când nu mai sunt cioturi.
O astfel de secvență asigură că, în fiecare etapă de dezvoltare, programatorul se ocupă simultan de un set vizibil și de înțeles de fragmente și poate fi sigur că structura generală a tuturor nivelurilor superioare ale programului este corectă.
Când mențineți și faceți modificări în program, se dovedește ce proceduri trebuie schimbate. Sunt introduse fără a afecta părți ale programului care nu au legătură directă cu acestea. Acest lucru asigură că atunci când se efectuează modificări și se remediază erori, o parte a programului care este în prezent în afara zonei de atenție a programatorului nu va eșua [18] [20] [21] [22] [23] [24 ] .
De asemenea, trebuie remarcat faptul că în „Prefață” la cartea „Programare structurată” [25] , Tony Hoare notează că principiile programării structurate pot fi aplicate în egală măsură la dezvoltarea programelor atât „de sus în jos”, cât și „de jos în sus” [26] .
Subrutinele nu au fost o condiție necesară pentru posibilitatea implementării programării structurate [27] . Inițial, subrutinele au apărut ca un mijloc de optimizare a programelor în ceea ce privește cantitatea de memorie ocupată - au făcut posibil să nu se repete blocuri identice de cod în program, ci să le descrie o dată și să le apeleze după cum este necesar. Pana acum[ când? ] această funcție a subrutinelor a devenit auxiliară, scopul lor principal este structurarea programului pentru a-l face mai ușor de înțeles și de întreținut.
Separarea unui set de acțiuni într-o subrutină și apelarea acestuia după cum este necesar vă permite să selectați în mod logic o subsarcină integrală care are o soluție tipică. O astfel de acțiune are un alt avantaj (pe lângă economisirea memoriei) față de repetarea aceluiași tip de acțiuni. Orice modificare (remediere de erori, optimizare, extensie de funcționalitate) făcută în subrutină se reflectă automat în toate apelurile sale, în timp ce în duplicat, fiecare modificare trebuie făcută la fiecare apariție a codului care este modificat.
Chiar și în acele cazuri în care un set unic de acțiuni este alocat subrutinei, acest lucru este justificat, deoarece permite reducerea dimensiunii blocurilor integrale de cod care alcătuiesc programul, adică pentru a face programul mai ușor de înțeles. si vizibile.
Urmărirea principiilor programării structurate a făcut ca textele programelor, chiar și cele destul de mari, să poată fi citite în mod normal. Înțelegerea programelor a devenit mult mai ușoară, a devenit posibilă dezvoltarea programelor într-un mod industrial normal, când un program poate fi înțeles fără prea multe dificultăți nu doar de autorul său, ci și de alți programatori. Acest lucru a făcut posibilă dezvoltarea unor sisteme software destul de mari pentru acea perioadă de către forțele echipelor de dezvoltare și menținerea acestor complexe timp de mulți ani, chiar și în fața schimbărilor inevitabile în componența personalului.
Programarea structurată îmbunătățește foarte mult claritatea și lizibilitatea programelor [28] . Edward Jordan explică:
Comportamentul multor programe nestructurale este adesea mai apropiat de mișcarea browniană decât de orice proces organizat. Orice încercare de a citi lista determină o persoană la disperare, deoarece un astfel de program execută de obicei mai multe instrucțiuni, după care controlul este transferat într-un punct de câteva pagini mai jos. Mai multe instrucțiuni sunt executate acolo și controlul este din nou transferat într-un punct aleatoriu. Aici mai sunt executați câțiva operatori etc. După mai multe astfel de transmisii, cititorul uită cum a început totul. Și își pierde șirul gândurilor.
Programele structurale, pe de altă parte, tind să fie organizate și executate secvenţial [29] .
Îmbunătățirea lizibilității programelor structurate se datorează faptului că absența instrucțiunii goto permite citirea programului de sus în jos fără întreruperi cauzate de transferurile de control. Drept urmare, puteți descoperi imediat (dintr-o privire) condițiile necesare pentru modificarea unuia sau altuia fragment al programului [30] .
Tehnologia P pentru producția de programe sau „tehnologia de programare bidimensională” [31] a fost creată la Institutul de Cibernetică V. M. Glushkov [32] . Sistemul grafic al tehnologiei de programare R este consacrat în standardele GOST 19.005-85 [33] , GOST R ISO/IEC 8631-94 [34] și standardul internațional ISO 8631Н.
Autorul tehnologiei de programare R, doctor în științe fizice și matematice, profesorul Igor Velbitsky, a propus să se reconsidere însuși conceptul de „structură de program”. Potrivit acestuia, „structura este un concept multidimensional. Prin urmare, afișarea acestui concept cu ajutorul textelor liniare (secvențe de operatori) reduce aproape la nimic avantajele abordării structurale. Posibilitățile asociative enorme ale aparatului vizual și ale aparatului gândirii umane sunt folosite practic în zadar - pentru recunoașterea imaginilor structurale sub forma unei secvențe uniforme de simboluri” [35] .
Metodologia programării structurate bidimensionale diferă semnificativ de programarea structurată unidimensională (textuală) clasică [36] [37] .
Ideile de programare structurată au fost dezvoltate atunci când grafica pe computer nu exista încă, iar instrumentul principal pentru algoritmist și programator a fost textul unidimensional (liniar sau în trepte ). Înainte de apariția graficii pe computer, metodologia de programare structurată clasică era cea mai bună soluție [10] .
Odată cu apariția graficii pe computer, situația s-a schimbat. Folosind mijloacele expresive ale graficii, a devenit posibilă modificarea, dezvoltarea și completarea a trei tipuri de structuri structurale de control de bază (text), precum și abandonarea completă a cuvintelor cheie if , then, else, case , switch, break, while , do, repetați, până când, pentru , foreach, continue, bucla, ieșire, când, ultimul etc. și înlocuiți-le cu grafice de control, adică folosiți programarea structurată bidimensională [33] [36] .
![]() |
|
---|