Translate

quinta-feira, 21 de julho de 2022

🚨 Alerta Vermelho na Enterprise: Descobrimos Que Não Era o COBOL 5... Era o 6.4!

Bellacosa Mainframe e atencao ao compilar uma nova função intrinseca


🚨 Alerta Vermelho na Enterprise: Descobrimos Que Não Era o COBOL 5... Era o 6.4!

Adendo — Em qual versão do Enterprise COBOL as funções definidas pelo usuário estão disponíveis?

Antes de avançarmos para a sala de máquinas, precisamos corrigir uma informação importante do artigo original:

As User-Defined Functions com FUNCTION-ID não foram introduzidas no Enterprise COBOL 5. Elas chegaram oficialmente ao IBM Enterprise COBOL for z/OS 6.4.

O suporte faz parte da implementação IBM de recursos definidos pelo padrão COBOL 2002. No Enterprise COBOL 6.4, o programador passou a poder criar funções próprias, declarar parâmetros, retornar um valor e invocá-las por meio de um identificador de função. (IBM)

🖖 Adendo do Capitão Kirk

Enterprise COBOL 6.4, compilação e JCL para User-Defined Functions

Intrínseca ou definida pelo usuário?

Existe uma diferença importante de terminologia.

Uma função como:

FUNCTION CURRENT-DATE
FUNCTION LENGTH
FUNCTION UPPER-CASE
FUNCTION NUMVAL

é uma função intrínseca, pois já vem implementada no compilador COBOL.

Uma função criada com:

FUNCTION-ID. CALCULA-DESCONTO.

é uma User-Defined Function, ou função definida pelo usuário.

Ela pode ser usada de maneira parecida com uma função intrínseca, mas não passa a fazer parte do compilador. Ela continua sendo um componente da aplicação que precisa ser compilado e disponibilizado corretamente.

Portanto, tecnicamente, não estamos adicionando uma nova função intrínseca ao compilador IBM. Estamos criando uma função de aplicação que pode ser invocada com uma sintaxe semelhante.


A partir de qual versão está disponível?

No ambiente IBM Z, o suporte a User-Defined Functions foi introduzido no:

IBM Enterprise COBOL for z/OS 6.4

O recurso utiliza o parágrafo:

FUNCTION-ID

e o delimitador:

END FUNCTION

A função também deve possuir obrigatoriamente uma cláusula RETURNING no cabeçalho da PROCEDURE DIVISION. (IBM)

Resumo de compatibilidade

Versão do Enterprise COBOLUser-Defined Function com FUNCTION-ID
Enterprise COBOL 4.xNão
Enterprise COBOL 5.1Não
Enterprise COBOL 5.2Não
Enterprise COBOL 6.1Não
Enterprise COBOL 6.2Não
Enterprise COBOL 6.3Não
Enterprise COBOL 6.4Sim

Em alguns ambientes, o Enterprise COBOL 6.4 pode precisar estar com os PTFs recomendados pela IBM instalados para que correções e recursos complementares, como protótipos de função, estejam disponíveis.

O suporte básico a User-Defined Functions pertence ao 6.4. Já o suporte ampliado a function prototypes, relacionado ao padrão COBOL 2014, foi fornecido por manutenção do compilador 6.4, incluindo o APAR/PTF associado ao suporte documentado pela IBM.


Programa completo para treinamento

Neste exemplo, criaremos a função:

CALCULA-IMPOSTO

Ela receberá:

  • valor da operação;

  • alíquota percentual;

E retornará:

  • valor do imposto.

A função e o programa principal serão colocados no mesmo membro-fonte para facilitar o laboratório.


