Translate

Mostrar mensagens com a etiqueta performance cobol. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta performance cobol. Mostrar todas as mensagens

quarta-feira, 12 de novembro de 2025

🔥☕ O BATCH VAI FECHAR EM 40 MINUTOS! LABORATÓRIO PRÁTICO DE ENGENHARIA DE SOFTWARE PARA PROGRAMADOR

 

Bellacosa Mainframe laboratorio pratico engenharia de software mainframe

🔥☕ O BATCH VAI FECHAR EM 40 MINUTOS!

LABORATÓRIO PRÁTICO DE ENGENHARIA DE SOFTWARE PARA PROGRAMADOR COBOL JUNIOR NO IBM Z 💣💾

🏛️ Missão Enterprise

Você acaba de entrar no plantão noturno de um grande banco.

O fechamento batch começou.

O JES2 está carregado.
O DB2 processando milhões de transações.
O operador já abriu chamado.
O scheduler está pressionando a janela batch.

E agora…

💥 um programa COBOL começou a falhar em produção.

Sua missão:

✅ diagnosticar
✅ corrigir
✅ melhorar
✅ estabilizar
✅ preparar o sistema para sobreviver no mundo enterprise


🎯 OBJETIVOS DO LAB

Ao final deste laboratório você entenderá:

✅ mentalidade enterprise no IBM Z
✅ engenharia de software aplicada ao COBOL
✅ análise de ABEND
✅ legibilidade de código
✅ modularização
✅ tratamento de erro
✅ observabilidade
✅ restartabilidade
✅ debugging operacional

⏱️ Duração estimada: 30 a 40 minutos


☕ CENÁRIO DO AMBIENTE

Plataforma

ItemTecnologia
Sistema Operacionalz/OS
LinguagemCOBOL
BatchJES2
BancoDB2
OnlineCICS
SegurançaRACF
SchedulerControl-M

🚨 INCIDENTE INICIAL

O operador envia a seguinte mensagem:

JOB FINCLOSE ABEND S0C7
STEP001 FAILED

☕ O QUE É S0C7?

💥 erro de conversão numérica

Normalmente causado por:

  • campo inválido

  • lixo em variável

  • dado alfanumérico em campo numérico

  • corrupção de entrada


🔥 MISSÃO #1 — ANALISAR O CÓDIGO LEGADO

PROGRAMA RECEBIDO

IDENTIFICATION DIVISION.
PROGRAM-ID. FIN001.

DATA DIVISION.

WORKING-STORAGE SECTION.

01 WS-TOTAL PIC 9(09)V99 VALUE 0.

01 WS-VALOR PIC 9(05)V99.

PROCEDURE DIVISION.

MOVE 'ABCDE' TO WS-VALOR.

ADD WS-VALOR TO WS-TOTAL.

DISPLAY WS-TOTAL.

STOP RUN.

🎯 ATIVIDADE

Identifique:

✅ o problema
✅ o risco operacional
✅ o impacto em produção


✅ SOLUÇÃO

MOVE 'ABCDE' TO WS-VALOR

causa:

💥 S0C7

Porque:

  • WS-VALOR é numérico

  • ABCDE é alfanumérico


☕ DICA MAINFRAME

No mundo enterprise:

um campo inválido pode parar uma cadeia inteira de batch.


🔥 MISSÃO #2 — CRIANDO VALIDAÇÃO ENTERPRISE

REFATORAÇÃO

IF WS-ENTRADA NUMERIC
   MOVE WS-ENTRADA TO WS-VALOR
ELSE
   DISPLAY 'ERRO DADO INVALIDO'
   MOVE 0 TO WS-VALOR
END-IF.

🎯 O QUE FOI MELHORADO?

✅ proteção operacional
✅ estabilidade
✅ previsibilidade
✅ observabilidade


☕ CURIOSIDADE MAINFRAME

Muitos incidentes bancários históricos começaram com:

💀 um único campo inválido


🔥 MISSÃO #3 — O MONSTRO DAS 15 MIL LINHAS

Você recebeu um programa com:

☠️ GO TO
☠️ IF aninhado
☠️ sem comentários
☠️ sem modularização


EXEMPLO RUIM

IF A = 1
   IF B = 2
      IF C = 3
         MOVE 1 TO X.

🎯 ATIVIDADE

Refatore para estilo enterprise.


✅ SOLUÇÃO

IF CLIENTE-ATIVO
   PERFORM PROCESSA-CLIENTE
END-IF.

☕ DICA MAINFRAME

Código enterprise precisa ser:

✅ legível
✅ auditável
✅ sustentável
✅ fácil de alterar


🔥 MISSÃO #4 — ANALISANDO PERFORMANCE

O JOB ESTÁ DEMORANDO 5 HORAS

O operador reclama:

BATCH WINDOW ESTOURANDO

🎯 O QUE INVESTIGAR?

✅ SORT excessivo
✅ leitura duplicada
✅ acesso DB2
✅ EXCP
✅ loops
✅ índices


☕ DICA MAINFRAME

No IBM Z:

💸 CPU = dinheiro real


🔥 MISSÃO #5 — TRATAMENTO DE SQLCODE

CÓDIGO RECEBIDO

EXEC SQL
   SELECT NOME
   INTO :WS-NOME
   FROM CLIENTES
END-EXEC.

🎯 PROBLEMA

Nenhum tratamento de erro.


✅ SOLUÇÃO ENTERPRISE

EXEC SQL
   SELECT NOME
   INTO :WS-NOME
   FROM CLIENTES
END-EXEC.

IF SQLCODE = 100
   DISPLAY 'CLIENTE NAO ENCONTRADO'
ELSE
   IF SQLCODE NOT = 0
      DISPLAY 'ERRO DB2'
      DISPLAY SQLCODE
   END-IF
END-IF.

☕ CURIOSIDADE

SQLCODE -911

Pode indicar:

💥 deadlock
💥 timeout
💥 rollback automático


🔥 MISSÃO #6 — OBSERVABILIDADE

CÓDIGO SEM LOG

PERFORM PROCESSA.

🎯 PROBLEMA

Quando falhar:

☠️ ninguém entende o que aconteceu


✅ SOLUÇÃO

DISPLAY 'INICIO PROCESSAMENTO'

PERFORM PROCESSA

DISPLAY 'FIM PROCESSAMENTO'

☕ REGRA DE OURO

Se não logou…

não aconteceu.


🔥 MISSÃO #7 — ENGENHARIA DE SOFTWARE REAL

O QUE O JUNIOR PENSA

“Meu programa compilou.”


O QUE O ENGENHEIRO PENSA

✅ restart
✅ rollback
✅ auditoria
✅ rastreabilidade
✅ suporte
✅ manutenção
✅ observabilidade
✅ performance


☕ DESAFIO FINAL

O banco informa:

“Agora o programa processará 80 milhões de registros.”


O QUE VOCÊ ANALISA?

✅ batch window
✅ EXCP
✅ buffering
✅ índices DB2
✅ paralelismo
✅ checkpoint/restart
✅ SORT
✅ consumo CPU


🏛️ LIÇÃO FINAL DO LAB

No IBM Z:

☕ COBOL não é apenas linguagem.

É engenharia de sobrevivência enterprise.


💣 FRASE FINAL

“O verdadeiro programador mainframe não escreve apenas código.

Ele sustenta o mundo invisível que continua funcionando enquanto bilhões dormem.” ☕🔥

sábado, 22 de julho de 2023

Subscript versus Index : Quando um Programador Descobre que o Maior Vírus Não Está no Data Center... Está em um Mito que Sobrevive Desde os Anos 1970

 

Bellacosa Mainframe apresenta tabelas cobol subscript versus index

☕ Um Café no Bellacosa Mainframe

Subscript versus Index sem Mistérios para Programadores COBOL

Quando um Programador Descobre que o Maior Vírus Não Está no Data Center... Está em um Mito que Sobrevive Desde os Anos 1970

"Em um laboratório subterrâneo, um microscópio pode revelar um organismo invisível. Em um mainframe, um dump pode revelar um erro invisível. Em ambos os casos, a sobrevivência depende de entender detalhes que quase ninguém percebe."


Introdução – Bem-vindo ao Laboratório P4 do Mainframe

Imagine que você acaba de ser contratado para trabalhar em um dos maiores bancos do planeta.

São duas da manhã.

O processamento noturno começou.

Milhões de contas serão atualizadas.

Bilhões de registros serão lidos.

A folha de pagamento nacional será recalculada.

O sistema de cartões irá consolidar compras realizadas no mundo inteiro.

Tudo parece tranquilo...

Até que um batch começa a consumir CPU muito acima do esperado.

O monitor RMF mostra utilização elevada.

O WLM começa a redistribuir prioridades.

O operador do console observa o relógio.

O gerente pergunta:

"O que mudou?"

Depois de algumas horas de investigação, alguém encontra uma rotina COBOL.

Ela percorre uma tabela centenas de milhões de vezes usando um método inadequado de acesso.

Não existe bug.

Não existe ABEND.

Não existe corrupção de dados.

Existe apenas uma pequena decisão tomada por um programador anos atrás.

Uma decisão aparentemente inocente.

Subscript ou Index?

