stringtranslate.com

Programación estructurada

La programación estructurada es un paradigma de programación destinado a mejorar la claridad, la calidad y el tiempo de desarrollo de un programa de computadora mediante el uso extensivo de las construcciones de flujo de control estructurado de selección ( si/entonces/si no ) y repetición ( mientras y para ), estructuras de bloques. y subrutinas .

Surgió a finales de la década de 1950 con la aparición de los lenguajes de programación ALGOL 58 y ALGOL 60 , [1] este último incluía soporte para estructuras de bloques. Los factores que contribuyen a su popularidad y aceptación generalizada, al principio en el mundo académico y luego entre los profesionales, incluyen el descubrimiento de lo que ahora se conoce como el teorema del programa estructurado en 1966, [2] y la publicación del influyente " Ir a la declaración considerada dañina " . Carta abierta de 1968 del informático holandés Edsger W. Dijkstra , quien acuñó el término "programación estructurada". [3]

La programación estructurada se utiliza con mayor frecuencia con desviaciones que permiten programas más claros en algunos casos particulares, como cuando se debe realizar un manejo de excepciones .

Elementos

Estructuras de Control

Siguiendo el teorema del programa estructurado , todos los programas se consideran compuestos de tres estructuras de control :

Representación gráfica de los tres patrones básicos (secuencia, selección y repetición) mediante diagramas NS (azul) y diagramas de flujo (verde).

Subrutinas

Subrutinas ; Las unidades invocables, como procedimientos, funciones, métodos o subprogramas, se utilizan para permitir que una sola declaración haga referencia a una secuencia.

Bloques

Los bloques se utilizan para permitir que grupos de declaraciones se traten como si fueran una sola declaración. Los lenguajes estructurados en bloques tienen una sintaxis para encerrar estructuras de alguna manera formal, como una declaración if entre corchetes como if..fien ALGOL 68 , o una sección de código entre corchetes BEGIN..END, como en PL/I y Pascal , sangría de espacios en blanco como en Python , o las llaves {...}de C y muchos lenguajes posteriores .

Lenguajes de programación estructurados

Es posible realizar programación estructurada en cualquier lenguaje de programación, aunque es preferible utilizar algo parecido a un lenguaje de programación procedimental . [ cita necesaria ] [ aclaración necesaria ] Algunos de los lenguajes utilizados inicialmente para la programación estructurada incluyen: ALGOL , Pascal , PL/I , Ada y RPL , pero la mayoría de los nuevos lenguajes de programación procedimental desde entonces han incluido características para fomentar la programación estructurada y, a veces, deliberadamente. omitió funciones, en particular GOTO, en un esfuerzo por hacer la programación no estructurada más difícil.La programación estructurada (a veces conocida como programación modular [ cita necesaria ] ) impone una estructura lógica en el programa que se escribe para hacerlo más eficiente y más fácil de entender y modificar.

Historia

Fundamento teórico

El teorema del programa estructurado proporciona la base teórica de la programación estructurada. Afirma que tres formas de combinar programas (secuenciación, selección e iteración) son suficientes para expresar cualquier función computable . Esta observación no se originó con el movimiento de programación estructurada; estas estructuras son suficientes para describir el ciclo de instrucción de una unidad central de procesamiento , así como el funcionamiento de una máquina de Turing . Por lo tanto, un procesador siempre está ejecutando un "programa estructurado" en este sentido, incluso si las instrucciones que lee de la memoria no forman parte de un programa estructurado. Sin embargo, los autores suelen acreditar el resultado a un artículo de 1966 de Böhm y Jacopini, posiblemente porque el propio Dijkstra citó este artículo. [4] El teorema del programa estructurado no aborda cómo escribir y analizar un programa estructurado de manera útil. Estas cuestiones se abordaron a finales de los años 1960 y principios de los 1970, con importantes contribuciones de Dijkstra , Robert W. Floyd , Tony Hoare , Ole-Johan Dahl y David Gries .

Debate

