Boas práticas de programação COBOL

Photo by Anton on Unsplash

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.

Figura 1. IDENTIFICATION DIVISION padrão.

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:

Figura 2. Comentários explicam por quê.

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:

Figura 3. Comentários separando os parágrafos.

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.

ItemPadrão sugeridoComentáriosExemplos
Nome do programassPnnnnOnde ss é a sigla do sistema e nnnn é um número sequencialCRP0301
Nome do arquivo na cláusula SELECT e na FDssAnnnnOnde ss é uma sigla do sistema e nnnn é um número sequencialCRA0201
Nome de registro na FDssAnnnn-REGISTROO nome do registro tem o nome do arquivo como prefixoCRA0201-REGISTRO
Campos de registrossAnnnn-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 WORKINGWV-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
ConstantesWC-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 SECTIONSS-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 SECTIONSS-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 SECTIONLS-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 DIVISIONnnnn-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:

Cobol: Diagrama estruturado
Figura 4. Exemplo de diagrama estruturado

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.


Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *