Boas práticas de programação COBOL
Boas práticas de programação existem em qualquer linguagem, e têm por objetivo aumentar a legibilidade do programa, otimizar o uso de recursos, evitar erros de codificação e agilizar as manutenções futuras.
Em resumo, boas práticas de programação aumentam a longevidade de programas e sistemas na medida em que retardam a sua entropia.
Quando pensamos especificamente em programação COBOL, alguns fatores dificultam a elaboração de um “manual genérico e universal” de melhores práticas. Ao longo das décadas, cada empresa adotou um padrão diferente, e é bastante possível que esse padrão tenha sido alterado mais de uma vez. O resultado: é comum encontrar numa mesma empresa, e às vezes num mesmo sistema, programas que foram codificados de maneiras completamente diferentes.
O melhor que um programador pode fazer, portanto, é respeitar os padrões de nomenclatura e codificação adotados pela instalação. Todos nós já vimos programas cuja manutenção era complexa e consumia mais tempo justamente porque passaram na mão de muitos programadores, cada um seguindo sua própria convenção.
No entanto, existem sim algumas práticas gerais de programação em COBOL que favorecem a mantenibilidade dos programas no futuro.
Não ignore as declarações da IDENTIFICATION DIVISION
O COBOL nasceu com a missão de ser autodocumentável, não só adotando o inglês como referência, mas também oferecendo cláusulas opcionais que fornecessem mais informação sobre o programa.
A IDENTIFICATION DIVISION é onde estão a maioria dessas cláusulas. A rigor, apenas o PROGRAM-ID é obrigatório. Mas a prática de preencher AUTHOR, INSTALLATION, DATE-WRITTEN e REMARKS não consome nem tempo nem dinheiro, e seguramente vai fornecer alguma informação útil a alguém no futuro.
Use os comentários a seu favor (e a favor do programador que vai alterar o programa um dia)
Pouca coisa é mais chata do que dar manutenção no programa dos outros. E isso acontece exatamente porque passamos mais tempo tentando entender o que o autor quis fazer do que implementando a funcionalidade que temos que implementar.
Muitas empresas “exigem” que o código seja comentado, mas como ninguém toma conta disso, é comum ver comentários tão pobres como “ARQUIVO DE ENTRADA” na FILE SECTION e “PROCESSAMENTO DE ARQUIVO” na PROCEDURE DIVISION.
Bons comentários não são aqueles que tentam explicar o que o programa está fazendo. Isso o próprio COBOL faz, já que é baseado em inglês. Bons comentários explicam por que o programa está fazendo aquilo; algo muito mais relacionado com funcionalidade e regra de negócio do que à solução de código.
A imagem abaixo mostra alguns exemplos:
Use linhas de comentários também com traços e/ou outro caracter para separar divisões e parágrafos, pois isso facilita a localização visual do programador:
Use um padrão de nomeclatura consistente (qualquer que seja)
Seguir um padrão para nomear arquivos, registros, campos, variáveis de trabalho, tabelas, copybooks e parágrafos facilita o trabalho de todos os programadores de determinada instalação ou empresa.
Padronizar prefixos e adotar nomes que tenham significados concretos faz com que qualquer programador, hoje ou amanhã, entenda o programa mais rapidamente.
Cada empresa define seu próprio padrão, e nunca encontrei duas que adotassem padrões iguais. Como comentei em um parágrafo anterior, o melhor que um programador pode fazer é seguir o padrão do programa que está modificando, que de maneira ideal (o que nem sempre acontece) deveria ser aderente a um padrão geral da instalação.
Em outras palavras, se você precisa modificar um programa onde todas as variáveis da WORKING começam com um WT-, não seja você o primeiro a criar uma variável chamada simplesmente de CONTADOR.
A tabela abaixo é só uma sugestão de nomenclatura, similar a inúmeras que vi ao longo desses anos. Não existe um “padrão certo”, e a ideia de um “padrão melhor que os outros” é obviamente subjetiva.
Item | Padrão sugerido | Comentários | Exemplos |
---|---|---|---|
Nome do programa | ssPnnnn | Onde ss é a sigla do sistema e nnnn é um número sequencial | CRP0301 |
Nome do arquivo na cláusula SELECT e na FD | ssAnnnn | Onde ss é uma sigla do sistema e nnnn é um número sequencial | CRA0201 |
Nome de registro na FD | ssAnnnn-REGISTRO | O nome do registro tem o nome do arquivo como prefixo | CRA0201-REGISTRO |
Campos de registro | ssAnnnn-xx-nome-do-campo | (1) O nome do arquivo é o prefixo do nome do campo para facilitar sua identificação na PROCEDURE; (2) xx deve ser substituído por um mnemônico que defina uma categoria para o campo, como por exemplo, nm para nome, vr para valor, dt para data etc. (3) nome-do-campo deve ser o mais concreto possível e evitar abreviações desnecessárias. Por exemplo, “pagamento” ao invés de “pg”. | CRA0201-DT-PAGAMENTO CRA0201-NM-ALUNO |
Variáveis de trabalho na WORKING | WV-xx-nome-do-campo | (1) Todas as variáveis de trabalho da WORKING, sejam itens de grupo ou itens elementares, devem ser declaradas com prefixo WV-, (2) xx e nome-do-campo seguem as regras mencionadas no item anterior. | WV-DT-PAGAMENTO WV-CT-CONTADOR |
Constantes | WC-nome-da-constante | (1) Constantes, definidas com nível 78 ou não, devem ser prefixadas com WC-, (2) nome-da-constante deve ser o mais completo possível, evitando-se abreviações, (3) Se o programa for codificado em lowercase, o nome da constante deve estar em uppercase para chamar a atenção na PROCEDURE. | WC-TAMANHO-TABELA |
Nomes de tela na SCREEN SECTION | SS-Tm | (1) SS-T é o prefixo que mostra que se trata de uma tela, (2) m é um número sequencial para criar uma identificação única para a tela. | SS-T1 |
Variáveis de tela na SCREEN SECTION | SS-T1-xx-nome-do-campo | (1) SS-Tm é o nome da tela que contém o campo, (2) xx e nome-do-campo seguem as mesmas regras das variáveis de trabalho que vimos anteriormente. | SS-T1-DT-PAGAMENTO |
Argumentos de entrada e variáveis de retorno da LINKAGE SECTION | LS-xx-nome-do-campo | (1) LS– é o prefixo que mostra que se trata de um item definido na LINKAGE SECTION, (2) xx e nome-do-campo seguem as mesmas regras das variáveis de trabalho. | LS-MT-ALUNO |
Parágrafos da PROCEDURE DIVISION | nnnn-verbo-objeto | (1) nnnn número que mostra a posição do parágrafo dentro da estrutura do programa. Esse tema será abordado com mais detalhes na próxima seção, (2) verbo e objeto devem refletir o objetivo único do parágrafo, evitando ambiguidades. | 212-CALCULAR-IMPOSTO |
Adote uma regra de numeração de parágrafos que mostre quem está chamando quem
Um dos princípios da programação estruturada estabelece que o programa deve ser organizado em blocos ou parágrafos que serão chamados de cima para baixo. Um parágrafo deve executar sua função chamando parágrafos menores e mais específicos de maneira que o programador possa abstrair determinados detalhes da implementação quando estiver analisando o programa.
O diagrama abaixo mostra um exemplo de programa estruturado em parágrafos:
O programa começa com um parágrafo principal que seria o nível zero. Esse parágrafo principal chama três outros parágrafos para executar funções de preparação do programa (nível 1), processamento principal (nível 2) e funcões de encerramento (nível 3).
O parágrafo 2-PROCESSA, por sua vez, chama dois outros parágrafos: 21-IMPRIME-DETALHE e 22-IMPRIME-TOTAL. Repare que a regra de numeração nos indica que os parágrafos 21- e 22- foram chamados por um parágrafo 2-. Assim como o parágrafo 211-CALCULA-COMISSAO nos mostra que ele foi chamado por um parágrafo cujo prefixo é 21-.
Num programa grande, essa convenção facilita a navegação do programador. Se lá no meio da PROCEDURE ele resolve analisar quem está chamando o parágrafo 223124-REGISTRA-PAGAMENTO, ele só precisa buscar por algum parágrafo cujo prefixo seja 22312-.
Procure posicionar os parágrafos dentro do programa na ordem em que eles são mencionados. O programa do diagrama anterior, por exemplo, poderia ter os parágrafos codificados na seguinte ordem:
0-PRINCIPAL. ... 1-INICIA. ... 2-PROCESSA. ... 3-TERMINA. ... 21-IMPRIME-DETALHE. ... 22-IMPRIME-TOTAL. ... 211-CALCULA-COMISSAO. ... X1-IMPRIME-CABECALHO. ...
Parágrafos chamados por vários parágrafos, como é o caso de X1-IMPRIME-CABECALHO, devem ter um prefixo próprio e estar posicionados no final do programa.
Codifique de maneira que a edentação não vire um drama
A edentação correta pode ser um desafio, principalmente se o COBOL for codificado em fixed format, que é o padrão em 99,999% dos casos. O fato de só poder trabalhar entre as colunas 12 e 72 faz com que a edentação de longos ninhos de IF e grandes PERFORMs in-line se transformem num quebra-cabeças.
Veja o exemplo abaixo:
IF SE12V-TP-MOV = '01' SEP11470
MOVE '60' TO SE12V-TP-MOV SEP11480
MOVE WT-VR-SD-DEV TO SE12V-VR-SD-ANT
MOVE WT-NR-PRES-PG TO SE12V-NR-PRES-PG
PERFORM S22-INS-SE12V THRU SS22
COMPUTE WT-VR-SD-DEV = WT-VR-SD-DEV - SE12V-VR-MOV
ELSE IF SE12V-TP-MOV = '02' SEP11470
MOVE '61' TO SE12V-TP-MOV SEP11480
MOVE WT-VR-SD-DEV TO SE12V-VR-SD-ANT
MOVE WT-NR-PRES-PG TO SE12V-NR-PRES-PG
PERFORM S22-INS-SE12V THRU SS22
COMPUTE WT-VR-SD-DEV = WT-VR-SD-DEV - SE12V-VR-MOV
ELSE IF SE12V-TP-MOV = '03' OR SE12V-TP-MOV = '04' OR SEP11470
SE12V-TP-MOV = '06' OR SE12V-TP-MOV = '05' SEP11470
MOVE '62' TO SE12V-TP-MOV SEP11480
MOVE WT-VR-SD-DEV TO SE12V-VR-SD-ANT
COMPUTE WT-NR-PRES-PG = WT-NR-PRES-PG - 1
MOVE WT-NR-PRES-PG TO SE12V-NR-PRES-PG
PERFORM S22-INS-SE12V THRU SS22
COMPUTE WT-VR-SD-DEV = WT-VR-SD-DEV + SE12V-VR-MOV
MOVE SE12V-DT-REF TO WT-DT-REF
MOVE WT-AA-REF TO WT-DA-REF-AA
MOVE WT-MM-REF TO WT-DA-REF-MM
MOVE WT-DA-REF TO SE05V-DA-REF
ELSE IF SE12V-TP-MOV = '22' SEP11470
MOVE '52' TO SE12V-TP-MOV SEP11480
MOVE WT-VR-SD-DEV TO SE12V-VR-SD-ANT
MOVE WT-NR-PRES-PG TO SE12V-NR-PRES-PG
PERFORM S22-INS-SE12V THRU SS22
COMPUTE WT-VR-SD-DEV = WT-VR-SD-DEV + SE12V-VR-MOV
ELSE IF SE12V-TP-MOV = '27' SEP11470
MOVE '74' TO SE12V-TP-MOV SEP11480
MOVE WT-VR-SD-DEV TO SE12V-VR-SD-ANT
MOVE WT-NR-PRES-PG TO SE12V-NR-PRES-PG
PERFORM S22-INS-SE12V THRU SS22
COMPUTE WT-VR-SD-DEV = WT-VR-SD-DEV + SE12V-VR-MOV
ELSE IF SE12V-TP-MOV = '52' SEP11470
MOVE '22' TO SE12V-TP-MOV SEP11480
MOVE WT-VR-SD-DEV TO SE12V-VR-SD-ANT
MOVE WT-NR-PRES-PG TO SE12V-NR-PRES-PG
PERFORM S22-INS-SE12V THRU SS22
COMPUTE WT-VR-SD-DEV = WT-VR-SD-DEV - SE12V-VR-MOV
ELSE IF SE12V-TP-MOV = '72' SEP11470
MOVE '59' TO SE12V-TP-MOV SEP11480
MOVE WT-VR-SD-DEV TO SE12V-VR-SD-ANT
MOVE WT-NR-PRES-PG TO SE12V-NR-PRES-PG
PERFORM S22-INS-SE12V THRU SS22
COMPUTE WT-VR-SD-DEV = WT-VR-SD-DEV + SE12V-VR-MOV
ELSE
IF SE12V-TP-MOV = '79'
CONTINUE
ELSE
MOVE SE01V-MT-AERUS TO AIDA-MT-AERUS
MOVE SE01V-NR-SEQ-RECEB TO AIDA-NR-SEQ-RECEB
MOVE SE01V-TP-EMP TO AIDA-TP-EMP
MOVE SE01V-NR-NIVEL TO AIDA-NR-NIVEL
MOVE SE12V-TP-MOV TO AIDA-TP-MOV
MOVE 'MOVIMENTO NAO ESTORNADO'
TO AIDA-MSG
WRITE AIDA-REGISTRO
END-IF
END-IF
END-IF
END-IF
END-IF
END-IF
END-IF
END-IF. SEP11490
Com certeza o programador edentou desse jeito porque, de outro modo, os oito níveis de IF ultrapassariam o limite da coluna 72. Mas agora alguém que precise inserir um IF ou um ELSE a mais vai ter que pensar um pouco antes de codificar. Um EVALUATE teria resolvido de maneira muito mais elegante e deixaria o código mais claro.
O importante é considerar, na hora da codificação, se não existe algum outro recurso da COBOL que possa trazer mais clareza ao programa.
Use sempre delimitadores de escopo (e evite encerrar sentenças com ponto quando não é necessário)
Se o compilador disponível seguir o padrão ANS 85, ou posterior, o uso de delimitadores deveria ser obrigatório. E isso vale não só para END-IF e END-PERFORM, mas também para muitos outros comandos onde eles estão disponíveis, como END-READ, END-EVALUATE, END-SEARCH etc.
O uso de delimitadores previne erros facilmente cometidos quando, por exemplo, se coloca (ou se deixa) um ponto fora do lugar.
Observe o exemplo abaixo:
IF SE01-DT-CONC(4:7) NOT = SE01-DT-CONC-ANT(4:7) SEP06500
MOVE SE01-VR-SD-DEV-ANT TO WT-VR-SD-DEV-ANT SEP06510
SEP06520
IF SE01-VR-LIQUIDO = 0 AND SH01-DT-DEMIS NOT = '01.01.0001' SEP06530
AND SH01-CD-PATR NOT = '98' SEP06540
AND SH01-ST-PART NOT = '2' SEP06550
AND SH01-ST-PART NOT = '4' SEP06560
MOVE 0 TO WT-VR-JUROS SEP06570
ELSE SEP06580
SEP06590
**** AMORTIZA PRESTACAO - PRESTACAO SEM JUROS **** SEP06600
SEP06610
* COMPUTE WT-AMORT-PRES = WT-VR-PRES / SEP06620
* SE01-TX-JUROS-ANT SEP06630
MOVE WT-VR-PRES TO WT-AMORT-PRES
SEP06640
**** CALCULO DOS JUROS - SD SEM PRESTACAO AMORTIZADA **** SEP06650
SEP06660
* COMPUTE WT-VR-JUROS = (SE01-VR-SD-DEV-ANT - SEP06670
* WT-AMORT-PRES) * SEP06680
* (SE01-TX-JUROS-ANT - 1) SEP06690
MOVE ZEROS TO WT-VR-JUROS SEP06670
SEP06700
COMPUTE WT-VR-SD-ANT-32 = (SE01-VR-SD-DEV-ANT + SEP06710
WT-VR-JUROS) SEP06720
SEP06730
**** MOVIMENTO EMPRESTIMO DE VALOR JUROS **** SEP06740
SEP06750
MOVE '32' TO SE12-TP-MOV SEP06760
MOVE WT-VR-SD-DEV-ANT TO SE12-VR-SD-ANT SEP06770
MOVE WT-VR-JUROS TO SE12-VR-MOV SEP06780
SEP06790
MOVE SE01-NR-PRES-ANT TO WT-NR-PRES-PG SEP06800
MOVE WT-NR-PRES-PG TO WT-NR-PREST SEP06810
MOVE SE01-NR-PRES-PG-ANT TO WT-NR-PREST-PG SEP06820
MOVE WT-NR-PREST-PG TO SE12-NR-PRES-PG SEP06830
MOVE SX07-DT-ANDA TO SE12-DT-MOV SEP06840
SE12-DT-REF SEP06850
* MOVE WT-DATA(1:4) TO SE12-DT-MOV(7:4) SEP06860
* SE12-DT-REF(7:4) SEP06870
* MOVE WT-DATA(5:2) TO SE12-DT-MOV(4:2) SEP06880
* SE12-DT-REF(4:2) SEP06890
MOVE 'SIM' TO WT-ERRO SEP06900
SEP06910
PERFORM S02-INS-SE12 THRU SS02-EXIT SEP06920
UNTIL WT-ERRO = 'NAO' SEP06930
SEP06940
ADD WT-VR-JUROS TO AC-VR-JUROS(WT-CD-PATR). SEP06950
Tudo isso faz parte de um único IF, que termina com o ponto na última linha. Além da edentação não ajudar, a chance de uma manutenção futura inserir um erro nesse código é grande.
Não deixe código morto dentro do programa (porque nem toda linha de comentário é boa)
O exemplo anterior nos leva a outra recomendação: muitos programadores têm o hábito (que alguns chamariam de seguro) de alterar um comando e deixar o original comentado. Algo como…
* COMPUTE WT-VR-JUROS = (SE01-VR-SD-DEV-ANT - WT-AMORT-PRES)
* * (SE01-TX-JUROS-ANT - 1)
MOVE ZEROS TO WT-VR-JUROS
A intenção de quem fez a alteração é clara: deixar a fórmula anterior para facilitar um possível ajuste caso a regra mude de novo. Com o tempo, porém, esse tipo de prática só cria ruído no fonte, tornando o código cada vez mais confuso.
Em alguns casos, vale mais a pena colocar um texto comentando quando, por que e quem pediu que a regra mudasse. E se olharmos para trás, não foram muitas as vezes em que esses comandos desligados nos ajudaram em alguma coisa.
Reuse (com moderação)
Copybooks e subrotinas são excelentes recursos para promover o reuso de código em COBOL. O trade-off é que seu uso excessivo prejudica a clareza do programa.
Já vi instalações onde quase tudo era copybook: SELECT ASSIGN, FD, variáveis de trabalho obrigatórias mesmo quando não eram necessárias, formatadores de cabeçalhos de telas e relatórios e tudo o mais que algum arquiteto achou que valia a pena reutilizar. Já vi programas dando CALL em subrotinas que só faziam mover ZERO para uma (uma!) variável…
O problema é que um programador recém incorporado à equipe levava o triplo do tempo para entender o programa.
O uso de copybooks e subprogramas, portanto, deve se restringir àquelas situações onde o reuso realmente traga uma vantagem no futuro. Uma rotina que faz operações com datas é uma forte candidata a virar subprograma, uma vez que esse tipo de operação é comum a quase todos os sistemas. Um copybook para definir layouts de arquivo também, já que em caso de mudança apenas uma peça precisaria de intervenção e os programs impactados só teriam que ser recompilados.
Outras práticas que podem ser úteis
Outras práticas podem ser recomendadas para melhorar tanto a clareza quanto a performance de programas.
O uso de variáveis binárias e compactadas em operações aritméticas, por exemplo, pode trazer ganhos de performance consideráveis, dependendo da complexidade do código. Na verdade, variáveis numéricas USAGE DISPLAY só devem mesmo ser usadas nos casos em que são, efetivamente, exibidas em algum lugar ou transmitidas para plataformas diferentes.
Da mesma forma, variáveis COMP-3 deveriam sempre ter tamanho ímpar (pic s9(7) comp-3 ao invés de pic s9(8) comp-3, por exemplo). Variáveis compactadas com tamanho par desperdiçam meio byte.
Nomes condicionais (itens de nível 88), se bem usados, também podem ajudar na legibilidade do programa, assim como o uso de sentenças NOT, como em NOT AT END.
Conclusão
Um padrão não é bom só porque é um padrão. É importante que cada regra e cada diretriz tenha uma justificativa clara que leve a pelo menos um dos seguintes objetivos: (1) facilitar manutenções futuras mitigando o risco de introdução de erros, ou (2) otimizar o consumo de recursos aumentando o desempenho do programa em particular e da instalação como um todo.
É importante também que a busca por um objetivo não comprometa a conquista do outro. Pouco adianta ter um programa com performance excepcional se qualquer manutenção simples vai levar semanas para ser concluída com sucesso. Da mesma forma, o programa mais claro e bonito do mundo pouco ajuda se não couber na janela de produção.
O segredo, como sempre, está no bom senso e na busca pelo caminho do meio.