PJ Plauger , uno de los primeros en adoptar la programación estructurada, describió su reacción al teorema del programa estructurado:

Nosotros, los conversos, agitamos esta interesante noticia ante las narices de los programadores no reconstruidos en lenguaje ensamblador que seguían presentando retorcidos fragmentos de lógica y diciendo: "Apuesto a que no puedes estructurar esto". Ni las pruebas de Böhm y Jacopini ni nuestros repetidos éxitos en la escritura de código estructurado les permitieron convencerse un día antes de lo que estaban preparados para convencerse a sí mismos. [5]

Donald Knuth aceptó el principio de que los programas deben escribirse teniendo en cuenta la demostrabilidad, pero no estuvo de acuerdo con abolir la declaración GOTO y, a partir de 2018, ha seguido utilizándola en sus programas. [6] En su artículo de 1974, "Programación estructurada con declaraciones Goto", [7] dio ejemplos en los que creía que un salto directo conduce a un código más claro y eficiente sin sacrificar la demostrabilidad. Knuth propuso una restricción estructural más flexible: debería ser posible dibujar un diagrama de flujo de un programa con todas las ramas hacia adelante a la izquierda, todas las ramas hacia atrás a la derecha y ninguna rama que se cruce entre sí. Muchos de los conocedores de compiladores y teoría de grafos han abogado por permitir solo gráficos de flujo reducibles [ cuando se definen como? ] . [ ¿OMS? ]

Los teóricos de la programación estructurada ganaron un aliado importante en la década de 1970 después de que el investigador de IBM Harlan Mills aplicara su interpretación de la teoría de la programación estructurada al desarrollo de un sistema de indexación para los archivos de investigación del New York Times . El proyecto fue un gran éxito de ingeniería y los gerentes de otras empresas lo citaron en apoyo de la adopción de programación estructurada, aunque Dijkstra criticó las formas en que la interpretación de Mills difería del trabajo publicado. [8]

Todavía en 1987 todavía era posible plantear la cuestión de la programación estructurada en una revista de informática. Frank Rubin lo hizo ese año con una carta abierta titulada "'GOTO Considerado Dañino' Considerado Dañino". [9] Siguieron numerosas objeciones, incluida una respuesta de Dijkstra que criticaba duramente tanto a Rubin como a las concesiones que otros escritores hicieron al responderle.

Resultado

A finales del siglo XX, casi todos los informáticos estaban convencidos de que era útil aprender y aplicar los conceptos de programación estructurada. Los lenguajes de programación de alto nivel que originalmente carecían de estructuras de programación, como FORTRAN , COBOL y BASIC , ahora las tienen.

Desviaciones comunes

Si bien goto ha sido reemplazado en gran medida por construcciones estructuradas de selección (si/entonces/si no) y repetición (mientras y para), pocos lenguajes son puramente estructurados. La desviación más común, que se encuentra en muchos idiomas, es el uso de una declaración de retorno para la salida anticipada de una subrutina. Esto da como resultado múltiples puntos de salida, en lugar del único punto de salida requerido por la programación estructurada. Existen otras construcciones para manejar casos que resultan incómodos en la programación puramente estructurada.

salida anticipada

La desviación más común de la programación estructurada es la salida anticipada de una función o bucle. A nivel de funciones, esto es una returndeclaración. A nivel de bucles, esto es una breakdeclaración (terminar el bucle) o continueuna declaración (terminar la iteración actual, continuar con la siguiente iteración). En la programación estructurada, estos se pueden replicar agregando ramas o pruebas adicionales, pero para los retornos de código anidado esto puede agregar una complejidad significativa. C es un ejemplo temprano y destacado de estas construcciones. Algunos lenguajes más nuevos también tienen "descansos etiquetados", que permiten salir de algo más que el bucle más interno. Las excepciones también permiten la salida anticipada, pero tienen consecuencias adicionales y, por lo tanto, se tratan a continuación.

