A programa¸c˜ao procedimental parte da ideia base de se expressarem as solu¸c˜oes para problemas como sequˆencias de ac¸c˜oes (comandos) a serem executadas. Num programa correcto, `a medida que as ac¸c˜oes v˜ao sendo executadas, o estado do sistema tende para a solu¸c˜ao do problema (essa solu¸c˜ao pode estar explicitamente expressa em vari´aveis do programa, ou implicitamente registada no caminho de execu¸c˜ao de comandos que o programa percorre).
Com este m´etodo, o problema de programa¸c˜ao “reduz-se” – para al´em de uma especifica¸c˜ao adequada (e suficiente) de vari´aveis para armazenamento expl´ıcito de informa¸c˜ao do programa – `a decomposi¸c˜ao de “cima-para-baixo” do algoritmo do pro- cedimento inicial, numa sequˆencia de ac¸c˜oes mais simples5 – podendo elas pr´oprias
serem novos procedimentos, pass´ıveis de uma nova decomposi¸c˜ao – envolvendo quando necess´ario instru¸c˜oes de atribui¸c˜ao de valor a vari´aveis, instru¸c˜oes condicionais6 e ins-
tru¸c˜oes repetitivas7. Este processo de decomposi¸c˜ao aplica-se hierarquicamente a cada
ac¸c˜ao resultante da decomposi¸c˜ao anterior, at´e que o algoritmo resultante esteja com- pletamente expresso em fun¸c˜ao de ac¸c˜oes pr´e-existentes [Wirth 71, Wirth 74].
3Esta caracter´ıstica ´e importante na escolha e compara¸c˜ao entre diferentes aproxima¸c˜oes ao polimorfismo
subtipo como se ver´a `a frente (p´agina 23)
4Veremos (p´agina 24) que a linguagem Eiffel tem um sistema de tipos que permite, embora de uma forma
limitada, que a semˆantica dos tipos fa¸ca parte destes.
5Decomposi¸c˜ao por “concatena¸c˜ao” segundo Dijkstra [Dijkstra 72, p´agina 19]. 6Decomposi¸c˜ao por “selec¸c˜ao” segundo Dijkstra [Dijkstra 72, p´agina 19].
if CONDITION then COMMANDS end while CONDITION do COMMANDS end repeat COMMANDS until CONDITION
Figura 3.1: Instru¸c˜oes condicionais e repetitivas estruturadas.
Uma caracter´ıstica importante desta aproxima¸c˜ao – ali´as partilhada pela programa- ¸c˜ao orientada por objectos – ´e a sua natureza imperativa. A express˜ao de um algoritmo ´e feita por uma sequˆencia de comandos que podem modificar explicitamente o estado do sistema (ou seja a execu¸c˜ao de comandos pode ter efeitos colaterais no programa como resultado da modifica¸c˜ao do valor das vari´aveis).
Outro aspecto essencial desta aproxima¸c˜ao ´e a utiliza¸c˜ao da chamada abstrac¸c˜ao algor´ıtmica. Este tipo de abstrac¸c˜ao consiste no encapsulamento de algoritmos dentro de procedimentos (ac¸c˜oes) ou de fun¸c˜oes (c´alculo de valores)8, separando dessa forma
a utiliza¸c˜ao – geralmente simples e facilmente compreens´ıvel – da implementa¸c˜ao desse algoritmo. Assim, a reutiliza¸c˜ao de algoritmos e a compreensibilidade dos programas pode ser substancialmente melhorada.
A compreens˜ao de programas ser´a tanto mais facilitada quanto maior for a proxi- midade entre a sua estrutura est´atica e o seu comportamento dinˆamico (ou seja: em tempo de execu¸c˜ao) [Dijkstra 68c]. Uma aproxima¸c˜ao nesse sentido ser´a fazer com que as instru¸c˜oes das linguagens tenham apenas um ponto de entrada e um ponto de sa´ıda [Dijkstra 72, p´aginas 16–23] [Wirth 74]. Dessa forma elas podem facilmente ser isoladas e interpretadas como sendo uma ´unica ac¸c˜ao numa computa¸c˜ao sequen- cial. Esta propriedade da programa¸c˜ao procedimental estruturada ´e muito importante j´a que facilita a an´alise e compreens˜ao de algoritmos de “cima-para-baixo”. Assim, as propriedades (que podem ser expressas por axiomas sobre o estado do programa) de cada instru¸c˜ao s˜ao definidas de “fora-para-dentro”, e n˜ao o inverso. ´E o caso das instru¸c˜oes condicionais e repetitivas estruturadas, cujo comportamento ´e imposto pela estrutura externamente vis´ıvel das pr´oprias instru¸c˜oes (figura 3.1).
Os comandos COMMANDS – quaisquer que eles sejam – s´o ser˜ao executados caso sejam seleccionados pelas instru¸c˜oes condicionais ou repetitivas onde est˜ao inseridos. Iremos designar as instru¸c˜oes de linguagens que cumpram esta propriedade por instru¸c˜oes estruturadas puras.
Esta propriedade facilita a associa¸c˜ao a qualquer ac¸c˜ao sequencial A de duas as- ser¸c˜oes9 – P e R – atestando a sua correc¸c˜ao [Hoare 69]:
{P } A {R}
Esta f´ormula, conhecida por terno de Hoare, pode ser expressa da seguinte forma: se a pr´e-condi¸c˜ao P se verificar no in´ıcio da execu¸c˜ao da ac¸c˜ao A, ent˜ao a p´os-condi¸c˜ao R ser´a verdadeira no seu fim10
8H´a linguagens, como por exemplo o C que n˜ao distinguem de uma forma sint´acticamente expl´ıcita proce-
dimentos de fun¸c˜oes, embora – mesmo nesse caso – se possa considerar que fun¸c˜oes do tipo void correspondem a procedimentos.
9Predicados.
i = 1; // (1)
l1: printf("%d\n",i); // (2)
i++; // (3)
if (i <= 10) // (4)
goto l1; // (5)
Figura 3.2: Exemplo de um algoritmo com “saltos” em C.
Esta aproxima¸c˜ao axiom´atica `a correc¸c˜ao de programas – devida principalmente a Floyd [Floyd 67] e Hoare [Hoare 69] – ser´a uma das contribui¸c˜oes mais importan- tes da programa¸c˜ao procedimental estruturada (tendo sido adaptada e extendida na programa¸c˜ao orientada por objectos, com a programa¸c˜ao por contrato).
Uma consequˆencia quase imediata desta aproxima¸c˜ao `a constru¸c˜ao de algoritmos ´e a inadequa¸c˜ao da utiliza¸c˜ao de instru¸c˜oes de “saltos”11. Em geral, a utiliza¸c˜ao de
“saltos” torna mais dif´ıcil relacionar o comportamento dinˆamico de um programa com a sua estrutura textual est´atica. Essa instru¸c˜ao pode esconder estruturas algor´ıtmicas essenciais como as estruturas repetitivas ou as condicionais muito longe da sua real ocorrˆencia, o que pode tornar o algoritmo de muito dif´ıcil compreens˜ao12 (por essa
raz˜ao ´e usual designar a utiliza¸c˜ao de “saltos” em programas como c´odigo tipo “espar- guete”). Ou seja, a constru¸c˜ao de algoritmos com “saltos”, ao contr´ario das instru¸c˜oes estruturadas puras, pode obrigar `a compreens˜ao do algoritmo de “dentro-para-fora”.
A figura 3.2 exemplifica a implementa¸c˜ao de um algoritmo repetitivo utilizando uma instru¸c˜ao de “saltos”. Assim s´o em (5) ´e que o programador se pode aperceber de que est´a perante um algoritmo repetitivo iniciado em (2). Muito embora se possam utilizar disciplinadamente as instru¸c˜oes de “saltos” (sendo Knuth o grande defensor dessa utiliza¸c˜ao regrada [Knuth 74]), tal op¸c˜ao faz com que deixe de haver a garantia em tempo de compila¸c˜ao de que a estrutura algor´ıtmica ´e simples, perdendo-se a garantia do uso exclusivo de instru¸c˜oes estruturadas puras.
3.2.1 Limita¸c˜oes
A programa¸c˜ao procedimental estruturada come¸ca a mostrar as suas limita¸c˜oes `a medida que a complexidade do problema a resolver vai aumentando. Com efeito para problemas com alguma complexidade n˜ao far´a muito sentido atribuir importˆancia a um ´unico procedimento de topo. Facilmente se podem definir v´arios procedimentos de topo – provavelmente com decomposi¸c˜oes de “cima-para-baixo” bastante diferentes – para o mesmo problema a resolver, podendo estes depender, por exemplo, do tipo de interac¸c˜ao entre o utilizador e o programa (interface gr´afica, consola de texto, etc.). Fazer depender a decomposi¸c˜ao algor´ıtmica dessa escolha conjuntural ´e claramente um
Estes dois formalismos diferem apenas do detalhe de na nota¸c˜ao original de Hoare a p´os-condi¸c˜ao s´o ser aplic´avel caso a ac¸c˜ao termine (correc¸c˜ao parcial) enquanto que a nota¸c˜ao utilizada pressup˜oe e imp˜oe a termina¸c˜ao (em tempo finito) da ac¸c˜ao [Gries 81, p´agina 109]. Para os objectivos deste trabalho, no entanto, essa diferen¸ca n˜ao nos parece ser de todo relevante.
11goto.
12Como em todas as regras, h´a no entanto algumas excep¸c˜oes. Em linguagens sem mecanismos de excep¸c˜oes,
o uso de “saltos” pode ser justificado para lidar com situa¸c˜oes excepcionais por forma a n˜ao “poluir” o c´odigo normal e a simplificar programas.
erro e uma sobre-especifica¸c˜ao.
Outro problema mais cr´ıtico assenta no facto desta aproxima¸c˜ao ter uma modula- ridade fraca. Em geral, os procedimentos e fun¸c˜oes n˜ao s˜ao auto-suficientes, tendo a necessidade de estar associados a estruturas de dados apropriadas. Por exemplo, uma fun¸c˜ao que indique se uma qualquer data (definida por dia, mˆes e ano) ´e v´alida, est´a intimamente ligada `a estrutura de dados que representa datas (que poder´a ser composta por trˆes valores inteiros, por uma estrutura com trˆes campos inteiros, ou uma outra representa¸c˜ao qualquer). Uma qualquer modifica¸c˜ao da estrutura de dados implica com grande probabilidade a modifica¸c˜ao dos procedimentos e fun¸c˜oes que dela dependem.
Dos cinco crit´erios de modularidade apresentados (p´agina 7), trˆes s˜ao directamente colocados em causa com esta aproxima¸c˜ao:
• Composi¸c˜ao modular: cada m´odulo ter´a de estar ligado aos tipos de dados que utiliza (os quais, por sua vez, podem ter uma coes˜ao grande com outros m´odulos). • Compreens˜ao modular: a compreens˜ao de cada m´odulo passa tamb´em pela com- preens˜ao dos tipos de dados a ele associados, quando n˜ao passa tamb´em pela compreens˜ao de outras fun¸c˜oes (m´odulos).
• Protec¸c˜ao modular, h´a uma coes˜ao grande com tipos de dados externos ao m´odulo. Estas deficiˆencias de modularidade na metodologia da programa¸c˜ao procedimental estruturada, s˜ao abordadas e resolvidas na programa¸c˜ao orientada por objectos.