Princípios da programação estruturada
A programação estruturada é uma técnica de programação, desenvolvida a partir dos anos 1960, e que tem por objetivo aumentar a qualidade e a clareza de programas, além de acelerar o tempo necessário para codificação e teste de um programa.
Esse paradigma ganhou força e foi amplamente aceito tanto pelo meio acadêmico quanto pela indústria a partir, principalmente, da publicação em 1968 do artigo Go To Statement Considered Harmful, de Edsger Dijkstra, cientista da computação holandês que é considerado o pai da programação estruturada.
Por causa desse artigo, a oposição entre programação estruturada e o uso do comando GO TO virou quase um dogma: “se tem GO TO não é estruturado”, o que obviamente é um exagero. Ou uma visão muito simplista. Algum tempo atrás publiquei um artigo falando sobre o meu ponto de vista em relação a essa controvérsia.
De acordo com o teorema da programação estruturada, todo programa pode ser construído a partir da combinação de três estruturas básicas: sequência, seleção e repetição. Neste artigo vamos revisar quais recursos estão disponíveis no COBOL para implementar cada uma delas. Muitos desses recursos só se tornaram disponíveis após a revisão COBOL-85.
Sequência
A sequência ou bloco de comandos é a estrutura mais simples das três. Trata-se, como o nome diz, apenas de uma sequência de comandos sem desvios dentro do programa.
No COBOL, qualquer sequência de comandos de atribuição, entrada/saída e operação arimética se encaixa nessa definição. Nossa única preocupação deve ser agrupá-los de acordo com sua finalidade funcional, e talvez separá-los em parágrafos específicos, quando aplicável.
Veja o exemplo abaixo:
initialize sh05v-cd-uf
exec sql
select cd_uf
into :sh05v-cd-uf
from sh05v
where mt_part = :sh02v-mt-part
and nr_seq_end = (select max(nr_seq_end)
from sh05v
where mt_part = :sh02v-mt-part)
end-exec.
move xx00v-da-arg to r2000-da-ref move sh02v-mt-part to r2000-mt-part move sh01v-nm-part to r2000-nm-part move sh01v-tp-sexo to r2000-tp-sexo move sh01v-tp-civil to r2000-tp-civil move sh01v-tp-ativo to r2000-tp-ativo move sh01v-tp-apos to r2000-tp-apos move sh01v-cd-patr to r2000-cd-patr move sh05v-cd-uf to r2000-cd-uf move sc03v-vr-sal to r2000-vr-sal move sh02v-tx-part-ant to r2000-tx-part-ant move sh02v-tx-part-atu to r2000-tx-part-atumove sh01v-dt-nasc to wt-dt-aux
compute r2000-nr-idade = wt-da-aa-arg - wt-aa-aux
move sh01v-dt-inscr to wt-dt-aux
compute r2000-nr-tmp-aerus = wt-da-aa-arg - wt-aa-aux
write r2000-relatorio
move sh02v-mt-part to r3000-mt-part move sh01v-nm-part to r3000-nm-part write r3000-relatorio
Neste exemplo, o conjunto de todos os comandos formam uma sequência, inclusive a instrução SQL que aparece no começo. O que podemos reparar é que essa sequência parece juntar blocos com finalidades diferentes. As primeiras buscam um registro na base de dados, em seguida vemos linhas que parecem montar uma linha de impressão para o relatório r2000 e finalmente alguns outros comandos para montar uma linha de impressão para um relatório chamado r3000.
Uma opção a considerar seria separar os três conjuntos em três parágrafos distintos, cada um deles com uma finalidade específica. Algo como LE-REGISTRO-SH05V, IMPRIME-RELATORIO-R2000 e IMPRIME-RELATORIO-R3000.
Naturalmente isso não é uma regra; é só um ponto a considerar. A questão que deve ser respondida para tomar esse tipo de decisão é: separar esses comandos em parágrafos distintos vai melhorar ou piorar a clareza do programa para um programador que tenha que modificá-lo daqui a 10 anos?
Seleção
Comandos de seleção são aqueles que permitem desviar o fluxo de execução de um programa para processar alguns comandos e outros não, como mostram as figuras abaixo:
Normalmente essa estrutura é formada por comandos IF/ELSE, e a partir do COBOL-85 delimitadores devem ser usados para aumentar a clareza do programa, como mostrados em amarelo no exemplo abaixo:
if sh02v-tx-part-atu not = sh02v-tx-part-ant
if sh01v-dt-transf not = "01.01.0001"
move sh01v-dt-transf(7:4) to wt-da-aa-transf
move sh01v-dt-transf(4:2) to wt-da-mm-transf
if wt-da-transf > sh02v-da-ref-ant
move "S" to r2000-fl-transf
perform s05-select-sh12v thru ss05
move sh12v-cd-repr to r2000-cd-repr
end-if end-if end-if
Outro comando do COBOL-85 que pode ser usado para seleção é o EVALUATE, representado graficamente pela figura 4.
Este comando faz a função “case”, permitindo a execução de mais de dois blocos de comandos em função dos valores de determinadas variáveis.
O comando EVALUATE permite diversas variações para uso de múlplicas variáveis e múltiplos valores para comparação. Seu formato mais simples é o que executa comandos em função dos valores possíveis de uma única variável, como no exemplo abaixo:
evaluate adirf-id-registro
when "00"
perform c211-id-registro-00 thru sc211
when "01"
perform c212-id-registro-01 thru sc212
when "3 "
perform c213-id-registro-3 thru sc213
when "20"
perform c214-id-registro-20 thru sc214
when "21"
perform c215-id-registro-21 thru sc215
when "02"
perform c216-id-registro-02 thru sc216
when "23"
perform c217-id-registro-23 thru sc217
when "44"
perform c218-id-registro-44 thru sc218
end-evaluate
Naturalmente cada opção “when” poderia ter mais de um comando, ao invés do perform que se vê no exemplo acima.
Existem outros comandos do COBOL-85 em diante igualmente válidos para seleção. Por exemplo, um comando READ com as opções AT END e NOT AT END funciona, na prática, como se houvesse um IF/THEN associado ao resultado da leitura:
READ ARQUIVO
AT END MOVE CURRENT-DATE TO WT-DT-FIM-EXECUCAO
ADD WT-CT-REGISTROS TO WT-TT-PROCESSADO
NOT AT END
ADD 1 TO WT-CT-REGISTROS
END-READ
Repetição
Estruturas de repetição são usadas para executar um determinado bloco de comandos (1) enquanto uma ou mais condições são satisfeitas (WHILE), (2) até que uma ou mais condições sejam satisfeitas (UNTIL), (3) uma determinada quantidade de vezes (TIMES) (4) ou variando uma variável até que ela atinja determinado valor (FOR).
Todas essas opções podem ser reproduzidas por variações do comando PERFORM, algumas delas alguns existentes no COBOL desde a especificação de 1960.
A semântica do COBOL contribui para algumas confusões, principalmente quando o programador está começando a trabalhar com a linguagem. Por exemplo, para construir em COBOL a estrutura while que se vê abaixo…
while A < B
C = B ** 2
A = A + 1
end-while
…teríamos que escrever…
PERFORM UNTIL A >= B
COMPUTE C = B ** 2
ADD 1 TO A
END-PERFORM
Note que utilizamos a opção UNTIL do comando PERFORM para executar um while e que o operador lógico está invertido: A < B no primeiro código, A >= B no segundo. Isso acontece porque o default do PERFORM é testar a condição que está no UNTIL antes de executar o bloco de comandos, assim como o while na maioria das linguagens.
Já numa estrutura until, o teste das condições acontece depois da primeira execução do bloco de comandos. Algo mais ou menos assim:
do
C = B ** 2
A = A + 1
until A >= B
Em COBOL, continuaríamos usando o comando PERFORM com opção UNTIL, mas com a cláusula WITH TEST AFTER:
PERFORM UNTIL A >= B WITH TEST AFTER
COMPUTE C = B ** 2
ADD 1 TO A
END-PERFORM
Outra variação muito comum do comando PERFORM é a opção VARYING, que permite incrementar uma variável até que determinada condição seja atigida.
Em Java, seria algo semelhante ao código abaixo:
Outras práticas que favorecem a estruturação de programas em COBOL
(1) Organizar o programa em parágrafos que tenham uma única finalidade, (2) usar as diversas opções do comando PERFORM para organizar a execução do programa “de cima para baixo”, (3) usar delimitadores de escopo como END-IF, END-PERFORM, END-READ etc sempre que possível.
Essas seriam as três regras essenciais da programação estruturada em COBOL. Algumas outras práticas, porém, vão contribuir significativamente para a clareza e a organização do programa.
O uso do comando SET para ativar e desativar condições definidas com nível 88 é uma delas. Além de serem mais simples, essas instruções deixam mais evidentes as intenções do programar.
Veja no exemplo abaixo:
WORKING-STORAGE SECTION. ... 01 TIPO-MOVIMENTO PIC 9(001) VALUE ZEROS. 88 CREDITO VALUE 1 THRU 3. 88 DEBITO VALUE 4 THRU 6. 88 ESTORNO VALUE 9. ... PROCEDURE DIVISION. ... SET DEBITO TO TRUE
Quando um nome condicional possui mais de um valor possível, como é o caso de CREDITO e DEBITO, o comando SET TO TRUE vai fazer com que a variável (TIPO-MOVIMENTO) receba o primeiro valor definido na cláusula VALUE.do nome condicional. No exemplo acima, SET DEBITO TO TRUE corresponde a um MOVE 4 TO TIPO-MOVIMENTO.
Se não usássemos o comando SET teríamos que usar o MOVE. O uso do SET deixou a instrução mais evidente, e produziu o mesmo efeito.
Da mesma forma, o uso de cláusulas NOT ajuda muito a manter o código estruturado e legível. O NOT está disponível para sentenças a partir do COBOL-85, tais como AT END, ON SIZE ERROR e INVALID KEY.
O exemplo abaixo mostra como ficaria um código hipotético com e sem a cláusula NOT:
Com NOT AT END:
READ ARQUIVO
AT END
SET ARQEOF TO TRUE
NOT AT END
ADD 1 TO CONTADOR
END-READ
Sem NOT AT END:
READ ARQUIVO AT END SET ARQEOF TO TRUE END-READ IF NOT ARQEOF ADD 1 TO CONTADOR END-IF
Conclusão
Algumas linguagens de programação, principalmente aquelas que são estritamente orientadas a objeto, exigem uma estrutura mais rígida de codificação que minimiza a desorganização do programa ao longo do tempo.
O COBOL, no entanto, foi projetado para que o programa fosse um “texto” escrito em inglês, com seções, parágrafos e sentenças. E textos podem ser claros e concisos ou verborrágicos e desorganizados.
A prática da programação estruturada não só reduz o esforço necessário para manutenções futuras, mas também aumenta a longevidade do programa na medida em que reduz aquele nosso velho impulso de “jogar fora e fazer de novo”.
E quem nunca…