É exatamente essa pequena diferença que vamos investigar hoje.

Assim como em The Andromeda Strain (1971), onde cientistas analisam um organismo microscópico para impedir uma catástrofe, nós vamos colocar o COBOL sob um microscópio e descobrir que existe muito mais acontecendo do que aparenta.

Vista seu jaleco.

O laboratório Bellacosa Mainframe está oficialmente aberto.


O paciente: OCCURS

Antes de falar sobre Subscript ou Index precisamos entender o verdadeiro protagonista.

01 CLIENTES.
   05 CLIENTE OCCURS 100 TIMES.
      10 CODIGO PIC 9(6).
      10 NOME   PIC X(30).

Essa estrutura cria uma tabela.

Ou seja...

Na memória teremos algo semelhante a:

CLIENTE(1)

CLIENTE(2)

CLIENTE(3)

CLIENTE(4)

...

CLIENTE(100)

Nada de mágico aconteceu.

A memória simplesmente possui cem blocos consecutivos.

A pergunta é:

Como chegar até o elemento número 57?

É aí que a investigação começa.


O primeiro suspeito: o Subscript

O Subscript é o método que praticamente todo iniciante aprende.

01 WS-I PIC 9(4).

Depois:

MOVE CLIENTE-NOME (WS-I)

Parece simples.

E realmente é.

Mas existe um detalhe invisível.

Toda vez que fazemos isso, o compilador precisa calcular onde está aquele elemento.

Imagine uma estante enorme.

Você diz:

"Quero o livro número 37."

O bibliotecário pensa:

37

menos

1

×

tamanho do livro

Só depois ele encontra a posição correta.

Em linguagem de máquina acontece algo parecido.

Base da tabela

+

(I-1)

×

Tamanho do registro

Essa conta acontece continuamente.


O segundo suspeito: o Index

Agora surge um personagem misterioso.

05 CLIENTE OCCURS 100 TIMES
   INDEXED BY IDX-CLIENTE.

Observe uma coisa.

O Index não é declarado na Working-Storage.

Ele não possui PIC.

Não possui COMP.

Não possui tamanho conhecido.

Na verdade...

Você nunca sabe como ele realmente é.

Porque ele pertence ao compilador.

É um objeto especial.


O maior mito do COBOL

Durante décadas ouvimos:

"Index é uma variável COMP."

Não.

Isso nunca foi garantido pelo padrão COBOL.

O Enterprise COBOL pode implementá-lo da maneira que desejar.

Ele normalmente utiliza deslocamentos internos.

Mas isso faz parte da implementação do compilador.

Não do programa.

Por isso não podemos fazer:

ADD 1 TO IDX

O compilador simplesmente responde:

"Não."

Quem controla o Index é ele.

Não você.


O laboratório encontra uma pista

Imagine esta tabela.

Registro

34 bytes

O elemento 2 começa exatamente após 34 bytes.

O elemento 3 começa após 68.

O elemento 4 após 102.

O Index guarda justamente esse deslocamento.

Em vez de calcular tudo novamente...

Ele já sabe onde está.

É como um GPS.

Enquanto o Subscript pergunta:

"Qual rua?"

O Index responde:

"Já estou estacionado na frente da casa."


O verdadeiro funcionamento interno

Subscript

Base

+

(I × tamanho)

Index

Base

+

Offset

Essa diferença parece pequena.

Mas em milhões de repetições...

Ela cresce.

Muito.


Então o Index sempre vence?

Não.

Aqui entra um dos maiores avanços dos compiladores modernos.

Os primeiros compiladores COBOL surgiram quando processadores eram extremamente limitados.

Multiplicar números custava caro.

Hoje...

O Enterprise COBOL 6.x é absurdamente inteligente.

Ele reconhece padrões.

Remove cálculos repetidos.

Mantém valores em registradores.

Transforma multiplicações em deslocamentos.

Faz otimizações que simplesmente não existiam nos anos 70.

Em diversas situações...

Subscript e Index acabam gerando praticamente o mesmo código de máquina.

Sim.

Você leu corretamente.


O fantasma dos anos 1970

Por que então tantos livros dizem:

"Nunca use Subscript."

Porque eles estavam certos...

Na época deles.

Era uma realidade dos compiladores antigos.

Hoje a situação mudou.

Mas o mito permaneceu vivo.

É um daqueles vírus culturais que atravessam gerações.

Assim como em The Andromeda Strain, onde o verdadeiro perigo era um organismo microscópico desconhecido, aqui o verdadeiro "vírus" é uma recomendação antiga repetida sem contexto.


SEARCH: onde o Index reina absoluto

Existe um momento em que não há discussão.

SEARCH.

SEARCH CLIENTE

Ou

SEARCH ALL CLIENTE

Esses comandos exigem Index.

Por quê?

Porque o compilador controla o deslocamento automaticamente.

Ele move o ponteiro.

Avança.

Retrocede.

Calcula posições intermediárias.

Tudo sem intervenção do programador.


SEARCH ALL e a inteligência da busca binária

Imagine uma lista com um milhão de clientes.

Uma busca sequencial faria:

1

2

3

4

5

6

...

999999

SEARCH ALL faz algo muito diferente.

500000

↓

250000

↓

375000

↓

312500

Cada comparação elimina metade da tabela.

É o famoso algoritmo Binary Search.

Complexidade:

O(log n)

Enquanto uma busca sequencial é

O(n)

A diferença entre essas duas abordagens é gigantesca quando falamos em milhões de registros.


Um detalhe que poucos conhecem

Podemos copiar índices.

SET IDX-B TO IDX-A

Isso não copia um número.

Copia o deslocamento interno.

É extremamente eficiente.


Outra curiosidade

Você consegue escrever:

DISPLAY WS-I

Mas nunca faz:

DISPLAY IDX

Porque ele não representa um número lógico.

Ele representa uma posição interna conhecida apenas pelo compilador.


O que realmente acontece no processador IBM Z?

Agora entramos na sala limpa do laboratório.

Os processadores IBM Z atuais possuem instruções extremamente sofisticadas para cálculo de endereços.

O Enterprise COBOL conhece essas instruções.

Ele utiliza registradores.

Mantém offsets.

Evita recálculos.

Explora pipelines internos do processador.

Resultado?

Muito do trabalho pesado desaparece antes mesmo do programa executar.

É uma das razões pelas quais aplicações COBOL continuam processando milhões de transações por segundo.


Comparando com C

Quem conhece C entende rapidamente.

Subscript lembra:

array[i]

O compilador faz:

base

+

i

×

sizeof(item)

Já o Index funciona como um ponteiro controlado pelo compilador.

Embora tecnicamente não seja um ponteiro exposto ao programador.


Quando usar Subscript?

Excelente escolha para:

✔ programas didáticos

✔ tabelas pequenas

✔ processamento simples

✔ manutenção fácil

✔ código mais intuitivo


Quando usar Index?

Ideal para:

✔ SEARCH

✔ SEARCH ALL

✔ tabelas enormes

✔ processamento intensivo

✔ milhões de iterações

✔ rotinas críticas


Curiosidade histórica

Nos anos 1970, CPU era um recurso precioso.

Algumas empresas alugavam tempo de processamento por minuto.

Cada instrução economizada significava dinheiro.

Não era exagero.

Era sobrevivência.

Foi nesse ambiente que a preferência por Index nasceu.

Décadas depois, muitos programadores continuam repetindo a recomendação sem conhecer sua origem.


Easter Egg Bellacosa Mainframe 🥚

Se você assistir novamente The Andromeda Strain, perceberá que praticamente toda a tensão do filme vem da investigação meticulosa.

Ninguém sai correndo.

Ninguém explode prédios.

Ninguém derrota o problema com um discurso motivacional.

Os cientistas fazem algo muito mais difícil:

Eles observam.

Coletam evidências.

Eliminam hipóteses.

É exatamente assim que trabalham os melhores analistas de performance em mainframe.

Eles não começam alterando código.

Eles analisam:

  • RMF

  • SMF

  • CPU Time

  • EXCP

  • Cache

  • WLM

  • Explain

  • Dumps

  • Estatísticas do compilador

Performance não nasce de achismos.

Nasce de evidências.


Curiosidades que poucos iniciantes conhecem

☕ O comando SET IDX UP BY 1 não soma necessariamente o valor 1. Ele ajusta o deslocamento interno para o próximo elemento da tabela, independentemente do tamanho do registro.

☕ Um OCCURS DEPENDING ON cria tabelas de tamanho variável, exigindo atenção extra ao acessar elementos.

SEARCH ALL só funciona corretamente quando a tabela está previamente ordenada pelo campo pesquisado. Muitos erros de produção surgem porque essa regra foi esquecida.

☕ O Enterprise COBOL pode eliminar cálculos redundantes durante a compilação, principalmente com níveis altos de otimização (OPTIMIZE).

☕ Em alguns casos, o gargalo não está no acesso à tabela, mas no I/O, em chamadas ao Db2, VSAM ou CICS. Otimizar Subscript versus Index sem medir o restante do programa pode não produzir ganho perceptível.


