Vés al contingut

Programació estructurada

De la Viquipèdia, l'enciclopèdia lliure

La programació estructurada es pot definir com un subconjunt o una disciplina de la programació procedimental, un dels paradigmes de programació més importants. Està orientat a millorar la claredat, qualitat i temps de desenvolupament d'un programa d'ordinador recorrent únicament a subrutines i a tres estructures de control bàsiques: seqüència, selecció (if i switch) i iteració (bucles for i while); així mateix, es considera innecessari i contraproduent l'ús de la transferència incondicional (GOTO); aquesta instrucció sol acabar generant l'anomenat codi espagueti, molt més difícil de seguir i de mantenir, a més d'originar nombrosos errors de programació.

Va sorgir a la dècada de 1960, particularment del treball de Böhm i Jacopini,[1] i un famós escrit de 1968: «La sentència goto, considerada perjudicial», d'Edsger Dijkstra.[2] Els seus postulats es veurien reforçats, a nivell teòric, pel teorema del programa estructurat i, a nivell pràctic, per l'aparició de llenguatges com ALGOL, dotat d'estructures de control consistents i ben formades.[3]

Elements

[modifica]

Estructura de la programació estructurada

[modifica]

Els programes estructurats estan formats per estructures simples organitzades de forma jeràrquica que controlen el flux d'execució del programa. Hi ha tres estructures bàsiques:

  • Estructures de concatenació: una seqüència de declaracions o instruccions executades en ordre. En molts llenguatges de programació l'ordre d'execució de les instruccions està marcat pels salts de línia o per altres caràcters especials (per exemple ;).
  • Estructures de selecció: són estructures que permeten seleccionar quines declaracions del programa s'executen depenent del seu estat. Normalment s'expressen utilitzant paraules clau com if..then..else...endif, switch, case, etc.
  • Estructures de repetició: són estructures que permeten repetir l'execució d'una declaració fins que es compleixi una determinada condició. També s'utilitzen paraules clau (p. ex. for, repeat. while, do..until, etc.)

Subrutines

[modifica]

Les subrutines són les unitats a les quals es pot anomenar, com a procediments, funcions, mètodes o subprogrames. S'utilitzen per permetre que una sola declaració faci referència a una seqüència.

Blocs

[modifica]

Els blocs s'utilitzen per permetre que grups de declaracions es tractin com si fossin una sola declaració. Els llenguatges "estructurats en blocs" tenen una sintaxi per tancar estructures d'alguna manera formals, com ara una declaració if entre parèntesis if..fi com a ALGOL 68, o una secció de codi entre claudàtors BEGIN..END, com a PL/I i Pascal, sagnia d'espai en blanc com a Python, o les claus {...} de C i molts llenguatges posteriors.

Història

[modifica]

Orígens de la programació estructurada

[modifica]

A finals dels anys setanta va sorgir la programació estructurada basada en el teorema del programa estructurat, demostrat per Böhm-Jacopini, demostra que tot programa es pot escriure utilitzant únicament els tres tipus d'estructures del llenguatge estructurat (seqüència, selecció i iteració). La majoria de llenguatges de programació estructurats disposen d'un repertori més ampli d'instruccions però aquestes instruccions es poden construir a partir de les instruccions bàsiques.

Aquest teorema proporciona la teoria bàsica per a la programació estructurada. Aquest sistema és de fet l'utilitzat per les unitats centrals de processament (CPU en angles) per l'execució de les instruccions del llenguatge màquina o ensamblador en les màquines simples que segueixen el mòdel de Von Neumann. Per tant, és interessant indicar que totes les aplicacions finalment s'executen en un llenguatge estructurat de baix nivell tot i que el llenguatge d'alt nivell llegit per la CPU des de la memòria principal del sistemes estigui implementat amb un altre paradigma de disseny (com per exemple Programació Orientada a Objectes).

El teorema del programa estructura estructurat no defineix com s'han d'escriure programes amb llenguatge estructurat. Les contribucions que gent com Edsger Dijkstra, Robert W. Floyd, Tony Hoare o David Gries van fer a finals dels anys 60 van posar les bases dels llenguatges estructurats.

Desviacions habituals

[modifica]

Tot i que ara el goto s'ha substituït en gran part per les construccions estructurades de selecció (si/llavors/els altres) i repetició (mentre i per), pocs llenguatges estan purament estructurats. La desviació més comuna, que es troba en molts idiomes, és l'ús d'una sentència de retorn per sortir anticipadament d'una subrutina. Això dona lloc a múltiples punts de sortida, en lloc del punt de sortida únic requerit per la programació estructurada. Hi ha altres construccions per gestionar casos que són incòmodes en la programació purament estructurada.