Pueden surgir múltiples salidas por una variedad de razones, la mayoría de las veces porque la subrutina no tiene más trabajo que hacer (si devuelve un valor, ha completado el cálculo), o ha encontrado circunstancias "excepcionales" que le impiden continuar, por lo que necesita manejo de excepciones.

El problema más común en la salida anticipada es que las declaraciones finales o de limpieza no se ejecutan; por ejemplo, la memoria asignada no se desasigna o los archivos abiertos no se cierran, lo que provoca pérdidas de memoria o de recursos . Estos deben realizarse en cada sitio de devolución, ya que es frágil y puede provocar errores fácilmente. Por ejemplo, en un desarrollo posterior, un desarrollador podría pasar por alto una declaración de retorno y una acción que debería realizarse al final de una subrutina (por ejemplo, una declaración de seguimiento ) podría no realizarse en todos los casos. Los lenguajes sin declaración de devolución, como Pascal estándar y Seed7 , no tienen este problema.

La mayoría de los lenguajes modernos brindan soporte a nivel de idioma para evitar este tipo de filtraciones; [10] consulte la discusión detallada en gestión de recursos . Por lo general, esto se hace a través de la protección de desenrollado, que garantiza que se garantice la ejecución de cierto código cuando la ejecución sale de un bloque; Esta es una alternativa estructurada a tener un bloque de limpieza y un archivo goto. Esto suele denominarse try...finally,y considerarse parte del manejo de excepciones . En caso de que se introduzcan varias returndeclaraciones try...finally,sin excepciones, puede parecer extraño. Existen varias técnicas para encapsular la gestión de recursos. Un enfoque alternativo, que se encuentra principalmente en C++, es La adquisición de recursos es inicialización , que utiliza el desenrollado normal de la pila (desasignación de variables) en la salida de la función para llamar a destructores en variables locales para desasignar recursos.

Kent Beck , Martin Fowler y sus coautores han argumentado en sus libros sobre refactorización que los condicionales anidados pueden ser más difíciles de entender que cierto tipo de estructura más plana que utiliza múltiples salidas predicadas por cláusulas de guardia . Su libro de 2009 afirma rotundamente que "un punto de salida realmente no es una regla útil. La claridad es el principio clave: si el método es más claro con un punto de salida, utilice un punto de salida; de lo contrario, no lo haga". Ofrecen una solución de libro de recetas para transformar una función que consta únicamente de condicionales anidados en una secuencia de declaraciones de retorno (o lanzamiento) protegidas, seguidas de un único bloque no protegido, que pretende contener el código para el caso común, mientras que las declaraciones protegidas son se supone que se ocupa de los menos comunes (o de los errores). [11] Herb Sutter y Andrei Alexandrescu también argumentan en su libro de consejos de C++ de 2004 que el punto de salida única es un requisito obsoleto. [12]

En su libro de texto de 2004, David Watt escribe que "los flujos de control de múltiples salidas y de entrada única suelen ser deseables". Utilizando la noción marco de secuenciador de Tennent , Watt describe uniformemente las construcciones de flujo de control que se encuentran en los lenguajes de programación contemporáneos e intenta explicar por qué ciertos tipos de secuenciadores son preferibles a otros en el contexto de flujos de control de salidas múltiples. Watt escribe que los gotos (secuenciadores de salto) sin restricciones son malos porque el destino del salto no se explica por sí mismo para el lector de un programa hasta que el lector encuentra y examina la etiqueta o dirección real que es el objetivo del salto. Por el contrario, Watt sostiene que la intención conceptual de un secuenciador de retorno queda clara a partir de su propio contexto, sin tener que examinar su destino. Watt escribe que una clase de secuenciadores conocidos como secuenciadores de escape , definidos como un "secuenciador que finaliza la ejecución de un comando o procedimiento que incluye texto", abarca tanto las interrupciones de los bucles (incluidas las interrupciones de varios niveles) como las declaraciones de retorno. Watt también señala que si bien los secuenciadores de salto (gotos) han estado algo restringidos en lenguajes como C, donde el objetivo debe ser un bloque interno o un bloque externo envolvente, esa restricción por sí sola no es suficiente para hacer que la intención de los gotos en C sea propia. -describir y así todavía pueden producir " código espagueti ". Watt también examina en qué se diferencian los secuenciadores de excepción de los secuenciadores de escape y salto; esto se explica en la siguiente sección de este artículo. [13]