Passo a passo para escolher corretamente

  1. A tabela é pequena?
    Use Subscript. A clareza do código geralmente vale mais.

  2. Há busca frequente?
    Considere INDEXED BY e SEARCH.

  3. A tabela está ordenada?
    Aproveite SEARCH ALL para busca binária.

  4. O programa processa milhões de registros?
    Meça o desempenho antes de otimizar.

  5. O compilador moderno já resolve o problema?
    Verifique o código gerado e os relatórios de otimização antes de assumir que existe vantagem.

  6. Você está seguindo um mito ou uma evidência?
    Essa talvez seja a pergunta mais importante de todas.


Conclusão – O verdadeiro agente invisível

No fim da investigação, descobrimos que o verdadeiro inimigo não era o Subscript.

Nem o Index.

O verdadeiro agente invisível era o mito.

Durante décadas, frases como "Index sempre é mais rápido" foram repetidas como se fossem leis universais. A realidade é muito mais interessante: compiladores evoluíram, processadores IBM Z ficaram extraordinariamente eficientes e muitas otimizações passaram a acontecer automaticamente.

Isso não significa que Subscript e Index sejam iguais. Eles continuam sendo conceitos diferentes, com propósitos diferentes. O Index permanece essencial para SEARCH, SEARCH ALL e cenários de alto desempenho. O Subscript continua sendo simples, legível e perfeitamente adequado para inúmeras aplicações.

O programador COBOL que apenas memoriza regras continua preso aos manuais dos anos 1970. Já aquele que entende o funcionamento interno da linguagem, do compilador e da arquitetura IBM Z enxerga além dos mitos. Ele sabe quando confiar no compilador, quando medir desempenho e quando realmente vale a pena otimizar.

Como em The Andromeda Strain, o sucesso não pertence a quem faz mais barulho. Pertence a quem observa os detalhes invisíveis, testa hipóteses e toma decisões baseadas em evidências. No universo do mainframe, essa mentalidade é o que transforma um simples programador em um verdadeiro investigador dos sistemas críticos que sustentam bancos, governos e a economia mundial.

quinta-feira, 11 de maio de 2023

Guia Completo de COBOL Recursivo no IBM Mainframe

 

 

Bellacosa Mainframe cobol recursivo


☕ Um Café no Bellacosa Mainframe

Guia Completo de COBOL Recursivo no IBM Mainframe

Da Teoria à Engenharia de Software em Enterprise COBOL

Ao longo desta série exploramos um dos assuntos mais fascinantes — e também menos compreendidos — do Enterprise COBOL: a recursividade.

Embora poucos sistemas corporativos utilizem algoritmos recursivos no dia a dia, compreender esse recurso permite enxergar o funcionamento interno do Enterprise COBOL, do Language Environment (LE) e da pilha de execução (Call Stack), oferecendo uma visão muito mais profunda sobre como programas COBOL realmente funcionam.

Esta série foi escrita pensando no Programador COBOL Padawan que deseja evoluir para Pleno e Sênior, compreendendo não apenas a sintaxe da linguagem, mas também sua arquitetura e seus mecanismos internos. 

📘 Parte 1 — Conceitos Fundamentais

Nesta primeira parte mostramos que recursividade vai muito além do tradicional exemplo do cálculo do fatorial.

Foram apresentados conceitos como:

  • O que realmente significa um programa recursivo.

  • Como funciona o Call Stack.

  • Como o Enterprise COBOL cria novas ativações do programa.

  • Diferenças entre programas RECURSIVE e tradicionais.

  • A importância da Working-Storage e da Local-Storage.

  • Como o Language Environment participa da execução.

➡️ Leia a Parte 1: https://eljefemidnightlunch.blogspot.com/2023/01/cobol-recursivo-muito-alem-do-fatorial.html


📘 Parte 2A — Construindo o Primeiro Programa Recursivo

Na segunda etapa colocamos a teoria em prática.

Construímos um programa recursivo completo em Enterprise COBOL e acompanhamos sua execução passo a passo.

Entre os assuntos abordados:

  • Exemplo completo comentado.

  • Caso Base (Base Case).

  • Crescimento e redução da pilha.

  • Como ocorre o retorno das chamadas (Unwinding).

  • Comparação entre recursividade e PERFORM.

  • Boas práticas para evitar erros comuns.

➡️ Leia a Parte 2A:
https://eljefemidnightlunch.blogspot.com/2023/02/cobol-recursivo-muito-alem-do-fatorial.html


📘 Parte 2B — Aplicações Reais da Recursividade

Depois dos conceitos básicos, mostramos onde a recursividade realmente faz sentido em ambientes corporativos.

Foram apresentados diversos cenários reais, como:

  • Percorrimento de árvores.

  • Estruturas XML.

  • Objetos JSON.

  • Busca em profundidade (DFS).

  • QuickSort.

  • MergeSort.

  • Estruturas hierárquicas.

  • Organogramas.

  • Diretórios do z/OS UNIX (USS).

  • Conceitos utilizados por compiladores e bancos de dados.

Também discutimos quando não utilizar recursividade.

➡️ Leia a Parte 2B:
https://eljefemidnightlunch.blogspot.com/2023/03/cobol-recursivo-muito-alem-do-fatorial.html


📘 Parte 2C — Performance, Debugging e Engenharia

Na última parte entramos nos detalhes que normalmente interessam aos Programadores Mainframe mais experientes.

Entre os temas abordados:

  • Performance da recursividade.

  • Consumo de memória.

  • Stack Overflow.

  • Tail Recursion.

  • Call Stack.

  • Debugging de programas recursivos.

  • Análise de Dumps.

  • Papel do Language Environment (LE).

  • Working-Storage versus Local-Storage.

  • Checklist para utilização segura da recursividade.

  • Dicas, truques e curiosidades pouco conhecidas.

➡️ Leia a Parte 2C:

https://eljefemidnightlunch.blogspot.com/2023/04/cobol-recursivo-muito-alem-do-fatorial.html


O que um Programador COBOL deve levar desta série?

Mesmo que você nunca desenvolva um algoritmo recursivo em produção, compreender esse tema permitirá entender melhor:

  • Como o Enterprise COBOL administra memória.

  • Como funciona a pilha de chamadas.

  • Como parâmetros são preservados.

  • O papel do Language Environment.

  • Por que existe a Local-Storage Section.

  • Como analisar dumps mais complexos.

  • Como modelar problemas hierárquicos de forma elegante.

Em outras palavras, estudar recursividade não serve apenas para aprender uma técnica de programação; serve para compreender a engenharia invisível que sustenta aplicações críticas executadas diariamente no IBM Z.

Conclusão

Recursividade é uma ferramenta poderosa, mas não deve ser utilizada apenas porque produz código elegante. Em processamento linear, o tradicional PERFORM continua sendo, na maioria dos casos, a solução mais eficiente.

Entretanto, quando lidamos com árvores, estruturas aninhadas, documentos XML, objetos JSON, algoritmos de busca, compiladores e diversos outros problemas naturalmente hierárquicos, a recursividade oferece uma forma clara, organizada e expressiva de modelar a solução.

Esperamos que esta série tenha ajudado você a enxergar o Enterprise COBOL sob uma nova perspectiva. Mais do que aprender um recurso da linguagem, você percorreu uma jornada pela arquitetura do IBM Mainframe, compreendendo como memória, pilha de execução, Language Environment e engenharia de software trabalham em conjunto para manter alguns dos sistemas mais críticos do mundo em funcionamento.

☕ Nos encontramos no próximo Café no Bellacosa Mainframe!


quarta-feira, 19 de abril de 2023

COBOL Recursivo — Muito Além do Fatorial (Parte II section C)

 

Bellacosa Mainframe apresenta cobol recursivo parte II sec c

☕ Um Café no Bellacosa Mainframe

COBOL Recursivo — Muito Além do Fatorial (Parte 2C)

Performance, Tail Recursion, Debugging, Language Environment e os Segredos que Todo Programador Mainframe Deveria Conhecer

"A pergunta que todo Programador COBOL Sênior faz não é 'a recursão funciona?', mas sim 'quanto ela custa, quando vale a pena e como o Enterprise COBOL administra tudo isso por baixo dos panos?'" 


Introdução

Chegamos à última parte da nossa jornada.

Na Parte 1 entendemos o conceito.

Na Parte 2A acompanhamos a pilha crescendo e diminuindo.

Na Parte 2B vimos onde a recursividade realmente faz sentido.

Agora vamos responder às perguntas que normalmente aparecem em entrevistas técnicas e discussões entre Programadores Sêniores.

  • A recursão é lenta?

  • Quanto de memória ela consome?

  • O compilador otimiza chamadas recursivas?

  • Existe Tail Recursion no Enterprise COBOL?

  • Como depurar uma rotina recursiva?

  • O que acontece em um dump?

  • Como o Language Environment administra tudo isso?

Prepare mais um café.

Agora vamos olhar por dentro do motor do Enterprise COBOL.


O verdadeiro custo de uma chamada recursiva

Imagine uma rotina extremamente simples.

ROTINA A

↓

ROTINA A

↓

ROTINA A

↓

ROTINA A

Muitos imaginam que apenas uma instrução CALL é executada.

Na realidade ocorre muito mais.

Cada chamada exige que o sistema preserve o contexto da execução atual antes de iniciar a próxima.

Normalmente isso envolve:

  • salvar registradores utilizados;

  • armazenar o endereço de retorno;

  • reservar espaço para variáveis locais;

  • preparar parâmetros;

  • criar um novo frame de execução;

  • transferir o controle para a nova ativação.

Quando essa rotina retorna, todo esse processo acontece novamente, porém na ordem inversa.


Quanto custa isso?

Cada chamada possui um custo fixo.

Imagine uma recursão com profundidade 500.

Teremos aproximadamente:

  • 500 ativações;

  • 500 retornos;

  • centenas de registradores preservados;

  • centenas de frames criados.

Enquanto isso um simples:

PERFORM VARYING

reutiliza praticamente o mesmo ambiente durante toda a execução.

É por isso que loops costumam ser mais rápidos.


O PERFORM continua sendo o campeão

Imagine um processamento de um arquivo VSAM.

100 milhões de registros

Qual solução utilizar?

Recursão?

Jamais.

O correto continua sendo.

PERFORM UNTIL EOF

Por quê?

Porque o problema é linear.

Cada registro é independente.

Não existe árvore.

Não existe hierarquia.

Não existe motivo para criar milhares de novos contextos de execução.


Quando a recursão vence

Agora imagine.

Empresa

↓

Departamento

↓

Subdepartamento

↓

Equipe

↓

Funcionário

Aqui o problema possui profundidade variável.

Não sabemos quantos níveis existirão.

A estrutura muda constantemente.

Nesse cenário a recursividade pode produzir um código muito menor, mais legível e muito mais fácil de manter.


Complexidade não é desempenho

Existe uma confusão bastante comum.

Alguns desenvolvedores acreditam que:

"Se um algoritmo é recursivo então ele é lento."

Não.

O que determina o desempenho é o algoritmo.

QuickSort continua sendo extremamente eficiente.

MergeSort também.

DFS também.

A recursão é apenas uma técnica utilizada para implementar esses algoritmos.


A profundidade da pilha

Imagine.

Nível 1

↓

Nível 2

↓

Nível 3

↓

...

↓

Nível 500

Cada nível ocupa memória.

Quanto maior a profundidade.

Maior o consumo.

Por isso uma pergunta importante é:

Qual a profundidade máxima esperada?


Stack Overflow

Toda pilha possui limite.

Se o algoritmo continuar chamando a si próprio indefinidamente.

Chegará um momento em que não haverá espaço suficiente.

Resultado.

Stack Overflow.

Em ambientes Enterprise COBOL isso normalmente se manifesta como falha de execução provocada pelo esgotamento da pilha ou da região disponível para o processo.


Como evitar?

Existem algumas regras simples.

Sempre possuir:

  • caso base;

  • redução do problema;

  • validação da entrada;

  • profundidade conhecida.

Nunca confiar que os dados "sempre estarão corretos".


Um pequeno erro pode ser catastrófico

Imagine.

PROCESSA(100)

↓

PROCESSA(100)

↓

PROCESSA(100)

Percebe o problema?

O valor nunca muda.

O caso base jamais será alcançado.

O algoritmo continuará chamando a si próprio até consumir toda a pilha.

Esse é um dos bugs mais perigosos em programas recursivos.


Tail Recursion

Agora chegamos a um assunto que raramente aparece em livros de COBOL.

Considere.

ROTINA

↓

chama novamente

↓

retorna imediatamente

Não existe mais nada para fazer após o retorno.

Esse padrão recebe o nome de Tail Recursion.


Por que ela é especial?

Alguns compiladores conseguem transformar automaticamente esse tipo de recursão em um simples loop.

Resultado.

A pilha praticamente deixa de crescer.

Essa otimização é conhecida como Tail Call Optimization (TCO).


E o Enterprise COBOL?

O Enterprise COBOL não é conhecido por realizar uma otimização geral de Tail Call equivalente à encontrada em linguagens como Scheme ou algumas implementações modernas de C/C++. Em outras palavras, não é seguro assumir que uma chamada recursiva em posição de cauda será convertida automaticamente em um laço.

A recomendação prática para aplicações corporativas continua sendo:

  • se a profundidade pode ser grande;

  • e existe uma solução iterativa clara;

prefira PERFORM.

Sempre que escrever uma rotina recursiva pensando em desempenho, consulte a documentação da versão específica do compilador e valide o comportamento com testes e medições.


Debugging

Aqui começa uma das maiores dificuldades.

Imagine um breakpoint.

Você observa.

LS-NUMERO = 4

Continua executando.

Agora.

LS-NUMERO = 3

Depois.

LS-NUMERO = 2

Depois.

LS-NUMERO = 1

O iniciante acredita que a variável está sendo alterada.

Na realidade.

Você está olhando ativações diferentes.

Cada uma possui sua própria Local-Storage.

Esse detalhe costuma confundir quem está depurando um programa recursivo pela primeira vez.


Como depurar corretamente

A primeira dica.

Sempre descubra:

"Em qual nível da pilha estou?"

Depois.

Observe:

  • parâmetros;

  • variáveis locais;

  • valor de retorno.

Nunca apenas o conteúdo de uma variável.


O Dump

Quando ocorre um abend.

O dump costuma revelar algo parecido.

ROTINA

↓

ROTINA

↓

ROTINA

↓

ROTINA

↓

ROTINA

Centenas de vezes.

Isso normalmente indica:

  • ausência de caso base;

  • dados inválidos;

  • profundidade inesperada.

Aprender a reconhecer esse padrão economiza muitas horas de investigação.


Language Environment (LE)

Poucos Programadores Júnior conhecem o LE.

Mas praticamente todo programa Enterprise COBOL moderno depende dele.

O Language Environment é responsável por diversos serviços de tempo de execução, incluindo a organização do ambiente necessário para chamadas de programas, tratamento de exceções, gerenciamento de pilha e integração entre linguagens.

Quando um programa recursivo cria novas ativações, existe uma infraestrutura por trás garantindo que cada contexto seja preservado corretamente.

Sem esse ambiente de execução seria muito mais difícil oferecer suporte consistente a recursos modernos do Enterprise COBOL.


O papel do LOCAL-STORAGE revisitado

Depois de tudo que vimos.

Fica fácil entender.

Cada frame precisa de suas próprias variáveis.

É exatamente isso que Local-Storage oferece.

Visualmente.

+-------------------+

Frame 1

LOCAL-STORAGE

+-------------------+

Frame 2

LOCAL-STORAGE

+-------------------+

Frame 3

LOCAL-STORAGE

+-------------------+

Cada chamada possui sua própria área.

Nenhuma interfere na outra.


E a Working-Storage?

Continua existindo.

Mas pertence ao programa.

Não à chamada.

Visualmente.

WORKING-STORAGE

↓

Frame 1

↓

Frame 2

↓

Frame 3

Todos enxergam a mesma área.

Por isso ela deve armazenar apenas informações realmente compartilhadas.


O impacto em aplicações CICS

Embora o CICS suporte programas escritos em Enterprise COBOL, nem todo programa é um bom candidato à recursão.

Em aplicações OLTP, normalmente buscamos:

  • baixa latência;

  • previsibilidade;

  • consumo controlado de recursos.

Por isso, algoritmos profundamente recursivos raramente aparecem na lógica de transações de alta frequência.

Quando uma solução recursiva for necessária, ela deve ser cuidadosamente analisada quanto à profundidade máxima, uso de memória e comportamento sob carga.


Recursão e paralelismo

Existe outro ponto interessante.

Recursividade não significa paralelismo.

Nem concorrência.

São conceitos completamente diferentes.

É possível possuir:

  • algoritmo recursivo sequencial;

  • algoritmo iterativo paralelo;

  • algoritmo recursivo paralelo.

Não confunda os conceitos.


Quando um Sênior escolhe recursão?

Normalmente quando observa:

✓ estrutura hierárquica

✓ profundidade variável

✓ código muito mais simples

✓ facilidade de manutenção

✓ menor complexidade lógica

Ou seja.

A decisão raramente é baseada apenas em velocidade.


Checklist Bellacosa ☕

Antes de escrever um algoritmo recursivo, faça estas perguntas:

  • Existe um caso base claramente definido?

  • Cada chamada aproxima o problema desse caso base?

  • A profundidade máxima é conhecida ou razoavelmente limitada?

  • Uma solução iterativa seria significativamente mais simples?

  • As variáveis específicas de cada chamada estão em LOCAL-STORAGE?

  • O algoritmo foi testado com entradas extremas?

  • O comportamento em erro e em dumps é compreendido pela equipe?

Se alguma resposta for "não", vale a pena revisar o projeto antes de seguir.


Truques de Programador Mainframe

Algumas boas práticas que ajudam muito.

✔ Nunca misture lógica recursiva com variáveis globais desnecessárias.

✔ Documente claramente qual é o caso base.

✔ Comente qual parâmetro reduz o problema.

✔ Sempre teste entradas:

  • mínimas;

  • máximas;

  • inválidas.

✔ Desenhe a árvore de chamadas antes de codificar.

✔ Não escolha recursão apenas porque o código fica "bonito".

Código elegante que produz um abend continua sendo um programa ruim.


Easter Egg Bellacosa ☕

Existe uma curiosidade interessante.

Muitos Programadores Mainframe trabalham vinte ou trinta anos sem escrever um único algoritmo recursivo.

Mesmo assim.

Os melhores profissionais costumam compreender perfeitamente:

  • Call Stack;

  • Frames;

  • Local-Storage;

  • Language Environment;

  • Endereços de retorno;

  • Passagem de parâmetros.

Por quê?

Porque todos esses conceitos aparecem diariamente em dumps, depuração, integração com C, Assembler, APIs, LE e análise de problemas complexos.

Ou seja.

Você pode nunca escrever um QuickSort recursivo.

Mas provavelmente utilizará o conhecimento adquirido estudando recursão durante toda sua carreira.


Uma reflexão final

Existe um velho ditado entre arquitetos de software.

"Toda abstração tem um custo."

A recursividade é uma abstração poderosa.

Ela reduz dezenas de linhas de código para poucas chamadas elegantes.

Em troca.

Consome pilha.

Cria novos contextos.

Exige planejamento.

O Programador Júnior pergunta:

"Posso usar?"

O Programador Pleno pergunta:

"Vale a pena usar?"

O Programador Sênior pergunta:

"Qual é o impacto dessa decisão daqui a cinco anos?"

É essa mudança de perspectiva que diferencia quem apenas domina a sintaxe de quem realmente compreende engenharia de software.


Conclusão da Série

Se você acompanhou esta série desde a Parte 1, provavelmente percebeu que o objetivo nunca foi ensinar apenas um algoritmo de fatorial.

Nosso verdadeiro objetivo foi mostrar que a recursividade é uma porta de entrada para compreender a arquitetura do Enterprise COBOL.

Ao estudar esse tema, aprendemos sobre:

  • Call Stack;

  • Frames de execução;

  • RECURSIVE;

  • WORKING-STORAGE versus LOCAL-STORAGE;

  • passagem de parâmetros;

  • modelagem de problemas hierárquicos;

  • árvores, XML, JSON e algoritmos clássicos;

  • desempenho, consumo de memória e depuração.

Talvez você passe toda a carreira sem precisar implementar uma rotina recursiva em produção.

Ainda assim, entender como ela funciona tornará muito mais fácil compreender dumps, o Language Environment, integrações com C e Assembler, além do comportamento interno do Enterprise COBOL.

No fim das contas, a maior contribuição da recursividade não é ensinar o computador a chamar uma rotina novamente.

É ensinar o programador a pensar em estruturas, contexto, memória e arquitetura.

E essa forma de pensar acompanha um verdadeiro Engenheiro Mainframe por toda a vida profissional.

☕ Fim da série "COBOL Recursivo — Muito Além do Fatorial". Juntas, as Partes 1, 2A, 2B e 2C formam um material extenso e progressivo, saindo dos fundamentos até aspectos de arquitetura e engenharia de software voltados ao Enterprise COBOL no IBM Z.


terça-feira, 21 de fevereiro de 2023

COBOL Recursivo — Muito Além do Fatorial (Parte II - Section A)

 

Bellacosa Mainframe apresenta cobol recursivo parte ii sec a

☕ Um Café no Bellacosa Mainframe

COBOL Recursivo — Muito Além do Fatorial (Parte 2A)

Construindo Nosso Primeiro Programa Recursivo: Entendendo o Call Stack Passo a Passo

"Todo programador COBOL aprende a escrever um PERFORM. Poucos já pararam para observar o que realmente acontece quando um programa chama a si próprio. Nesta segunda parte, vamos acompanhar cada chamada como se estivéssemos olhando diretamente para a memória do IBM Z."


Introdução

Na primeira parte desta série entendemos que recursividade não é simplesmente "um programa chamando ele mesmo".

Ela representa a criação de novos contextos de execução, cada um possuindo seu próprio estado, seus parâmetros e, quando corretamente projetado, suas próprias variáveis locais.

Agora chegou o momento de acompanhar isso acontecendo.

Não vamos começar falando de árvores binárias.

Nem XML.

Nem JSON.

Vamos começar pelo exemplo mais famoso da computação.

O cálculo do fatorial.

Não porque seja o algoritmo mais útil.

Mas porque ele é pequeno o suficiente para enxergarmos exatamente como a pilha cresce e diminui.

Nosso objetivo não é aprender matemática.

Nosso objetivo é aprender como o Enterprise COBOL pensa.


Antes de escrever uma linha de código...

Imagine que alguém pergunte:

Quanto vale 5!?

Matematicamente sabemos:

5! = 5 × 4 × 3 × 2 × 1

Mas existe outra forma de enxergar.

5!

↓

5 × 4!

↓

5 × (4 × 3!)

↓

5 × (4 × (3 × 2!))

↓

5 × (4 × (3 × (2 × 1!)))

Perceba algo interessante.

O problema original é reduzido a um problema menor.

Depois outro.

Depois outro.

Até chegar em um caso extremamente simples.

1! = 1

Esse ponto recebe um nome muito importante.

Caso Base (Base Case).

Toda recursão possui um.

Sem ele...

O algoritmo nunca termina.


O primeiro programa recursivo

Vamos analisar uma implementação didática.

IDENTIFICATION DIVISION.
PROGRAM-ID. FATORIAL IS RECURSIVE.

DATA DIVISION.

LOCAL-STORAGE SECTION.

01 LS-NUMERO       PIC 9(4).
01 LS-RESULTADO    PIC 9(18).

LINKAGE SECTION.

01 LK-NUMERO       PIC 9(4).
01 LK-RESULTADO    PIC 9(18).

PROCEDURE DIVISION USING LK-NUMERO LK-RESULTADO.

    IF LK-NUMERO <= 1
        MOVE 1 TO LK-RESULTADO
    ELSE
        SUBTRACT 1 FROM LK-NUMERO GIVING LS-NUMERO

        CALL "FATORIAL"
             USING LS-NUMERO
                   LS-RESULTADO

        COMPUTE LK-RESULTADO =
                LK-NUMERO * LS-RESULTADO
    END-IF

    GOBACK.

Não se preocupe se parecer estranho.

Vamos desmontá-lo completamente.


O primeiro detalhe importante

Observe a primeira linha.

PROGRAM-ID. FATORIAL IS RECURSIVE.

Essa pequena palavra muda completamente o comportamento esperado do programa.

Ela informa ao compilador que poderão existir várias ativações simultâneas da mesma rotina.

Sem isso, o compilador poderá assumir um modelo inadequado para esse tipo de chamada.


Por que usamos LOCAL-STORAGE?

Veja novamente.

LOCAL-STORAGE SECTION.

01 LS-NUMERO.
01 LS-RESULTADO.

Muitos iniciantes perguntam:

"Posso colocar isso na Working-Storage?"

Tecnicamente...

Pode.

Mas provavelmente produzirá resultados incorretos.

Vamos entender por quê.


O que aconteceria usando Working-Storage?

Imagine:

WORKING-STORAGE

LS-NUMERO = 5

Primeira chamada.

Tudo bem.

Agora o programa chama ele mesmo.

WORKING-STORAGE

LS-NUMERO = 4

Ops.

O valor 5 desapareceu.

Nova chamada.

WORKING-STORAGE

LS-NUMERO = 3

Mais uma vez.

WORKING-STORAGE

LS-NUMERO = 2

Depois.

WORKING-STORAGE

LS-NUMERO = 1

Quando retornar...

Quem lembra do cinco?

Ninguém.

Ele foi sobrescrito.

É exatamente esse tipo de problema que Local-Storage resolve.


Agora usando Local-Storage

Cada chamada recebe sua própria cópia.

Visualmente.

FATORIAL(5)

LS-NUMERO = 5

Chama.

FATORIAL(4)

LS-NUMERO = 4

Chama.

FATORIAL(3)

LS-NUMERO = 3

Depois.

FATORIAL(2)

LS-NUMERO = 2

Depois.

FATORIAL(1)

LS-NUMERO = 1

Nenhuma variável interfere na outra.

Cada ativação possui seu próprio ambiente.


Vamos acompanhar a execução

Chamamos.

CALL FATORIAL(5)

O programa verifica.

5 <= 1 ?

Não.

Então calcula.

5 × FATORIAL(4)

Mas ele ainda não sabe quanto vale FATORIAL(4).

Então precisa descobrir.


Segunda chamada

Agora executa.

FATORIAL(4)

Pergunta.

4 <= 1 ?

Não.

Calcula.

4 × FATORIAL(3)

Ainda não sabe.

Então chama novamente.


Terceira chamada

FATORIAL(3)

Ainda não terminou.

Chama.

FATORIAL(2)

Quarta chamada

Agora.

FATORIAL(2)

Ainda não chegou.

Chama.

FATORIAL(1)

Quinta chamada

Finalmente.

FATORIAL(1)

Agora acontece algo mágico.

1 <= 1

SIM

O algoritmo encontrou o caso base.

Retorna.

1

A partir daqui...

A pilha começa a voltar.


A pilha crescendo

Durante a descida.

+----------------------+
|FATORIAL(1)           |
+----------------------+
|FATORIAL(2)           |
+----------------------+
|FATORIAL(3)           |
+----------------------+
|FATORIAL(4)           |
+----------------------+
|FATORIAL(5)           |
+----------------------+

Observe.

Nenhum cálculo foi concluído.

Todos aguardam.


Agora começa a subida

FATORIAL(1)

retorna

1

Então.

FATORIAL(2)

faz.

2 × 1

=

2

Retorna.


Depois.

FATORIAL(3)

recebe.

2

Calcula.

3 × 2

=

6

Retorna.


Depois.

FATORIAL(4)

recebe.

6

Calcula.

4 × 6

=

24

Retorna.


Depois.

FATORIAL(5)

recebe.

24

Calcula.

5 × 24

=

120

Agora sim.

Fim.


O momento em que tudo faz sentido

Perceba.

A descida não resolve o problema.

Ela apenas divide.

A subida resolve.

Esse conceito aparece em praticamente todos os algoritmos recursivos.


O papel do CALL

Muitos imaginam que o CALL copie o programa inteiro.

Não.

Ele apenas cria um novo contexto.

É como se o Language Environment dissesse:

"Guarde tudo o que estava acontecendo."

"Execute esta nova instância."

"Quando terminar, volte exatamente daqui."

Esse "guardar tudo" inclui:

  • registradores;

  • endereço de retorno;

  • parâmetros;

  • estado da execução.


Comparando com PERFORM

Agora vamos resolver exatamente o mesmo problema usando um laço tradicional.

MOVE 1 TO WS-RESULTADO

PERFORM VARYING WS-I
        FROM 1 BY 1
        UNTIL WS-I > WS-NUMERO

    MULTIPLY WS-I
         BY WS-RESULTADO

END-PERFORM

Muito menor.

Muito mais simples.

Muito mais eficiente.

Então surge a pergunta.


Por que alguém usaria recursão?

Porque nem todos os problemas são lineares.

Imagine uma árvore.

             CEO

      /        |        \

Financeiro    RH        TI

              |

         Recrutamento

Como percorrer isso usando apenas PERFORM?

É possível.

Mas rapidamente surgem:

  • pilhas auxiliares;

  • vetores;

  • índices;

  • controles complexos.

Já a solução recursiva acompanha naturalmente a estrutura da árvore.

Ela entra em um nó.

Depois em seus filhos.

Depois nos filhos dos filhos.

E assim sucessivamente.


A recursão modela o problema

Esse talvez seja o conceito mais importante deste artigo.

A melhor solução nem sempre é a mais rápida.

Muitas vezes ela é a que representa o problema de forma mais natural.

Uma árvore chama outra árvore.

Uma pasta contém outras pastas.

Um elemento XML contém outros elementos.

Um objeto JSON contém outros objetos.

Todos esses cenários possuem uma natureza recursiva.


O custo da elegância

Infelizmente...

Elegância tem preço.

Cada chamada implica em:

  • criação de um novo frame;

  • salvamento de registradores;

  • passagem de parâmetros;

  • reserva de memória local;

  • desvio para uma nova execução;

  • retorno ao ponto original.

Enquanto um PERFORM reutiliza o mesmo contexto durante todas as iterações, a recursão cria um novo contexto a cada nível.

Por isso, em rotinas batch que processam milhões de registros sequenciais, o PERFORM quase sempre será mais eficiente.


O que um Programador COBOL Padawan deve observar neste exemplo

Antes de pensar em escrever algoritmos recursivos sofisticados, é importante consolidar alguns princípios:

  • Toda recursão precisa obrigatoriamente de um caso base. Sem ele, a pilha cresce indefinidamente até provocar falha de execução.

  • Cada chamada deve aproximar o problema da condição de parada. Se o valor de entrada não evolui em direção ao caso base, o algoritmo nunca termina.

  • Variáveis que representam o estado da execução devem, preferencialmente, estar em LOCAL-STORAGE, evitando interferência entre diferentes ativações.

  • A recursão não substitui o PERFORM. Ela é uma técnica para problemas cuja própria estrutura é hierárquica.

  • Antes de implementar uma solução recursiva, pergunte: este problema realmente possui uma estrutura recursiva ou estou apenas complicando um processamento linear?

Essa última pergunta evita um dos erros mais comuns entre desenvolvedores iniciantes: usar recursão apenas porque ela parece elegante.


Um pequeno exercício para o leitor

Pegue papel e lápis.

Escreva a sequência:

FATORIAL(6)

Agora desenhe seis caixas empilhadas.

Dentro de cada caixa, anote:

  • valor recebido;

  • valor retornado;

  • quem chamou aquela rotina.

Ao final do exercício, você perceberá algo importante: o algoritmo não "anda para frente". Ele mergulha até o caso base e depois retorna desfazendo a pilha, uma camada de cada vez.

Esse simples desenho vale mais do que dezenas de linhas de código para compreender a essência da recursividade.


Encerrando o Café

Se você chegou até aqui, provavelmente percebeu que o exemplo do fatorial nunca foi o verdadeiro objetivo deste artigo.

O fatorial é apenas uma desculpa elegante para observar o funcionamento interno do Enterprise COBOL.

O aprendizado mais valioso não está no resultado 120, mas em entender como o compilador, o Language Environment e a pilha de execução trabalham juntos para permitir que várias instâncias do mesmo programa coexistam de forma segura.

É essa engenharia invisível que torna possível escrever compiladores, interpretadores, analisadores de XML, processadores de JSON e diversas outras soluções sofisticadas em COBOL moderno.

No próximo café, deixaremos os exemplos acadêmicos para trás. Vamos explorar algoritmos realmente interessantes — Fibonacci, percorrimento de árvores, busca em profundidade (DFS), QuickSort, MergeSort, XML, JSON e outros cenários em que a recursividade deixa de ser apenas uma curiosidade e passa a ser a ferramenta mais elegante para resolver problemas complexos no universo IBM Mainframe.


quarta-feira, 25 de janeiro de 2023

COBOL Recursivo — Muito Além do Fatorial: A Engenharia Invisível da Pilha de Execução no IBM Z - Parte I

 

Bellacosa Mainframe apresenta o cobol recursivo parte i

☕ Um Café no Bellacosa Mainframe

COBOL Recursivo — Muito Além do Fatorial: A Engenharia Invisível da Pilha de Execução no IBM Z

"Você provavelmente passará a carreira inteira escrevendo sistemas COBOL sem precisar desenvolver um programa recursivo. Ainda assim, compreender recursividade talvez seja uma das melhores formas de entender como o Enterprise COBOL realmente funciona por dentro."


Introdução

Existe um fenômeno curioso no universo do desenvolvimento Mainframe.

Pergunte para cem programadores COBOL se eles já utilizaram um programa recursivo em produção.

Talvez apenas cinco levantem a mão.

Pergunte agora se eles sabem exatamente como funciona um CALL, como o compilador cria uma nova instância do programa, o que acontece com a Working-Storage, como a pilha de execução (Call Stack) é organizada pelo Language Environment (LE) e por que existe a Local-Storage Section.

Muito provavelmente o número continuará sendo pequeno.

E esse é justamente o paradoxo.

Embora a recursividade seja pouco utilizada nos sistemas corporativos tradicionais, ela revela alguns dos conceitos mais importantes da arquitetura do COBOL moderno.

Ela nos obriga a abandonar a visão simplificada de que um programa é apenas uma sequência de comandos e nos faz enxergar aquilo que realmente está acontecendo:

  • memória;

  • pilha de execução;

  • contexto de chamadas;

  • passagem de parâmetros;

  • gerenciamento de variáveis;

  • criação e destruição de ambientes de execução.

Em outras palavras...

Recursividade não é apenas um algoritmo.

É uma janela para entender como o próprio Enterprise COBOL foi construído.

Prepare seu café.

Hoje vamos abrir a tampa do compilador.


O mito da recursividade em COBOL

Existe uma frase repetida há décadas.

"COBOL não foi feito para recursividade."

Essa afirmação está apenas parcialmente correta.

O COBOL clássico da década de 1960 realmente não possuía suporte adequado para chamadas recursivas.

Naquela época, memória era extremamente cara.

Processadores trabalhavam com poucos kilobytes.

Cada instrução era planejada para consumir o mínimo possível.

O objetivo principal do COBOL era simples:

  • ler registros;

  • processar registros;

  • gravar registros.

Tudo de forma linear.

Imagine um processamento bancário.

READ

↓

VALIDA

↓

CALCULA

↓

UPDATE

↓

WRITE

↓

READ

Milhões de vezes.

Sem árvores.

Sem grafos.

Sem estruturas hierárquicas.

Sem necessidade de chamar o mesmo programa novamente.

A arquitetura inteira do COBOL nasceu voltada para processamento sequencial.


Então por que o COBOL moderno suporta recursividade?

Porque o mundo mudou.

Hoje o Enterprise COBOL conversa diariamente com:

  • XML

  • JSON

  • REST APIs

  • C

  • C++

  • Java

  • Assembler

  • Language Environment

  • Web Services

  • z/OS Connect

  • IBM MQ

E todos esses ambientes trabalham intensamente com estruturas hierárquicas.

Uma árvore XML, por exemplo, é naturalmente recursiva.