Sortida anticipada

[modifica]

La desviació més comuna de la programació estructurada és la sortida anticipada d'una funció o bucle. A nivell de funcions, això és una declaració return. A nivell de bucles, això és una declaració break (acabar el bucle) o una declaració continue (acabar la iteració actual, continuar amb la següent iteració). En la programació estructurada, aquests es poden replicar afegint branques o proves addicionals, però per als retorns del codi imbricat això pot afegir una complexitat important. C és un exemple primerenc i destacat d'aquestes construccions. Alguns llenguatges més nous també tenen "ruptures etiquetades", que permeten sortir de més que el bucle més intern. Les excepcions també permeten una sortida anticipada, però tenen conseqüències addicionals i, per tant, es tracten a continuació.

Les sortides múltiples poden sorgir per diversos motius, la majoria de vegades perquè la subrutina no té més feina a fer (si retorna un valor, s'ha completat el càlcul), o s'ha trobat amb circumstàncies "excepcionals" que impedeixen que continuï, per tant, cal maneig d'excepcions.

El problema més comú a la sortida primerenca és que les instruccions de neteja o finals no s'executen; per exemple, la memòria assignada no es desassigna o els fitxers oberts no es tanquen, provocant fuites de memòria o fuites de recursos. S'han de fer a cada lloc de retorn, que és fràgil i pot provocar errors fàcilment. Per exemple, en un desenvolupament posterior, un desenvolupador podria passar per alt una instrucció de retorn, i una acció que s'hauria de realitzar al final d'una subrutina (per exemple, una sentència trace) pot no ser realitzada. en tots els casos. Els idiomes sense declaració de retorn, com ara Pascal estàndard i Seed7, no tenen aquest problema.

La majoria dels idiomes moderns ofereixen suport a nivell d'idioma per evitar aquestes filtracions;[4] vegeu la discussió detallada a gestió de recursos. Normalment, això es fa mitjançant la protecció de desenrotllament, que garanteix que cert codi s'executa quan l'execució surt d'un bloc; aquesta és una alternativa estructurada a tenir un bloc de neteja i a goto. Això es coneix més sovint com a try...finally, i es considera una part del maneig d'excepcions. En cas de múltiples declaracions return introduint try...finally, sense excepcions pot semblar estrany. Existeixen diverses tècniques per encapsular la gestió de recursos. Un enfocament alternatiu, que es troba principalment en C++, és l'adquisició de recursos és la inicialització (Resource Acquisition Is Initialization o RAII), que utilitza el desenrotllament normal de la pila (desassignació de variables) a la sortida de la funció per cridar destructors en variables locals per desassignar recursos.

Kent Beck, Martin Fowler i els seus coautors han argumentat en els seus llibres sobre refacció que els condicionals imbricats poden ser més difícils d'entendre que un determinat tipus d'estructura més plana utilitzant múltiples sortides predicades per clàusula de guarda. El seu llibre de 2009 afirma que "un punt de sortida no és realment una regla útil. La claredat és el principi clau: si el mètode és més clar amb un punt de sortida, utilitzeu un punt de sortida; en cas contrari, no". Ofereixen una solució de llibre de cuina per transformar una funció que consisteix només en condicionals imbricats en una seqüència de declaracions de retorn (o llançament) vigilades, seguides d'un únic bloc sense protecció, que pretén contenir el codi per al cas comú, mentre que les declaracions guardades són suposadament tractar amb els menys comuns (o amb errors).[5] Herb Sutter i Andrei Alexandrescu també argumenten al seu llibre de consells C++ de 2004 que el punt de sortida únic és un requisit obsolet.[6]

En el seu llibre de text de 2004, David Watt escriu que "els fluxos de control de múltiples sortides d'una sola entrada són sovint desitjables". Utilitzant la noció de marc de Tennent de seqüenciador, Watt descriu uniformement les construccions de flux de control que es troben en els llenguatges de programació contemporanis i intenta explicar per què determinats tipus de seqüenciadors són preferibles a altres en el context de fluxos de control de múltiples sortides. Watt escriu que els gotos sense restriccions (seqüenciadors de salt) són dolents perquè la destinació del salt no s'explica per si mateixa per al lector d'un programa fins que el lector troba i examina l'etiqueta o l'adreça real que és l'objectiu del salt. En canvi, Watt argumenta que la intenció conceptual d'un seqüenciador de retorn és clara des del seu propi context, sense haver d'examinar la seva destinació. Watt escriu que una classe de seqüenciadors coneguda com a "seqüenciadors d'escapament", definit com un "seqüenciador que acaba l'execució d'una ordre o procediment que inclou textualment", inclou tant ruptures de bucles (incloses interrupcions de diversos nivells) com declaracions de retorn. Watt també assenyala que, mentre que els seqüenciadors de salts (gotos) s'han restringit una mica en idiomes com C, on l'objectiu ha de ser un bloc dins del local o un bloc exterior que engloba, aquesta restricció per si sola no és suficient per fer que la intenció dels gotos en C sigui pròpia. -descrivint i així encara poden produir "codi d'espaguetis". Watt també examina com es diferencien els seqüenciadors d'excepció dels seqüenciadors d'escapament i salt; això s'explica a la següent secció d'aquest article.[7]

En contrast amb l'anterior, Bertrand Meyer va escriure al seu llibre de text de 2009 que instruccions com break i continue "són només els vells goto amb pell d'ovella" i es desaconsella el seu ús.[8]

Gestió d'excepcions

[modifica]

Basant-se en l'error de codificació de l'incident de l'Ariane 501, el desenvolupador de programari Jim Bonang argumenta que qualsevol excepció llançada des d'una funció infringeix el paradigma de sortida única i proposa que totes les excepcions entre procediments s'haurien de prohibir. Bonang proposa que tot C++ conforme a una única sortida s'hauria d'escriure seguint la línia de:

bool MyCheck1() throw() {
 bool success = false;
 try {
 // Fer alguna cosa que pugui generar excepcions.
 if (!MyCheck2()) {
 throw SomeInternalException();
 }
 // Un altre codi semblant a l'anterior.
 success = true;
 } catch (...) {
 // Totes les excepcions capturades i registrades.
 }
 return success;
}

Peter Ritchie també assenyala que, en principi, fins i tot un sol throw just abans del return en una funció constitueix una violació del principi de sortida única, però argumenta que les regles de Dijkstra es van escriure en un temps abans que el maneig d'excepcions es convertís en un paradigma en llenguatges de programació, per la qual cosa proposa permetre qualsevol nombre de punts de llançament a més d'un únic punt de retorn. . Assenyala que les solucions que inclouen excepcions pel bé de crear una única sortida tenen una profunditat de nidificació més alta i, per tant, són més difícils d'entendre, i fins i tot acusa els que proposen aplicar aquestes solucions als llenguatges de programació que admeten excepcions de participar en el pensament del culte de Cargo.[9]

David Watt també analitza el maneig d'excepcions en el marc dels seqüenciadors (introduït en aquest article a la secció anterior sobre sortides primerenques.) Watt assenyala que una situació anormal (generalment exemplificada amb desbordaments aritmètics o entrada/ errors de sortida com el fitxer no trobat) és una mena d'error que "es detecta en alguna unitat de programa de baix nivell, però [per a la qual] un controlador es troba més naturalment en una unitat de programa d'alt nivell". Per exemple, un programa pot contenir diverses trucades per llegir fitxers, però l'acció a realitzar quan no es troba un fitxer depèn del significat (propòsit) del fitxer en qüestió per al programa i, per tant, no es pot gestionar una rutina per a aquesta situació anormal. localitzat en codi de sistema de baix nivell. Watts assenyala, a més, que la introducció de proves de senyals d'estat a la persona que truca, com implicaria la programació estructurada d'una sola sortida o fins i tot els seqüenciadors de retorn (sortides múltiples), resulta en una situació en què "el codi de l'aplicació tendeix a desordenar-se per les proves de senyals d'estat" i que "el programador pot oblidar o deixar de provar un indicador d'estat. De fet, les situacions anormals representades per senyals d'estat s'ignoren per defecte!" Observa que, a diferència de les proves de senyals d'estat, les excepcions tenen el comportament oposat al comportament predeterminat, fent que el programa finalitzi tret que el programador tracti explícitament l'excepció d'alguna manera, possiblement afegint codi a voluntàriament ignorant-ho, Basant-se en aquests arguments, Watt conclou que els seqüenciadors de salt o els seqüenciadors d'escapament (que s'han comentat a la secció anterior) no són tan adequats com un seqüenciador d'excepcions dedicat amb la semàntica comentada anteriorment.[10]

El llibre de text de Louden i Lambert subratlla que el maneig d'excepcions difereix de les construccions de programació estructurada com ara bucles de while perquè la transferència de control "es configura en un punt diferent del programa que aquell on té lloc la transferència real. En el punt on es produeix realment la transferència, pot ser que no hi hagi cap indicació sintàctica que el control es transferirà de fet."[11] El professor d'informàtica Arvind Kumar Bansal també assenyala que en els llenguatges que implementen el maneig d'excepcions, fins i tot estructures de control com ara for, que tenen la propietat de sortida única en absència d'excepcions, ja no la tenen en presència d'excepcions, perquè una excepció pot provocar prematurament una sortida anticipada en qualsevol part de l'estructura de control; per exemple si init() introdueix una excepció for (init(); check(); increm()), aleshores no s'arriba al punt de sortida habitual després de check().[12] Citant diversos estudis previs d'altres (1999-2004) i els seus propis resultats, Westley Weimer i George Necula van escriure que un problema important amb les excepcions és que "creen camins de flux de control ocults que són difícils de raonar per als programadors".[13]

La necessitat de limitar el codi als punts de sortida únic apareix en alguns entorns de programació contemporanis centrats en la computació paral·lela, com ara OpenMP. Les diferents construccions paral·leles d'OpenMP, com ara parallel do, no permetre sortides primerenques de l'interior a l'exterior de la construcció paral·lela; aquesta restricció inclou tota mena de sortides, des de break a excepcions de C++, però tots ells es permeten dins de la construcció paral·lela si l'objectiu de salt també hi és.[14]

Entrada múltiple

[modifica]

Més rarament, els subprogrames permeten una "entrada" múltiple. Normalment només és una entrada "re" en una corutina (o generador/semicorutina), on un el subprograma dona control (i possiblement un valor), però després es pot reprendre on es va deixar. Hi ha una sèrie d'usos comuns d'aquesta programació, sobretot per a streams (especialment d'entrada/sortida), màquines d'estat i concurrència. Des del punt de vista de l'execució de codi, el rendiment des d'una corrutina està més a prop de la programació estructurada que el retorn d'una subrutina, ja que el subprograma en realitat no ha finalitzat i continuarà quan es torni a cridar, no és una sortida anticipada. Tanmateix, les corrutines signifiquen que diversos subprogrames tenen un estat d'execució, en lloc d'una única pila de trucades de subrutines, i per tant introdueixen una forma diferent de complexitat.

És molt rar que els subprogrames permetin l'entrada a una posició arbitrària del subprograma, ja que en aquest cas l'estat del programa (com els valors variables) no és inicialitzat o és ambigu, i això és molt semblant a un goto.

Màquines d'estat

[modifica]

Alguns programes, especialment els analitzadors i els protocols de comunicacions, tenen una sèrie d'estats que se succeeixen d'una manera que no es redueix fàcilment a les estructures bàsiques, i alguns programadors implementen els canvis d'estat amb un salt al nou estat. Aquest tipus de canvi d'estat s'utilitza sovint al nucli Linux.

Tanmateix, és possible estructurar aquests sistemes fent que cada canvi d'estat sigui un subprograma separat i utilitzant una variable per indicar l'estat actiu (vegeu trampolí). Alternativament, aquests es poden implementar mitjançant corrutines, que prescindeixen del trampolí.

Avantatges de la programació estructurada

[modifica]

Entre els avantatges de la programació estructurada sobre el model anterior (avui anomenat despectivament codi espagueti), cal esmentar les següents:

  • Els programes són més fàcils d'entendre, poden ser llegits de forma seqüencial i no hi ha necessitat d'haver de rastrejar salts de línies (GOTO) dins dels blocs de codi per intentar entendre la lògica interna.
  • L'estructura dels programes és clara, ja que les instruccions estan més lligades o relacionades entre si.
  • S'optimitza l'esforç en les fases de proves i depuració. El seguiment de les fallades o errors del programa (debugging), i amb ell la seva detecció i correcció, es facilita enormement.
  • Es redueixen els costos de manteniment. Anàlogament a la depuració, durant la fase de manteniment, modificar o estendre els programes és més fàcil.
  • Els programes són més senzills i més ràpids de confeccionar.
  • S'incrementa el rendiment dels programadors.

Llenguatges de programació estructurada

[modifica]

Si bé és possible desenvolupar la programació estructurada en qualsevol llenguatge de programació, resulta més idoni un llenguatge de programació procedimental. Alguns dels llenguatges utilitzats inicialment per a programació estructurada inclouen ALGOL, Pascal, PL/I i Ada, però la majoria dels nous llenguatges de programació procedimentals des de llavors han inclòs característiques per fomentar la programació estructurada i de vegades, deliberadament, ometen característiques[15] en un esforç per fer més difícil la programació no estructurada.

Nous paradigmes

[modifica]

Amb posterioritat a la programació estructurada s'han creat nous paradigmes tals com la programació modular, la programació orientada a objectes, la programació per capes i altres, així com nous entorns de programació que faciliten la programació de grans aplicacions i sistemes.

Desviacions habituals

[modifica]

Encara que goto ha estat substituït en gran manera per les construccions estructurades de selecció (if/then/else) i repetició (while i for), pocs llenguatges són purament estructurats. La desviació més comuna, trobada en molts llenguatges, és l'ús d'una sentència return per a la sortida primerenca d'una subrutina. Això resulta en múltiples punts de sortida, en lloc de l'únic punt de sortida requerit per la programació estructurada. Hi ha altres construccions per manejar casos que són incòmodes a la programació purament estructurada.

Llistes de programari famós desenvolupat utilitzant programació estructurada

[modifica]

Programari de sistema operatiu

[modifica]

Aplicacions

[modifica]

Jocs

[modifica]

Emuladors

[modifica]

Referències

[modifica]
  1. Böhm, Jacopini. "Flow diagrams, turing machines and languages with only two formation rules" Comm. ACM, 9(5):366-371, May 1966, doi=10.1145/355592.365646
  2. Edsger Dijkstra «Go To Statement Considered Harmful» (PDF). Communications of the ACM, 11, 3, 3-1968, pàg. 147-148. 10.1145/362929.362947. «The unbridled use of the go to statement has as an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress. ... The go to statement as it stands is just too primitive, it is too much an invitation to make a mess of one's program.»
  3. Clark, Leslie B. Wilson, Robert G.; Robert, Clark. Comparative programming languages. 3rd. Harlow, England: Addison-Wesley, 2000, p. 20. ISBN 9780201710120. 
  4. Elder, Matt; Jackson, Steve; Liblit, Ben (October 2008). Code Sandwiches (PDF) (Technical report). University of Wisconsin–Madison. 1647.
  5. Jay Fields. Refactoring: Ruby Edition. Pearson Education, 2009, p. 274–279. ISBN 978-0-321-60350-0. 
  6. Herb Sutter. C++ Coding Standards: 101 Rules, Guidelines, and Best Practices. Pearson Education, 2004. ISBN 978-0-13-265442-5. «Example 4: Single entry, single exit ("SESE"). Historically, some coding standards have required that each function have exactly one exit, meaning one return statement. Such a requirement is obsolete in languages that support exceptions and destructors, where functions typically have numerous implicit exits.» 
  7. Watt i Findlay, 2004, p. 215–221.
  8. Bertrand Meyer. Touch of Class: Learning to Program Well with Objects and Contracts. Springer Science & Business Media, 2009, p. 189. ISBN 978-3-540-92144-8. 
  9. «Single-Entry, Single-Exit, Should It Still be Applicable in Object-oriented Languages?». Peter Ritchie's MVP Blog, 07-03-2008. Arxivat de l'original el 2012-11-14. [Consulta: 15 juliol 2014].
  10. Watt i Findlay, 2004, p. 221–222.
  11. Kenneth C. Louden. Programming Languages: Principles and Practices. 3rd. Cengage Learning, 2011, p. 423. ISBN 978-1-111-52941-3. 
  12. Arvind Kumar Bansal. Introduction to Programming Languages. CRC Press, 2013, p. 135. ISBN 978-1-4665-6514-2. 
  13. «Exceptional Situations and Program Reliability». ACM Transactions on Programming Languages and Systems, vol. 30, 2, 2008. DOI: 10.1145/1330017.1330019.
  14. Rohit Chandra. Parallel Programming in OpenMP. Morgan Kaufmann, 2001, p. 45. ISBN 978-1-55860-671-5. 
  15. GOTO per exemple

Fonts

[modifica]

Bibliografia

[modifica]
  • Dijkstra, Edsger W. «Letters to the editor: Go to statement considered harmful». Communications of the ACM, vol. 11, 3, 3-1968, pàg. 147–148. DOI: 10.1145/362929.362947. ISSN: 0001-0782.
  • García-Bermejo Giner, José Rafael. Programación estructurada en C. 1.ª. Pearson Prentice Hall, 2008. 
  • Valls Ferrán, José María; Camacho Fernández, David. Programación estructurada y algoritmos en Pascal. 1.ª. Pearson Alhambra, 2004. 
  • Programación estructurada II. 1.ª. Enseñanza Técnica y Sistemas, S.A., 2000. 
  • Pseudocódigos y programación estructurada. 1.ª. Centro Técnico Europeo de Enseñanzas Profesionales, 1997. 
  • Sánchez Andrés, María Ángeles. Programación estructurada y fundamentos de programación. 1.ª. McGraw-Hill / Interamericana de España, S.A., 1996.