Fonte COBOL completo

       IDENTIFICATION DIVISION.
       FUNCTION-ID. CALCULA-IMPOSTO.

       DATA DIVISION.
       LINKAGE SECTION.

       01  LK-VALOR             PIC S9(09)V99 COMP-3.
       01  LK-ALIQUOTA          PIC S9(03)V99 COMP-3.
       01  LK-IMPOSTO           PIC S9(09)V99 COMP-3.

       PROCEDURE DIVISION
           USING BY REFERENCE LK-VALOR
                              LK-ALIQUOTA
           RETURNING LK-IMPOSTO.

           COMPUTE LK-IMPOSTO ROUNDED =
               LK-VALOR * LK-ALIQUOTA / 100

           GOBACK.

       END FUNCTION CALCULA-IMPOSTO.


       IDENTIFICATION DIVISION.
       PROGRAM-ID. MAINPROG.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.

       REPOSITORY.
           FUNCTION CALCULA-IMPOSTO.

       DATA DIVISION.
       WORKING-STORAGE SECTION.

       01  WS-VALOR             PIC S9(09)V99 COMP-3
                                VALUE 1000.
       01  WS-ALIQUOTA          PIC S9(03)V99 COMP-3
                                VALUE 18.50.
       01  WS-IMPOSTO           PIC S9(09)V99 COMP-3.

       01  WS-VALOR-EDITADO     PIC ZZZ,ZZZ,ZZ9.99-.
       01  WS-ALIQ-EDITADA      PIC ZZ9.99-.
       01  WS-IMPOSTO-EDITADO   PIC ZZZ,ZZZ,ZZ9.99-.

       PROCEDURE DIVISION.

           DISPLAY "========================================"
           DISPLAY " BELLACOSA MAINFRAME - TESTE DE FUNCAO"
           DISPLAY "========================================"

           COMPUTE WS-IMPOSTO =
               CALCULA-IMPOSTO(
                   WS-VALOR
                   WS-ALIQUOTA
               )

           MOVE WS-VALOR    TO WS-VALOR-EDITADO
           MOVE WS-ALIQUOTA TO WS-ALIQ-EDITADA
           MOVE WS-IMPOSTO  TO WS-IMPOSTO-EDITADO

           DISPLAY "VALOR DA OPERACAO : " WS-VALOR-EDITADO
           DISPLAY "ALIQUOTA           : " WS-ALIQ-EDITADA "%"
           DISPLAY "IMPOSTO CALCULADO  : " WS-IMPOSTO-EDITADO

           IF WS-IMPOSTO = 185
               DISPLAY "RESULTADO CORRETO. MISSAO CUMPRIDA."
           ELSE
               DISPLAY "RESULTADO INESPERADO. CHAME O SPOCK."
           END-IF

           GOBACK.

       END PROGRAM MAINPROG.

Por que a função aparece antes do programa?

Quando a função e o programa chamador são compilados no mesmo grupo de compilação, sem o uso de um protótipo separado, a definição da função deve aparecer antes da unidade que a utiliza.

A IBM recomenda, para projetos maiores, que a função seja mantida em arquivo separado e que um protótipo seja usado no programa chamador. Entretanto, para treinamento, colocar a função e o programa no mesmo membro é uma maneira direta de compreender o mecanismo. (IBM)

Há uma consequência importante:

A primeira unidade encontrada pelo compilador pode tornar-se o ponto de entrada padrão do módulo.

Como nossa primeira unidade é CALCULA-IMPOSTO, precisamos informar ao Binder que o verdadeiro programa inicial é:

MAINPROG

Por isso usaremos:

ENTRY MAINPROG

durante a linkedição.

Sem essa instrução, a nave pode tentar decolar pela porta da engenharia em vez de sair pela ponte de comando.


JCL completo: compilar, linkeditar e executar

O exemplo abaixo apresenta um fluxo tradicional com três etapas:

  1. COBOL — compilação;

  2. LKED — linkedição;

  3. RUN — execução.

Os nomes das bibliotecas precisam ser adaptados à instalação da sua empresa.