<empresa>

    <departamento>

        <funcionario>

            <dependente/>

        </funcionario>

    </departamento>

</empresa>

Cada elemento pode conter outros elementos.

Não existe limite teórico.

A melhor maneira de percorrer isso?

Recursão.


O verdadeiro significado de um programa recursivo

Quando um Padawan ouve falar em recursividade, normalmente pensa:

"É quando um programa chama ele mesmo."

Tecnicamente isso está correto.

Mas essa definição é superficial.

O que realmente acontece é isto:

Cada chamada cria um novo ambiente completo de execução.

Isso muda completamente nossa visão.

Imagine uma rotina simples.

PROCESSA(5)

Ela chama:

PROCESSA(4)

Que chama:

PROCESSA(3)

Que chama:

PROCESSA(2)

Que chama:

PROCESSA(1)

Visualmente:

PROCESSA(5)

↓

PROCESSA(4)

↓

PROCESSA(3)

↓

PROCESSA(2)

↓

PROCESSA(1)

Não existe apenas um programa funcionando.

Existem cinco instâncias simultâneas do mesmo programa.

Cada uma em um estágio diferente da execução.

Esse conceito é fundamental.


A grande mágica acontece na pilha (Stack)

Sempre que uma chamada ocorre, o Language Environment cria um novo frame.

Pense na pilha como uma torre de caixas.

+----------------------+
| PROCESSA(1)          |
+----------------------+
| PROCESSA(2)          |
+----------------------+
| PROCESSA(3)          |
+----------------------+
| PROCESSA(4)          |
+----------------------+
| PROCESSA(5)          |
+----------------------+

Cada caixa contém:

  • parâmetros;

  • registradores;

  • endereço de retorno;

  • variáveis locais;

  • contexto da execução.

Quando PROCESSA(1) termina:

a caixa é removida.

Depois PROCESSA(2).

Depois PROCESSA(3).

Até voltar ao programa original.

Essa estrutura recebe o nome de Call Stack.

Todo programador deveria conhecê-la.

Mesmo que nunca escreva um algoritmo recursivo.


A pilha sempre existiu

Existe outro detalhe curioso.

Mesmo programas totalmente lineares utilizam pilha.

Por exemplo.

Programa A.

CALL B

Programa B.

CALL C

Programa C.

CALL D

Visualmente:

A

↓

B

↓

C

↓

D

O sistema operacional precisa lembrar para onde voltar.

Então cria uma pilha.

+-----------+
| D         |
+-----------+
| C         |
+-----------+
| B         |
+-----------+
| A         |
+-----------+

Perceba algo interessante.

A recursividade apenas repete esse processo.

A diferença é que quem aparece novamente é o mesmo programa.


O compilador não tem medo disso

Muitos imaginam que o compilador "fica confuso".

Na realidade...

Para ele não faz diferença.

Ele apenas cria outra instância.

Depois outra.

Depois outra.

Até encontrar a condição de parada.


O maior perigo da recursividade

Imagine o seguinte pseudocódigo.

PROCESSA

↓

PROCESSA

↓

PROCESSA

↓

PROCESSA

↓

PROCESSA

↓

PROCESSA

E nunca para.

O que acontece?

A pilha cresce.

+----------------+
| PROCESSA       |
+----------------+
| PROCESSA       |
+----------------+
| PROCESSA       |
+----------------+
| PROCESSA       |
+----------------+
| PROCESSA       |
+----------------+
| PROCESSA       |
+----------------+
| PROCESSA       |
+----------------+
| PROCESSA       |
+----------------+

Cada chamada ocupa memória.

Mais cedo ou mais tarde...

Não haverá mais espaço.

Resultado:

Stack Overflow.

Em ambientes z/OS isso normalmente termina em um abend relacionado ao esgotamento da pilha ou da região disponível para a tarefa.

Por isso existe uma regra absoluta.

Toda recursão precisa possuir uma condição de parada.

Sem exceções.


O conceito mais importante para um Padawan

Existe um exercício mental muito útil.

Imagine cinco cópias do mesmo programa.

Não uma.

Cinco.

Cada uma com seus próprios parâmetros.

Cada uma em um ponto diferente da execução.

É exatamente isso que a recursividade produz.

Ela não "volta para o começo".

Ela cria outra execução.

Depois outra.

Depois outra.

Essa mudança de mentalidade faz toda diferença.


RECURSIVE x NON-RECURSIVE

O Enterprise COBOL precisa saber antecipadamente se um programa poderá chamar a si mesmo.

Por quê?

Porque isso muda completamente a estratégia de gerenciamento de memória.

Um programa tradicional pressupõe que existe apenas uma instância ativa.

Já um programa recursivo pode possuir dezenas ou centenas de ativações simultâneas.

É como comparar um apartamento ocupado por uma família com um hotel onde vários hóspedes utilizam quartos iguais ao mesmo tempo.

A estrutura física é parecida.

A forma de administrar os recursos é completamente diferente.


O papel do compilador

Quando um programa é preparado para suportar recursividade, o compilador gera código considerando que cada ativação precisará preservar seu próprio contexto.

Isso inclui:

  • parâmetros recebidos;

  • endereço de retorno;

  • variáveis locais;

  • registradores utilizados;

  • informações necessárias para retomar a execução exatamente do ponto em que foi interrompida.

Essa organização é uma das responsabilidades compartilhadas entre o Enterprise COBOL e o Language Environment (LE).


O maior erro de iniciantes

Existe um erro extremamente comum.

O desenvolvedor escreve:

WORKING-STORAGE SECTION.

01 WS-CONTADOR PIC 9(4).

Depois cria um algoritmo recursivo.

Na primeira chamada:

WS-CONTADOR = 1

Na segunda:

WS-CONTADOR = 2

Na terceira:

WS-CONTADOR = 3

Quando retorna...

O conteúdo foi alterado por outra ativação.

A lógica começa a produzir resultados inesperados.

O motivo?

Todas as ativações estão compartilhando a mesma Working-Storage.

Esse é um dos primeiros conceitos que todo Padawan precisa dominar.


Working-Storage: a memória compartilhada

A Working-Storage Section existe durante praticamente toda a vida do programa.

Ela é excelente para:

  • constantes;

  • tabelas fixas;

  • áreas de trabalho;

  • buffers reutilizados;

  • indicadores globais;

  • variáveis de controle que realmente precisam ser compartilhadas.

Mas ela não foi pensada para armazenar o estado individual de cada chamada recursiva.

Imagine uma lousa em uma sala de reunião.

Todos escrevem na mesma superfície.

O último a apagar ou sobrescrever vence.

É exatamente esse comportamento que pode ocorrer quando um programa recursivo utiliza Working-Storage para guardar informações específicas de cada ativação.


Local-Storage: a grande aliada da recursão

Foi justamente para resolver esse problema que surgiu a Local-Storage Section.

Ao contrário da Working-Storage, ela é criada novamente para cada ativação do programa.

Voltando ao exemplo da pilha:

+----------------------+
| PROCESSA(1)          |
| LOCAL-STORAGE        |
+----------------------+
| PROCESSA(2)          |
| LOCAL-STORAGE        |
+----------------------+
| PROCESSA(3)          |
| LOCAL-STORAGE        |
+----------------------+

Cada chamada possui sua própria cópia das variáveis locais.

Uma alteração realizada em uma ativação não interfere nas demais.

Esse comportamento torna a Local-Storage a escolha natural para programas recursivos e também para rotinas reentrantes e altamente concorrentes.


Uma analogia para nunca mais esquecer

Imagine uma cozinha industrial.

A Working-Storage seria uma única bancada compartilhada por todos os cozinheiros. Se um chef espalha farinha, outro pode encontrar essa farinha antes de começar sua própria receita.

A Local-Storage, por outro lado, funciona como uma bancada individual entregue a cada cozinheiro no início do trabalho. Cada um organiza seus ingredientes, prepara sua receita e, ao terminar, aquela bancada é desmontada sem afetar os demais.

Essa simples analogia ajuda a entender por que tantos problemas em programas recursivos desaparecem quando o estado da execução deixa de ser compartilhado e passa a ser local.


Conclusão da Parte 1

Ao ouvir a palavra recursividade, muitos programadores pensam imediatamente em exercícios acadêmicos como fatorial ou sequência de Fibonacci. No entanto, para quem trabalha com IBM Z, esse é apenas um detalhe.

O verdadeiro valor da recursão está em revelar como o Enterprise COBOL administra memória, organiza a pilha de execução, preserva contextos de chamadas e separa dados compartilhados de dados locais.

Mais do que aprender um algoritmo, estudar programas recursivos significa compreender os bastidores da linguagem — uma compreensão que será útil mesmo em projetos onde nenhuma rotina recursiva seja escrita.

No próximo café, vamos construir um programa recursivo completo em Enterprise COBOL, acompanhar cada chamada passo a passo dentro da pilha de execução, comparar sua execução com uma solução iterativa e descobrir em quais situações a recursão realmente se torna a melhor ferramenta para um desenvolvedor Mainframe.

Na Parte 2, aprofundaremos com código COBOL completo e comentado, execução passo a passo da pilha (stack), exemplos de fatorial, Fibonacci, árvores, XML/JSON, busca em diretórios, algoritmos clássicos (DFS, QuickSort e MergeSort) e diagramas de memória mostrando exatamente o que acontece a cada chamada recursiva.