A diferencia de lo anterior, Bertrand Meyer escribió en su libro de texto de 2009 que instrucciones como breaky continue"son sólo viejas gotocon piel de oveja" y desaconsejó encarecidamente su uso. [14]

Manejo de excepciones

Basado en el error de codificación del desastre de Ariane 501 , el desarrollador de software Jim Bonang sostiene que cualquier excepción lanzada desde una función viola el paradigma de salida única y propone que todas las excepciones entre procedimientos deberían prohibirse. Bonang propone que todo C++ compatible con salida única debería escribirse de la siguiente manera:

bool MyCheck1 () throw () { bool éxito = falso ; try { // Haz algo que pueda generar excepciones. if ( ! MyCheck2 ()) { lanzar SomeInternalException (); } // Otro código similar al anterior. éxito = verdadero ; } catch (...) { // Todas las excepciones detectadas y registradas. } devolución exitosa ; }                            

Peter Ritchie también señala que, en principio, incluso un solo throwderecho antes de returnen una función constituye una violación del principio de salida única, pero sostiene que las reglas de Dijkstra se escribieron en una época anterior a que el manejo de excepciones se convirtiera en un paradigma en los lenguajes de programación, por lo que propone permitir cualquier número de puntos de lanzamiento además de un único punto de retorno. Señala que las soluciones que envuelven excepciones con el fin de crear una salida única tienen una mayor profundidad de anidamiento y, por lo tanto, son más difíciles de comprender, e incluso acusa a quienes proponen aplicar tales soluciones a lenguajes de programación que admiten excepciones de participar en un pensamiento de culto a la carga. . [15]

David Watt también analiza el manejo de excepciones en el marco de los secuenciadores (introducido en este artículo en la sección anterior sobre salidas tempranas). Watt señala que una situación anormal (generalmente ejemplificada con desbordamientos aritméticos o fallas de entrada/salida como archivo no encontrado) es un tipo de error que "se detecta en alguna unidad de programa de bajo nivel, pero [para el cual] un controlador se ubica más naturalmente en una unidad de programa de alto nivel". Por ejemplo, un programa puede contener varias llamadas para leer archivos, pero la acción a realizar cuando no se encuentra un archivo depende del significado (propósito) del archivo en cuestión para el programa y, por lo tanto, no se puede crear una rutina de manejo para esta situación anormal. ubicado en el código del sistema de bajo nivel. Watts señala además que la introducción de pruebas de indicadores de estado en la persona que llama, como implicaría la programación estructurada de salida única o incluso secuenciadores de retorno (salidas múltiples), da como resultado una situación en la que "el código de la aplicación tiende a saturarse con pruebas de indicadores de estado" y que "el programador podría, por olvido o por pereza, omitir probar un indicador de estado. De hecho, las situaciones anormales representadas por indicadores de estado se ignoran de forma predeterminada". Señala que, a diferencia de las pruebas de indicadores de estado, las excepciones tienen el comportamiento predeterminado opuesto , lo que hace que el programa finalice a menos que el programador trate explícitamente la excepción de alguna manera, posiblemente agregando código para ignorarla intencionalmente. Con base en estos argumentos, Watt concluye que los secuenciadores de salto o los secuenciadores de escape (discutidos en la sección anterior) no son tan adecuados como un secuenciador de excepción dedicado con la semántica discutida anteriormente. [dieciséis]

El libro de texto de Louden y Lambert enfatiza que el manejo de excepciones difiere de las construcciones de programación estructurada como whilelos bucles porque la transferencia de control "se establece en un punto diferente del programa que aquel donde tiene lugar la transferencia real. En el punto donde realmente ocurre la transferencia , puede que no haya ninguna indicación sintáctica de que el control será efectivamente transferido." [17] El profesor de informática Arvind Kumar Bansal también señala que en los lenguajes que implementan el manejo de excepciones, incluso las estructuras de control como for, que tienen la propiedad de salida única en ausencia de excepciones, ya no la tienen en presencia de excepciones, porque una excepción puede ocurrir prematuramente. provocar una salida anticipada en cualquier parte de la estructura de control; por ejemplo, si init()se produce una excepción for (init(); check(); increm()), entonces no se alcanza el punto de salida habitual después de check(). [18] Citando múltiples estudios previos realizados por otros (1999-2004) y sus propios resultados, Westley Weimer y George Necula escribieron que un problema importante con las excepciones es que "crean rutas de flujo de control ocultas sobre las cuales es difícil razonar para los programadores". . [19]

La necesidad de limitar el código a puntos de salida única aparece en algunos entornos de programación contemporáneos centrados en la computación paralela , como OpenMP . Las diversas construcciones paralelas de OpenMP, como parallel do, no permiten salidas tempranas desde el interior al exterior de la construcción paralela; esta restricción incluye todo tipo de salidas, desde breakexcepciones de C++, pero todas ellas están permitidas dentro de la construcción paralela si el objetivo de salto también está dentro de ella. [20]

Entrada multiple

Más raramente, los subprogramas permiten entradas múltiples. Por lo general, esto es solo un reingreso a una corrutina (o generador /semirutina), donde un subprograma produce control (y posiblemente un valor), pero luego se puede reanudar donde lo dejó. Hay una serie de usos comunes de dicha programación, en particular para flujos (particularmente de entrada/salida), máquinas de estado y concurrencia. Desde el punto de vista de la ejecución de código, ceder desde una corrutina está más cerca de la programación estructurada que regresar de una subrutina, ya que el subprograma en realidad no ha terminado y continuará cuando se le vuelva a llamar; no es una salida anticipada. Sin embargo, las corrutinas significan que múltiples subprogramas tienen estado de ejecución (en lugar de una única pila de llamadas de subrutinas) y, por lo tanto, introducen una forma diferente de complejidad.

Es muy raro que los subprogramas permitan la entrada a una posición arbitraria en el subprograma, ya que en este caso el estado del programa (como los valores de las variables) no está inicializado o es ambiguo, y esto es muy similar a un goto.

maquinas de estado

Algunos programas, particularmente analizadores y protocolos de comunicaciones , tienen una cantidad de estados que se suceden entre sí de una manera que no se reduce fácilmente a las estructuras básicas, y algunos programadores implementan los cambios de estado con un salto al nuevo estado. Este tipo de cambio de estado se utiliza a menudo en el kernel de Linux. [ cita necesaria ]

Sin embargo, es posible estructurar estos sistemas haciendo que cada cambio de estado sea un subprograma separado y usando una variable para indicar el estado activo (ver trampolín ). Alternativamente, esto se puede implementar a través de corrutinas, que prescinden del trampolín.

Ver también

Referencias

Citas

  1. ^ Clark, Leslie B. Wilson, Robert G.; Robert, Clark (2000). Lenguajes de programación comparativos (3ª ed.). Harlow, Inglaterra: Addison-Wesley. pag. 20.ISBN _ 9780201710120. Archivado desde el original el 26 de noviembre de 2015 . Consultado el 25 de noviembre de 2015 .{{cite book}}: CS1 maint: multiple names: authors list (link)
  2. ^ Böhm y Jacopini 1966.
  3. ^ Dijkstra 1968, pág. 147, "El uso desenfrenado de la declaración go to tiene como consecuencia inmediata que se vuelve terriblemente difícil encontrar un conjunto significativo de coordenadas para describir el progreso del proceso... La declaración go to tal como está es demasiado primitiva , es demasiado una invitación a arruinar el propio programa."
  4. ^ Dijkstra 1968.
  5. ^ Plauger, PJ (12 de febrero de 1993). Programación con propósito, ensayos sobre diseño de software (1ª ed.). Prentice Hall. pag. 25.ISBN _ 978-0-13-721374-0.
  6. ^ DLS • Donald Knuth • Todas las preguntas respondidas. YouTube . Universidad de Waterloo. 15 de noviembre de 2018. 48 minutos en . Consultado el 24 de julio de 2022 .
  7. ^ Donald E. Knuth (diciembre de 1974). "Programación estructurada con declaraciones de acceso" (PDF) . Encuestas Informáticas . 6 (4): 261–301. doi :10.1145/356635.356640. S2CID  207630080. Archivado desde el original (PDF) el 23 de octubre de 2013.
  8. ^ En EWD1308, "Qué llevó a" Notas sobre programación estructurada""., del 10 de junio de 2001, Dijkstra escribe: "Aparentemente a IBM no le gustó la popularidad de mi texto; robó el término "Programación Estructurada" y bajo sus auspicios Harlan D. Mills trivializó el concepto original hasta abolir la declaración goto. "
  9. ^ Frank Rubin (marzo de 1987). ""GOTO Considerado perjudicial "Considerado perjudicial" (PDF) . Comunicaciones de la ACM . 30 (3): 195-196. doi :10.1145/214748.315722. S2CID  6853038. Archivado desde el original (PDF) el 20 de marzo de 2009.
  10. ^ Anciano, Matt; Jackson, Steve; Liblit, Ben (octubre de 2008). Sándwiches de código (PDF) (Informe técnico). Universidad de Wisconsin-Madison . 1647.
  11. ^ Campos de Jay; Shane Harvie; Martín Fowler; Kent Beck (2009). Refactorización: Edición Ruby . Educación Pearson. págs. 274-279. ISBN 978-0-321-60350-0.
  12. ^ Hierba Sutter; Andréi Alexandrescu (2004). Estándares de codificación de C++: 101 reglas, pautas y mejores prácticas . Educación Pearson. ISBN 978-0-13-265442-5. Ejemplo 4: Entrada única, salida única ("SESE"). Históricamente, algunos estándares de codificación han requerido que cada función tenga exactamente una salida, es decir, una declaración de retorno. Este requisito está obsoleto en lenguajes que admiten excepciones y destructores, donde las funciones suelen tener numerosas salidas implícitas.
  13. ^ Watt y Findlay 2004, págs. 215-221.
  14. ^ Bertrand Meyer (2009). Touch of Class: aprender a programar bien con objetos y contratos . Medios de ciencia y negocios de Springer. pag. 189.ISBN _ 978-3-540-92144-8.
  15. ^ "Entrada única, salida única, ¿debería seguir siendo aplicable en lenguajes orientados a objetos?". Blog de MVP de Peter Ritchie . 7 de marzo de 2008. Archivado desde el original el 14 de noviembre de 2012 . Consultado el 15 de julio de 2014 .
  16. ^ Watt y Findlay 2004, págs. 221-222.
  17. ^ Kenneth C. Louden; Kenneth A. Lambert (2011). Lenguajes de programación: principios y prácticas (3ª ed.). Aprendizaje Cengage. pag. 423.ISBN _ 978-1-111-52941-3.
  18. ^ Arvind Kumar Bansal (2013). Introducción a los Lenguajes de Programación . Prensa CRC. pag. 135.ISBN _ 978-1-4665-6514-2.
  19. ^ Weimer, W. y Necula, GC (2008). "Situaciones excepcionales y confiabilidad del programa" (PDF) . Transacciones ACM sobre lenguajes y sistemas de programación . 30 (2). 8:27. doi :10.1145/1330017.1330019. S2CID  3136431. Archivado desde el original (PDF) el 23 de septiembre de 2015.
  20. ^ Rohit Chandra (2001). Programación paralela en OpenMP . Morgan Kaufman. pag. 45.ISBN _ 978-1-55860-671-5.

Fuentes

enlaces externos