//BLCOBUDF JOB (ACCT),'COBOL UDF',
//             CLASS=A,
//             MSGCLASS=X,
//             MSGLEVEL=(1,1),
//             NOTIFY=&SYSUID
//*
//* ==========================================================
//* BELLACOSA MAINFRAME
//* COMPILACAO DE USER-DEFINED FUNCTION - COBOL 6.4
//* ==========================================================
//*
//COBOL    EXEC PGM=IGYCRCTL,
// PARM='LIB,OBJECT,RENT,APOST,LIST,MAP,XREF,OFFSET'
//STEPLIB  DD DISP=SHR,
//            DSN=IGY.V6R4M0.SIGYCOMP
//SYSIN    DD DISP=SHR,
//            DSN=SEU.USUARIO.COBOL(CALCUDF)
//SYSLIN   DD DSN=&&OBJETO,
//            DISP=(NEW,PASS),
//            UNIT=SYSDA,
//            SPACE=(TRK,(5,5)),
//            DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)
//SYSPRINT DD SYSOUT=*
//SYSOUT   DD SYSOUT=*
//SYSUT1   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT2   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT3   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT4   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT5   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT6   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT7   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT8   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT9   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT10  DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT11  DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT12  DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT13  DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT14  DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT15  DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//*
//LKED     EXEC PGM=IEWL,
// PARM='LIST,MAP,XREF,LET,RENT'
//SYSLIB   DD DISP=SHR,
//            DSN=CEE.SCEELKED
//         DD DISP=SHR,
//            DSN=CEE.SCEELKEX
//SYSLMOD  DD DISP=SHR,
//            DSN=SEU.USUARIO.LOAD(MAINPROG)
//SYSLIN   DD DISP=(OLD,DELETE),
//            DSN=&&OBJETO
//         DD *
  ENTRY MAINPROG
  NAME MAINPROG(R)