quinta-feira, 3 de janeiro de 2019

☕💥 A Jornada do Padawan COBOL – Parte 1 Desvendando o Universo dos CALLs no Mainframe

 

Bellacosa Mainframe e o CALL em programas COBOL Parte I

☕💥 A Jornada do Padawan COBOL – Parte 1

Desvendando o Universo dos CALLs no Mainframe

Ou como descobrir que chamar um programa em COBOL é quase tão importante quanto saber preparar café às 3 da manhã durante uma janela de produção

Por Vagner Bellacosa – Bellacosa Mainframe


Introdução

Todo desenvolvedor COBOL passa por um momento de iluminação.

Normalmente acontece quando ele abre um programa de produção com 30 mil linhas e encontra algo parecido com isto:

CALL 'PROG0001'
USING WS-AREA.

CALL WS-PROGRAMA
USING WS-COMMAREA.

CALL 'VALIDA01'
USING BY CONTENT WS-DATA.

CALL 'ROTINA99'
USING BY REFERENCE WS-BLOCO.

Neste momento surge a dúvida existencial:

Por que existem tantos tipos de CALL?

Qual é o mais rápido?

Qual gasta menos memória?

O que acontece dentro do z/OS?

O compilador faz mágica?

O Load Module engorda?

Como um banco executa milhões de CALLs por segundo sem explodir?

Prepare seu café.

Vamos abrir a tampa do motor do z/OS.


O que é um CALL?

Simplificando:

CALL significa pedir ajuda para outro programa.

Em vez de colocar 100 mil linhas em um único fonte, quebramos o sistema em pequenas peças reutilizáveis.

Por exemplo:

Programa principal

PROCESSA-CLIENTES

chama

VALIDA-CPF

CALCULA-JUROS

GERA-BOLETO

ENVIA-MQ

ATUALIZA-DB2

Cada um especializado.

É praticamente microserviços.

Só que inventados em 1960.


Por que IBM fez isso?

Porque memória custava uma fortuna.

Década de 70

Memória podia custar mais que um carro.

Era necessário:

reutilizar código

economizar memória

compartilhar lógica

facilitar manutenção

Assim nasceu o CALL.


Anatomia de um programa COBOL

Um programa COBOL compilado produz:

Objeto

Binder

Load Module

PDs Loadlib

Execução

Exemplo:

SYS1.LOADLIB


PROCESSA
VALIDA
JUROS
CPFCHK

O Padawan descobre o Static CALL

Exemplo

CALL 'VALIDA'

Simples.

Mas o compilador já conhece VALIDA.

Ele avisa o Binder:

Inclua VALIDA aqui.


O que acontece no LinkEdit

Binder faz:

PROCESSA


+

VALIDA


+

CPFCHK


+

JUROS


=

EXECUTÁVEL FINAL

Na memória

Antes:

PROCESSA

Depois:

PROCESSA


VALIDA


CPFCHK


JUROS

Tudo junto.

Tudo carregado.


Vantagens

Performance

Excelente.

Não precisa procurar.

Não precisa abrir bibliotecas.

Não precisa localizar módulo.

É praticamente:

BALR

Menor CPU

Menos instruções.

Menos overhead.

Menos I/O.


Desvantagens

Executável cresce.

Muito.

Exemplo

Programa principal

1 MB

Subrotinas

500 KB

Total

1,5 MB


Imagine 200 subrotinas.

Seu módulo vira um Godzilla.


Problema clássico

Padawan:

"Troquei VALIDA"

Produção:

Ainda usa antiga.

Porque precisa relinkar.


Quando usar?

Sempre que:

Programa nunca muda

Alta performance

Rotina crítica

Batch pesado

Exemplo:

Juros

Cálculo tributário

Validação interna


O Dynamic CALL aparece

Padawan evolui.

Descobre:

01 WS-PGM PIC X(8).

MOVE 'VALIDA' TO WS-PGM.

CALL WS-PGM.

O que acontece?

COBOL não sabe quem será chamado.

Somente em execução.


Busca do módulo

zOS procura:

STEPLIB

JOBLIB

LPA

LINKLIST


Exemplo

CALL CPFCHK

Sistema:

Existe?

Não.

Próximo.

Existe?

Sim.

Carrega.

Executa.


Vantagens

Flexibilidade absurda.

Pode trocar programas.

Sem recompilar.

Sem binder.

Sem relink.


Plugins COBOL

Exemplo

Cartão VISA

MOVE 'VISA0001'

Master

MOVE 'MASTER01'

PIX

MOVE 'PIX00001'

Mesmo sistema.

Rotinas diferentes.


Desvantagens

Procura programa.

Mais CPU.

Mais I/O.

Mais tempo.


Mas é lento?

Depende.

Primeira chamada.

Sim.

Segunda.

Muito rápida.

Porque pode ficar residente.


O segredo da residência

zOS é esperto.

Se programa está em memória.

Reutiliza.

Não busca novamente.


Comparação

Static

Casa própria

Dynamic

Airbnb

O executável cresce?

Static

Sim.

Dynamic

Não.

Executável principal fica pequeno.


Exemplo

Static

PROCESSA

1.5 MB

Dynamic

PROCESSA

600 KB

Subrotinas externas.


O que é mais performático?

Resposta curta.

Static.

Fim.

Mas...


O que é melhor?

Depende.

Banco.

Static.

Framework.

Dynamic.

Produtos.

Dynamic.

Rotinas financeiras.

Static.


Como o CALL funciona internamente?

Imagine isto.

Programa principal

00001000
PROCESSA

Subrotina

00025000
VALIDA

CALL executa

Guardar endereço retorno


Ir para 25000


Executar


Voltar

É praticamente um GOTO sofisticado.

Só que elegante.


Erros clássicos

S806

Programa não encontrado.

Mensagem

IEC806I

Causa

Módulo ausente.

STEPLIB errada.

Nome inválido.


S0C1

Executou lixo.

Programa corrompido.


S0C4

Endereço inválido.

Muito comum em parâmetros errados.


Como descobrir

SDSF

JESMSGLG

SYSOUT


Verificar:

STEPLIB

SYSLIB

LOADLIB


Easter Egg IBM

Muitos bancos possuem:

PROG0001

PROG0002

PROG0003

Ninguém sabe o que fazem.

Autor aposentou em 1998.

Documentação desapareceu.

Programa continua funcionando.

Todos têm medo de alterar.

É chamado:

Código Arqueológico Mainframe™


Dicas Bellacosa Mainframe

Dica 1

Static para alta frequência.


Dica 2

Dynamic para produtos.


Dica 3

Nunca fazer:

MOVE WS-USUARIO TO WS-PGM

CALL WS-PGM

Sem validar.

Pode chamar qualquer coisa.

Inclusive algo inexistente.


Dica 4

Validar sempre.

EVALUATE WS-PGM

WHEN 'CPFCHK'

WHEN 'JUROS01'

WHEN 'PIX0001'

WHEN OTHER

DISPLAY 'INVALIDO'

END-EVALUATE

Dica 5

Logar chamadas.

DISPLAY 'CALL=' WS-PGM

Ajuda muito.


A Filosofia Jedi do CALL

O Padawan iniciante pensa:

CALL serve apenas para executar outro programa.

O desenvolvedor intermediário pensa:

CALL serve para reutilizar código.

O Mestre Mainframe entende:

CALL é uma decisão arquitetural.

Ele impacta:

  • CPU

  • Memória

  • Tempo de resposta

  • Tamanho do Load Module

  • Facilidade de manutenção

  • Segurança

  • Escalabilidade

  • Observabilidade

  • Estratégia de deploy

E é exatamente por isso que, cinquenta anos depois, os sistemas bancários que movimentam bilhões de dólares diariamente ainda utilizam a mesma instrução COBOL que um programador digitou em um terminal verde na década de 1970:

CALL 'SUBPGM'

Na próxima etapa da jornada, o Padawan descobrirá que o verdadeiro poder do COBOL não está apenas em chamar programas, mas em como os dados atravessam a fronteira entre eles, mergulhando nos mistérios de BY REFERENCE, BY CONTENT, BY VALUE, ponteiros, Work-Storage, Local-Storage e Language Environment, onde vivem os temidos S0C4, os buffers compartilhados e os segredos que fazem alguns programas parecerem mágicos aos olhos dos desenvolvedores mais jovens.

Vagner Renato Bellacosa, IBM Champion e especialista em IBM Mainframe
IBM Z17 SYSTEM ONLINE
Sobre o autor

Vagner Renato Bellacosa

IBM Champion 2026 • Especialista em IBM Mainframe

Vagner Renato Bellacosa trabalha com IBM Mainframe desde 1988 , é IBM Champion e especialista em COBOL, CICS, Db2, z/OS e IBM Z. Compartilha experiências profissionais, conhecimento técnico, história da computação e práticas do universo mainframe para aproximar novas gerações das tecnologias que sustentam empresas, bancos e governos ao redor do mundo.

IBM Champion IBM Z COBOL CICS Db2 z/OS Mainframe desde 1988