/*
//SYSPRINT DD SYSOUT=*
//SYSUT1   DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//*
//RUN      EXEC PGM=MAINPROG,
//            COND=(0,NE)
//STEPLIB  DD DISP=SHR,
//            DSN=SEU.USUARIO.LOAD
//         DD DISP=SHR,
//            DSN=CEE.SCEERUN
//         DD DISP=SHR,
//            DSN=CEE.SCEERUN2
//SYSOUT   DD SYSOUT=*
//CEEDUMP  DD SYSOUT=*
//SYSUDUMP DD SYSOUT=*
//

Bibliotecas que devem ser adaptadas

Biblioteca do compilador

No exemplo:

DSN=IGY.V6R4M0.SIGYCOMP

Esse nome varia de instalação para instalação.

Você poderá encontrar algo parecido com:

IGY.V6R4M0.SIGYCOMP
IGY640.SIGYCOMP
SYS1.IGY640.SIGYCOMP
CBC.SIGYCOMP

Não copie cegamente o nome do exemplo.

Pergunte ao sysprog, consulte um JCL COBOL já utilizado na empresa ou verifique a PROC oficial de compilação.


Biblioteca de runtime do Language Environment

Normalmente são utilizadas:

CEE.SCEERUN
CEE.SCEERUN2

Para a linkedição, podem aparecer:

CEE.SCEELKED
CEE.SCEELKEX

Os nomes e concatenações dependem da configuração do z/OS e do Language Environment.


Biblioteca de load modules

No exemplo:

DSN=SEU.USUARIO.LOAD

Ela precisa existir como uma PDS ou PDSE adequada para módulos executáveis.

Uma definição típica poderia ser criada por meio do ISPF 3.2 ou por JCL, usando atributos compatíveis com a instalação.

Em ambientes modernos, normalmente é preferível utilizar uma PDSE para a biblioteca de carga.


Versão mais curta usando uma PROC catalogada

Muitos ambientes IBM Z possuem procedures catalogadas como:

IGYWCL
IGYWCLG
IGYWC

Os nomes representam, em geral:

  • C: compile;

  • L: link-edit;

  • G: go ou execute.

Um exemplo conceitual seria:

//BLCOBUDF JOB (ACCT),'COBOL UDF',
//             CLASS=A,
//             MSGCLASS=X,
//             NOTIFY=&SYSUID
//*
//COBCLG   EXEC IGYWCLG,
//         PARM.COBOL='LIB,RENT,APOST,LIST,MAP,XREF'
//COBOL.SYSIN DD DISP=SHR,
//         DSN=SEU.USUARIO.COBOL(CALCUDF)
//LKED.SYSLMOD DD DISP=SHR,
//         DSN=SEU.USUARIO.LOAD(MAINPROG)
//LKED.SYSIN DD *
  ENTRY MAINPROG
  NAME MAINPROG(R)
/*
//GO.STEPLIB DD DISP=SHR,
//         DSN=SEU.USUARIO.LOAD
//GO.SYSOUT DD SYSOUT=*
//GO.CEEDUMP DD SYSOUT=*

Entretanto, há um alerta vermelho piscando na ponte:

As procedures catalogadas não são idênticas em todas as empresas.

A PROC pode:

  • possuir outro nome;

  • utilizar outros qualificadores;

  • não aceitar os mesmos overrides;

  • já incluir bibliotecas adicionais;

  • ter parâmetros obrigatórios;

  • possuir nomes internos diferentes para as etapas.

Antes de utilizá-la, execute no ISPF:

TSO ISRDDN

ou consulte os JCLs-padrão da instalação.

Também é possível procurar a PROC nas bibliotecas de procedures, normalmente concatenadas em JES2 PROCLIB.


Entendendo o REPOSITORY

O programa chamador possui:

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.

       REPOSITORY.
           FUNCTION CALCULA-IMPOSTO.

Isso informa ao compilador que CALCULA-IMPOSTO é uma função conhecida pelo programa.

Com essa declaração, podemos escrever:

COMPUTE WS-IMPOSTO =
    CALCULA-IMPOSTO(WS-VALOR WS-ALIQUOTA)

A IBM documenta que a declaração no REPOSITORY permite invocar a função sem repetir a palavra reservada FUNCTION. (IBM)

Dependendo da forma de declaração e do contexto, uma referência explícita também pode assumir a forma:

FUNCTION CALCULA-IMPOSTO(
    WS-VALOR
    WS-ALIQUOTA
)

No exemplo apresentado, utilizamos o REPOSITORY para deixar a expressão mais limpa.


Saída esperada no SYSOUT

O resultado deverá ser semelhante a:

========================================
 BELLACOSA MAINFRAME - TESTE DE FUNCAO
========================================
VALOR DA OPERACAO :       1,000.00
ALIQUOTA           :          18.50%
IMPOSTO CALCULADO  :         185.00
RESULTADO CORRETO. MISSAO CUMPRIDA.

Return codes esperados

Em uma missão bem-sucedida, procure:

COBOL RC=0000
LKED  RC=0000
RUN   RC=0000

Dependendo das opções e dos avisos encontrados, a compilação pode terminar com:

RC=0004

Isso significa que houve advertências, não necessariamente um erro fatal. Mesmo assim, analise todas as mensagens.

Interpretação tradicional

Return codeSignificado geral
0000Processamento concluído sem diagnóstico relevante
0004Advertências
0008Erros que normalmente impedem o uso seguro
0012Erros graves
0016Erro muito grave ou falha de processamento

A interpretação exata deve considerar as mensagens emitidas pelo compilador e pelo Binder.


Erro: FUNCTION-ID não reconhecido

Caso o compilador apresente mensagens indicando que:

FUNCTION-ID

não é reconhecido, as causas mais prováveis são:

  1. O JCL está chamando um compilador anterior ao Enterprise COBOL 6.4.

  2. A STEPLIB aponta para outra versão do compilador.

  3. A PROC catalogada ainda referencia COBOL 6.2 ou 6.3.

  4. O produto 6.4 está instalado, mas não é a versão selecionada pelo JCL.

  5. O fonte está sendo processado por uma ferramenta intermediária incompatível.

  6. O editor ou analisador local conhece uma gramática COBOL antiga.

Verifique no listing de compilação a identificação da versão.

Você deverá encontrar uma indicação semelhante a:

IBM Enterprise COBOL for z/OS 6.4

Não confie apenas no nome da PROC.

Uma PROC chamada COBOL64 pode ter sido alterada, enquanto uma PROC chamada apenas COBOL pode estar usando o compilador mais recente.

Como Spock diria:

“O nome de um membro não constitui evidência lógica do conteúdo de sua STEPLIB.”


Erro de linkedição: ponto de entrada incorreto

Como a função aparece primeiro no grupo de compilação, o Binder pode selecionar a entrada errada caso não seja informado explicitamente.

Por isso usamos:

ENTRY MAINPROG

Para compilações AMODE 31 com LP(32), a IBM orienta que seja fornecida uma instrução ENTRY para o programa principal quando uma compilação contém funções e programas e a função aparece antes do programa. (IBM)

Se o ENTRY estiver ausente ou incorreto, poderão ocorrer:

  • início da execução no componente errado;

  • erro de entrada não encontrada;

  • comportamento imprevisível;

  • falha de linkedição;

  • abend durante a inicialização.


Não use CALL para invocar a função

Uma User-Defined Function deve ser utilizada como função:

COMPUTE WS-IMPOSTO =
    CALCULA-IMPOSTO(WS-VALOR WS-ALIQUOTA)

Não faça:

CALL "CALCULA-IMPOSTO"

A documentação IBM alerta que invocar uma User-Defined Function por meio da instrução CALL produz comportamento imprevisível. (IBM)

Se o componente foi projetado para ser chamado com CALL, escreva-o como subprograma com:

PROGRAM-ID

Se foi projetado como função, escreva-o com:

FUNCTION-ID

Não misture as duas interfaces.


ENTRY-INTERFACE

O FUNCTION-ID também pode controlar a forma de geração e invocação por meio de ENTRY-INTERFACE.

As User-Defined Functions podem ser estruturadas para diferentes formas de ligação, incluindo:

  • estática;

  • dinâmica;

  • DLL.

O padrão documentado para ENTRY-INTERFACE é STATIC. (IBM)

Para o primeiro laboratório, mantenha a configuração padrão.

A invocação estática reduz a quantidade de peças móveis e facilita:

  • compilação;

  • linkedição;

  • testes;

  • diagnóstico;

  • implantação inicial.

Só avance para modelos dinâmicos depois de dominar:

  • protótipos;

  • external names;

  • binder;

  • load libraries;

  • Language Environment;

  • convenções de interface.


Cuidado com BY REFERENCE

No exemplo, os parâmetros são recebidos com:

USING BY REFERENCE

Isso significa que a função recebe referências aos itens do programa chamador.

Embora uma boa função de cálculo não deva modificar seus argumentos, o uso de referência exige disciplina.

Evite comandos como:

MOVE ZERO TO LK-VALOR

Isso poderia alterar dados pertencentes ao chamador, dependendo da interface e do argumento utilizado.

Para obter um comportamento semelhante ao BY CONTENT, o Enterprise COBOL 6.4 disponibiliza a função CONTENT-OF. Nesse modelo, a definição formal continua usando BY REFERENCE, mas o argumento é protegido por uma cópia temporária criada na invocação. (IBM)

Exemplo conceitual:

REPOSITORY.
    FUNCTION CONTENT-OF INTRINSIC
    FUNCTION CALCULA-IMPOSTO.

Invocação:

COMPUTE WS-IMPOSTO =
    CALCULA-IMPOSTO(
        CONTENT-OF(WS-VALOR)
        CONTENT-OF(WS-ALIQUOTA)
    )

Isso é especialmente útil quando você deseja deixar claro que a função não deve alterar os argumentos fornecidos.


Dicas de compilação do Scotty

1. Comece sem otimização agressiva

Durante os primeiros testes, prefira opções que facilitem diagnóstico.

Por exemplo:

LIST,MAP,XREF,OFFSET

Depois que o programa estiver estabilizado, avalie as opções de otimização adotadas pela instalação.


2. Use TEST em ambiente de desenvolvimento

Caso a empresa utilize IBM Debug for z/OS, as opções apropriadas de depuração podem ajudar a acompanhar:

  • entrada na função;

  • conteúdo dos parâmetros;

  • valor retornado;

  • fluxo de execução.

As opções exatas dependem das ferramentas e padrões locais.


3. Verifique o listing

No listing de compilação, confirme:

  • versão do compilador;

  • opções efetivamente utilizadas;

  • definição da função;

  • referências à função;

  • mensagens de severidade;

  • offsets;

  • informações do objeto gerado.


4. Não esconda erros com COND inadequado

No laboratório, a execução deve ocorrer apenas quando as etapas anteriores forem bem-sucedidas.

Uma alternativa moderna é usar:

//RUN EXEC PGM=MAINPROG,
// IF (COBOL.RC LE 4 AND LKED.RC LE 4) THEN

ou estruturas IF/THEN/ELSE/ENDIF do JCL, conforme o padrão da empresa.

Evite executar um módulo quando a compilação terminou com erro grave.


5. Teste limites numéricos

Não teste apenas:

1000 × 18,5%

Teste também:

  • valor zero;

  • alíquota zero;

  • valor negativo, se permitido;

  • alíquota com muitas casas;

  • valor máximo do PIC;

  • resultado próximo de overflow;

  • arredondamento;

  • sinal;

  • casas decimais;

  • truncamento.


Checklist de lançamento da função

Antes de Kirk autorizar a partida, confirme:

[ ] Enterprise COBOL 6.4 está realmente sendo utilizado
[ ] FUNCTION-ID foi reconhecido
[ ] A função possui RETURNING
[ ] A função termina com END FUNCTION
[ ] O programa termina com END PROGRAM
[ ] A função aparece antes do chamador no mesmo grupo
[ ] O REPOSITORY declara a função
[ ] ENTRY MAINPROG foi fornecido ao Binder
[ ] A load library existe
[ ] CEE.SCEERUN está disponível na execução
[ ] Os parâmetros possuem formatos compatíveis
[ ] O valor de retorno cabe no campo receptor
[ ] Não existe CALL para a User-Defined Function
[ ] O listing foi revisado
[ ] Os testes de limite foram executados

Diário de bordo do Capitão Kirk

Criar uma função definida pelo usuário no Enterprise COBOL 6.4 não é apenas aprender uma nova sintaxe.

É aprender que o COBOL também evolui.

A linguagem que processava cartões perfurados agora pode organizar regras em funções reutilizáveis, oferecer interfaces mais expressivas e aproximar sistemas críticos de práticas modernas de engenharia de software.

Mas o Padawan precisa compreender toda a cadeia:

FONTE
  ↓
COMPILADOR COBOL 6.4
  ↓
OBJETO
  ↓
BINDER
  ↓
LOAD MODULE
  ↓
LANGUAGE ENVIRONMENT
  ↓
EXECUÇÃO

A função não “se instala” como um plugin.

Ela é:

  1. escrita;

  2. compilada;

  3. linkedita;

  4. armazenada em uma load library;

  5. disponibilizada ao programa que a utilizará.

Na ponte da Enterprise, Kirk conclui:

“Não basta descobrir um novo recurso. É preciso saber como colocá-lo em produção sem explodir os motores.”

Spock examina o listing e responde:

“Compilação RC zero, linkedição RC zero e resultado igual a 185. A missão é logicamente satisfatória.”

E Scotty, olhando para o Binder, acrescenta:

“Desde que ninguém esqueça o ENTRY MAINPROG, capitão.”

Esse pequeno detalhe resume uma grande verdade do mundo mainframe:

O código pode estar perfeito, mas a missão só termina quando compilação, linkedição e execução trabalham como uma única tripulação.

 

Sem comentários:

Enviar um comentário