Translate

Mostrar mensagens com a etiqueta WORKING-STORAGE. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta WORKING-STORAGE. Mostrar todas as mensagens

segunda-feira, 22 de janeiro de 2024

COBOL Recursivo: Quando o Padawan Descobre que um Programa Pode Invocar a Si Mesmo

 

Bellacosa Mainframe apresenta um programa cobol recursivo

COBOL Recursivo: Quando o Padawan Descobre que um Programa Pode Invocar a Si Mesmo

O Poder Proibido do CALL Infinito na Galáxia IBM Z

Por Bellacosa Mainframe

"O medo leva ao ABEND. O ABEND leva ao dump. O dump leva ao sofrimento."

— Mestre Sysprog Yoda/390

Durante muitos anos, programadores COBOL cresceram acreditando em uma verdade absoluta:

"Programa COBOL não faz recursão."

Era praticamente um dogma do templo Jedi dos mainframes.

E, honestamente?

Durante décadas isso foi quase verdade.

A maioria dos sistemas bancários, seguradoras, governo e processamento batch nunca precisou de algoritmos recursivos.

Mas então surgiram:

  • XML

  • JSON

  • árvores hierárquicas

  • parsers

  • estruturas dinâmicas

  • APIs modernas

  • integração com Java

  • serviços z/OS Connect

E um dia o jovem Padawan perguntou:

"Mestre Bellacosa...

Posso fazer um programa COBOL chamar ele mesmo?"

A resposta é:

Sim.

Mas como todo poder da Força, existe um preço.


O que é Recursividade?

Recursão significa:

Um procedimento invoca ele próprio.

Exemplo clássico:

Calcular fatorial.

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

Matematicamente:

Fatorial(n)

se n=1 retorna 1

senão

n * Fatorial(n-1)

Exemplo:

F(5)

5 × F(4)

5 × 4 × F(3)

5 × 4 × 3 × F(2)

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

120

É uma técnica extremamente elegante.


COBOL Sempre Teve Recursão?

Não.

Historicamente COBOL nasceu em 1959.

E sua filosofia era:

Processamento comercial.

Arquivos.

Registros.

Batch.

Volumes enormes.

Não havia necessidade prática.

Durante décadas COBOL assumia:

Programa residente

Uma única Working Storage

Compartilhada

Exemplo:

WORKING-STORAGE SECTION.

01 CONTADOR PIC 9(5).

Só existe uma instância.

Todo mundo usa a mesma.

Problema:

Programa chama ele mesmo.

Segunda chamada sobrescreve variáveis.

Primeira execução quebra.

Caos.


Quando Surgiu?

IBM Enterprise COBOL começou suportar adequadamente recursão através da opção:

RECURSIVE

ou

RENT

Dependendo da geração do compilador.

Hoje encontramos suporte em:

Enterprise COBOL 4

Enterprise COBOL 5

Enterprise COBOL 6.x

COBOL 6.3

COBOL 6.4

COBOL 6.5

Totalmente suportado.

Inclusive no IBM z17.


Como Declarar um Programa Recursivo

No cabeçalho:

IDENTIFICATION DIVISION.
PROGRAM-ID. FATORIAL RECURSIVE.

ou

PROGRAM-ID. FATORIAL.

RECURSIVE.

Depende do compilador.


Exemplo Completo

Passo 1

Definir LINKAGE

LINKAGE SECTION.


01 LK-NUMERO.
   PIC 9(4).

01 LK-RESULTADO.
   PIC 9(10).

Passo 2

Procedure Division

PROCEDURE DIVISION USING
          LK-NUMERO
          LK-RESULTADO.

Passo 3

Caso base

IF LK-NUMERO <= 1

   MOVE 1 TO LK-RESULTADO

ELSE

...

Passo 4

Criar variável local

LOCAL-STORAGE SECTION.


01 WS-TEMP.
   PIC 9(10).

01 WS-N.
   PIC 9(4).

Local Storage é fundamental.

Já veremos por quê.


Passo 5

Preparar próxima chamada

COMPUTE WS-N = LK-NUMERO -1

Passo 6

Invocar a si mesmo

CALL 'FATORIAL'

USING

WS-N
WS-TEMP

Passo 7

Calcular

COMPUTE LK-RESULTADO =
        LK-NUMERO * WS-TEMP

Fim.


Programa Completo

IDENTIFICATION DIVISION.
PROGRAM-ID. FATORIAL RECURSIVE.

DATA DIVISION.


LOCAL-STORAGE SECTION.


01 WS-N.
   PIC 9(4).

01 WS-TEMP.
   PIC 9(10).


LINKAGE SECTION.


01 LK-NUMERO.
   PIC 9(4).

01 LK-RESULTADO.
   PIC 9(10).


PROCEDURE DIVISION USING
        LK-NUMERO
        LK-RESULTADO.


IF LK-NUMERO <= 1

   MOVE 1 TO LK-RESULTADO

ELSE

   COMPUTE WS-N =
           LK-NUMERO -1


   CALL 'FATORIAL'

   USING

      WS-N
      WS-TEMP


   COMPUTE LK-RESULTADO =
           LK-NUMERO * WS-TEMP

END-IF.


GOBACK.

O que Acontece na Memória?

Aqui mora a magia.

Imagine:

FATORIAL(5)

Stack:

Frame 1
n=5

Chama:

Frame 2
n=4

Depois:

Frame 3
n=3

Depois:

Frame 4
n=2

Depois:

Frame 5
n=1

Retorno:

Frame 5 → 1

Frame 4 → 2

Frame 3 → 6

Frame 2 →24

Frame 1 →120


Visualmente

STACK



F(5)

F(4)

F(3)

F(2)

F(1)

Depois:

POP


F(1)


F(2)


F(3)


F(4)


F(5)

O Papel da LOCAL-STORAGE

Muitos Padawans cometem um erro fatal.

Usar:

WORKING-STORAGE

Exemplo:

01 WS-N.

Errado.

Por quê?

Porque Working Storage é única.

Compartilhada.

Exemplo:

Primeira chamada

WS-N=5

Segunda

WS-N=4

Terceira

WS-N=3

Sobrescreve.

Tudo quebra.


LOCAL-STORAGE

Cria uma cópia nova.

Para cada chamada.

Exemplo:

Frame 1

WS-N=5

Frame 2

WS-N=4

Frame 3

WS-N=3

Independentes.


Como o Compilador Faz Isso?

Ele cria activation records.

Também chamados:

Stack frames

Contém:

Variáveis locais

Endereço retorno

Parâmetros

Estado execução

Exatamente igual:

C

C++

Java

Pascal


CALL Estático

Mais eficiente.

CALL 'FATORIAL'

Resolvido no linkedit.

Menos overhead.


CALL Dinâmico

MOVE 'FATORIAL'

TO WS-PGM


CALL WS-PGM

Mais flexível.

Menos rápido.


Existe Limite?

Sim.

STACK.

Região LE.

Memory limits.

Exemplo:

F(1000000)

Boom.

SOC1

SOC4

S878

S80A

Dependendo ambiente.


Como Evitar?

Caso base.

Sempre.

Sempre.

Sempre.

Exemplo ruim:

CALL FATORIAL


CALL FATORIAL


CALL FATORIAL

Nunca termina.


Cuidados Importantes

1 Não usar Working Storage

Errado.


2 Ter condição parada

Obrigatório.


3 Validar entrada

Exemplo:

IF NUMERO < 0

Fatorial negativo?

Não faz sentido.


4 Cuidado com profundidade

100 níveis

Ok.

500

Talvez.

10000

Perigoso.


5 Testar LE Runtime

HEAP

STACK

ALL31

Importante.


Performance

Aqui muitos ficam decepcionados.

Recursão é elegante.

Mas nem sempre rápida.

Cada chamada cria:

Frame

Parâmetros

Contexto

Retorno

Overhead.


Loop tradicional:

PERFORM


UNTIL

Quase sempre vence.


Exemplo

Fatorial iterativo.

MOVE 1 TO WS-TOTAL


PERFORM VARYING I


FROM 1


BY 1


UNTIL I > N


MULTIPLY I


BY WS-TOTAL


END-PERFORM

Muito eficiente.


Quando Vale a Pena?

Árvores.

XML.

JSON.

DOM.

Grafos.

Estruturas hierárquicas.

Menus.

Organogramas.

Filesystem.

Dependências.


Exemplo XML

empresa


 departamento


   funcionario


 departamento


   funcionario

Percorrer árvore.

Recursão fica linda.


Segurança

Sim.

Existe aspecto de segurança.

Ataque:

Stack exhaustion.

Negação de serviço.

DoS.

Programa recebe:

99999999

Explode stack.


Proteção:

IF NIVEL > 1000

DISPLAY "ERRO"

STOP RUN

Debug

Debug recursivo é divertido.

E assustador.

Ferramentas:

IBM Debug Tool

Fault Analyzer

Abend Aid

Mostram:

Call Stack

Frame atual

Parâmetros

Muito útil.


Curiosidades

Poucos sistemas bancários usam recursão pesada.

COBOL nasceu para processamento sequencial.

Por isso muitos programadores veteranos passam décadas sem escrever uma única rotina recursiva.


Outra curiosidade:

COBOL OO suporta métodos recursivos naturalmente.

INVOKE SELF

Também funciona.


Recursão de Cauda (Tail Recursion)

Alguns compiladores modernos fazem otimizações.

Mas COBOL IBM geralmente não realiza Tail Call Optimization de forma agressiva como linguagens funcionais.

Logo:

RETURN F(N-1)

Ainda pode consumir stack.

Não confie nisso.


Padrão Recomendado pelo Mestre Bellacosa

Para o jovem Padawan COBOL, eu costumo ensinar uma regra bastante prática:

Use recursão apenas quando ela deixar a solução mais clara do que um PERFORM VARYING.

Se a lógica for:

  • Fatorial

  • Somatório

  • Contador

Prefira iteração.

Se a lógica envolver:

  • Árvores

  • XML

  • JSON aninhado

  • Catálogos IMS

  • Dependências complexas

  • Estruturas organizacionais

  • Menus multiníveis

Então a recursividade torna-se uma excelente aliada.


Considerações Finais

A recursão em COBOL é quase como encontrar um antigo holocron escondido nos corredores de um datacenter IBM Z.

Muitos veteranos nunca precisaram utilizá-la. Outros a evitam por receio de consumir stack, degradar performance ou provocar abends difíceis de diagnosticar.

No entanto, compreender programas recursivos amplia significativamente o repertório técnico de um desenvolvedor COBOL moderno. Em um mundo onde o mainframe conversa diariamente com APIs REST, processa documentos JSON complexos, integra-se com microsserviços e participa de arquiteturas híbridas, dominar recursividade deixa de ser apenas curiosidade acadêmica e passa a ser uma habilidade estratégica.

O verdadeiro Padawan não utiliza recursão para demonstrar inteligência. Ele a utiliza porque compreende profundamente o problema, conhece os limites da memória, respeita a pilha de execução do Language Environment, escolhe LOCAL-STORAGE sabiamente e sabe exatamente quando um simples PERFORM VARYING é a solução mais elegante.

E essa talvez seja a maior lição do COBOL recursivo:

Nem todo poder disponível deve ser usado. Mas todo poder disponível deve ser compreendido.

No templo Bellacosa Mainframe, conhecer a recursividade significa possuir mais uma ferramenta no cinto utilitário do Sysprog Jedi, pronta para ser acionada quando a missão exigir atravessar estruturas tão profundas quanto os corredores infinitos de uma galáxia IBM Z em plena era do z17.


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.


sexta-feira, 21 de janeiro de 2022

COBOL : Muito Além do PIC S9(7)V99 COMP-3

 

Bellacosa Mainframe analisa as variaveis estilo cobol

☕ Um Café no Bellacosa Mainframe

Muito Além do PIC S9(7)V99 COMP-3

O Que Todo Programador COBOL Padawan Precisa Saber Sobre a Anatomia de uma Variável, Como o IBM Z Enxerga Seus Dados e Por Que Essa Sintaxe Continua Dominando o Mundo Financeiro

"Qual é a variável mais importante do COBOL?"

A maioria dos programadores iniciantes responde algo como:

"WS-NOME."

Ou talvez:

"WS-SALARIO."

Mas essa pergunta possui uma resposta muito mais interessante.

A variável mais importante do COBOL não é uma variável específica.

É o conceito por trás de todas elas.

Uma simples declaração como esta:

05 WS-MONTANTE PIC S9(7)V99 COMP-3.

parece pequena.

São apenas alguns caracteres.

Entretanto, essa única linha descreve muito mais informações do que praticamente qualquer linguagem moderna consegue expressar de forma tão compacta.

Ela informa ao compilador:

  • onde o dado pertence;

  • qual seu significado;

  • como será armazenado;

  • como será processado pela CPU;

  • quanto espaço ocupará;

  • se aceita números negativos;

  • onde está a posição decimal;

  • como será gravado em disco;

  • como será enviado ao DB2;

  • como participará das operações matemáticas.

Em outras palavras...

Ela descreve completamente a vida daquele dado.

Hoje vamos abrir essa declaração como um engenheiro desmonta um motor Rolls-Royce.

Pegue seu café.

Vamos viajar para dentro da memória do IBM Z.


Bellacosa Mainframe e a anatomia de uma variavel

COBOL pensa diferente

Quem vem de Java, Python, C# ou JavaScript costuma enxergar uma variável assim:

double salario;

ou

salario = 1500.50

Pronto.

Acabou.

Mas o COBOL nasceu em uma época completamente diferente.

Na década de 60 memória era absurdamente cara.

Processadores eram lentos.

Discos eram pequenos.

Cada byte economizado representava milhares de dólares.

Por isso o COBOL não pergunta apenas:

"Que tipo de variável é essa?"

Ele pergunta:

"Como exatamente esse dado existirá dentro do computador?"

Essa filosofia mudou toda a arquitetura da linguagem.


Primeiro elemento: o nível

05 WS-MONTANTE ...

O número 05 não possui absolutamente nenhuma relação com tamanho.

Ele representa hierarquia.

Imagine um organograma de uma empresa.

Empresa

    Financeiro

        Contabilidade

            Funcionários

O COBOL organiza dados exatamente assim.

Exemplo:

01 CLIENTE.

   05 DADOS-PESSOAIS.

      10 NOME.

      10 CPF.

   05 DADOS-FINANCEIROS.

      10 LIMITE.

      10 SALDO.

Visualmente:

CLIENTE
│
├── DADOS-PESSOAIS
│      ├── NOME
│      └── CPF
│
└── DADOS-FINANCEIROS
       ├── LIMITE
       └── SALDO

Percebe?

O COBOL constrói verdadeiras árvores de dados.

É uma linguagem extremamente orientada à estrutura.

Muito antes de XML.

Muito antes de JSON.

Muito antes de objetos.


O nome importa muito

Observe:

WS-MONTANTE

Isso não é apenas um identificador.

É documentação.

Durante décadas milhares de desenvolvedores trabalharam simultaneamente no mesmo sistema.

Ninguém tinha IDE colorida.

Não existia autocomplete.

Então surgiram convenções.

Por exemplo:

WS-

significa:

Working-Storage

Já:

LS-

é Local Storage.

LK-

Linkage.

SQL-

variáveis utilizadas pelo DB2.

DFH-

estruturas do CICS.

Quando você vê um nome começando por WS-, imediatamente sabe onde aquela variável foi declarada.

Isso aumenta muito a legibilidade.


O verdadeiro coração da declaração

Chegamos ao famoso:

PIC

PIC significa:

Picture

Muitos imaginam que ele define apenas o tamanho.

Não.

Ele descreve a forma como o dado será representado.

É quase um DNA.

Vamos ver alguns exemplos.

Texto:

PIC X(30)

Trinta caracteres.

Somente letras:

PIC A(20)

Numérico:

PIC 9(8)

Numérico com casas decimais:

PIC 9(7)V99

Editado para impressão:

PIC $$$,$$9.99

Cada Picture possui uma finalidade diferente.


O significado do "9"

Na nossa variável aparece:

9(7)

O nove significa:

um dígito decimal.

Logo:

9

aceita:

0 até 9

Enquanto

99

aceita

00 até 99

9(7)

é apenas uma forma elegante de escrever:

9999999

Ou seja:

sete dígitos.

Nada mais.

Nada menos.


O misterioso S

Agora observe:

S9(7)

O "S" significa:

Signed.

Número com sinal.

Sem ele:

0
até

9999999

Com ele:

-9999999

até

+9999999

Mas aqui existe uma curiosidade interessante.

O sinal normalmente não ocupa um byte inteiro.

No formato COMP-3 ele é armazenado dentro do último nibble do número.

Uma solução extremamente elegante criada quando memória era um recurso precioso.


A genialidade da vírgula invisível

Agora chegamos ao personagem mais incompreendido do COBOL.

A letra:

V

A maioria dos iniciantes acredita que ela representa uma vírgula.

Na realidade...

Ela representa a posição lógica da vírgula.

A vírgula nunca é gravada.

Nunca.

Suponha:

PIC 9(5)V99

Na memória teremos:

1234567

Mas o COBOL interpreta como:

12345,67

Onde foi parar a vírgula?

Ela nunca existiu.

O compilador apenas sabe que, ao interpretar aquele número, deve considerar duas casas decimais.

Parece um detalhe.

Mas imagine milhões de registros.

Economizar um byte em cada registro significava reduzir discos inteiros.


A importância da precisão

Vamos comparar.

Python:

0.1 + 0.2

Resultado:

0.30000000000000004

Não é erro do Python.

É matemática binária.

Agora veja o COBOL usando Packed Decimal.

0.10

+

0.20

=

0.30

Sempre.

Sem aproximações.

Sem surpresas.

Sem arredondamentos inesperados.

É exatamente por isso que bancos utilizam COMP-3 há décadas.

Dinheiro exige precisão absoluta.


O que é COMP-3?

Agora chegamos ao astro da declaração.

COMP-3

Também conhecido como:

Packed Decimal.

BCD.

Decimal Empacotado.

Ao contrário do DISPLAY, onde cada dígito ocupa um byte inteiro, o COMP-3 armazena dois dígitos por byte.

Cada dígito utiliza apenas quatro bits.

Ou seja:

12

cabe em um único byte.

Muito mais eficiente.


Como o número fica na memória?

Suponha:

123456789

Em DISPLAY:

F1 F2 F3 F4 F5 F6 F7 F8 F9

Nove bytes.

No COMP-3:

12 34 56 78 9C

Cinco bytes.

O "C" representa positivo.

Se fosse negativo:

9D

Essa codificação ainda hoje é utilizada por praticamente todos os grandes bancos do planeta.


Quantos bytes essa variável ocupa?

Existe uma fórmula famosa.

(dígitos + 2) / 2

Arredondando para cima.

Nossa variável possui:

7

+

2

=

9 dígitos

Logo:

(9 + 2) / 2

=

5 bytes

Apenas cinco bytes.


Parece pouco?

Imagine um cadastro contendo:

300 milhões

de clientes.

Economizar quatro bytes por registro significa aproximadamente:

1,2 GB

Apenas em um campo.

Agora multiplique isso por centenas de campos.

Depois por milhares de tabelas.

Depois por backups.

Depois por replicações.

Agora você entende por que arquitetos de mainframe pensam diferente.


O compilador faz muito mais do que imaginamos

Quando escrevemos:

05 WS-MONTANTE PIC S9(7)V99 COMP-3.

Nós enxergamos apenas texto.

O compilador enxerga uma enorme quantidade de metadados.

Ele sabe:

✔ É numérico.

✔ É decimal.

✔ Possui sinal.

✔ Tem duas casas decimais.

✔ Ocupa cinco bytes.

✔ Pode participar de operações aritméticas.

✔ Será armazenado em Packed Decimal.

✔ É compatível com DECIMAL do DB2.

✔ Pode ser usado em comandos ADD, SUBTRACT, MULTIPLY e COMPUTE.

Tudo isso antes mesmo do programa começar a executar.


Por que DB2 gosta tanto de COMP-3?

Observe esta coluna SQL.

SALDO DECIMAL(9,2)

Em COBOL normalmente teremos:

PIC S9(7)V99 COMP-3

Os dois formatos representam exatamente o mesmo conceito.

Resultado?

Conversões mínimas.

Mais velocidade.

Menor consumo de CPU.

Menor custo operacional.


VSAM, IMS e CICS também entendem esse formato

Quando um programa COBOL grava um registro VSAM contendo COMP-3, os números são gravados exatamente nesse formato empacotado.

Quando outro programa COBOL lê esse registro, nenhuma conversão é necessária.

Tudo continua extremamente eficiente.

Por isso sistemas desenvolvidos há quarenta anos continuam rápidos até hoje.


O preço dessa eficiência

Existe um lado curioso.

Abra um arquivo contendo COMP-3 em um editor hexadecimal.

Você verá algo parecido com:

12 34 56 7C

Não parece um número.

Porque não é texto.

É uma representação física otimizada.

Por isso ferramentas modernas precisam converter esses bytes antes de exibi-los.


E quando falamos em APIs REST?

Hoje muitas aplicações COBOL conversam com Java, Python, Node.js e microsserviços.

Entretanto JSON não entende Packed Decimal.

Então ocorre uma conversão.

Internamente:

12 34 56 7C

Externamente:

{
  "saldo": 12345.67
}

É exatamente esse trabalho que tecnologias como IBM z/OS Connect, CICS Web Services e diversos frameworks de integração realizam.

Eles traduzem o universo do IBM Z para um formato compreendido pelas aplicações modernas, preservando a precisão decimal do COBOL.


Uma lição para o Programador Padawan

Talvez você esteja pensando:

"Tudo isso para declarar uma variável?"

Exatamente.

Essa é a beleza do COBOL.

Em linguagens modernas, muitas decisões ficam escondidas dentro da máquina virtual, do compilador ou da biblioteca de execução.

No COBOL, elas são explícitas.

O programador descreve com precisão quase cirúrgica como seus dados devem existir.

Essa filosofia nasceu da necessidade, mas transformou-se em uma das maiores virtudes da linguagem.

Não é por acaso que os maiores bancos, seguradoras, bolsas de valores e órgãos governamentais do mundo continuam confiando no IBM Z para processar trilhões de dólares diariamente.

Quando um PIX é liquidado, um cartão é autorizado, um financiamento é calculado ou uma aposentadoria é paga, há uma grande chance de que, em algum ponto dessa jornada, exista uma variável muito parecida com a nossa velha conhecida:

05 WS-MONTANTE PIC S9(7)V99 COMP-3.

Ela pode parecer simples.

Mas dentro dela vivem mais de seis décadas de engenharia, otimização, compatibilidade e confiabilidade.

E talvez essa seja a maior lição para todo Programador Padawan: no universo do Mainframe, uma linha de código raramente faz apenas uma coisa. Cada declaração carrega décadas de experiência acumulada, decisões arquiteturais cuidadosamente pensadas e um compromisso absoluto com precisão, desempenho e estabilidade. Aprender a "ler" uma variável COBOL é aprender a enxergar a lógica invisível que sustenta alguns dos sistemas mais críticos do planeta. Quando você dominar essa linguagem dos dados, deixará de apenas escrever programas e passará a compreender como o IBM Z realmente pensa.

segunda-feira, 18 de outubro de 2021

Working-Storage vs Local-Storage em COBOL: Muito Além do Static e Dynamic — A Engenharia Invisível que Mantém Milhões de Transações Funcionando

 

Bellacosa Mainframe evoluindo o data division trabalhando com o working-storage vrs local-storage

☕ Um Café no Bellacosa Mainframe

Working-Storage vs Local-Storage em COBOL: Muito Além do Static e Dynamic — A Engenharia Invisível que Mantém Milhões de Transações Funcionando

"Todo programador COBOL aprende cedo que existe a Working-Storage Section e a Local-Storage Section. Quase todos decoram que uma é 'estática' e a outra é 'dinâmica'. Poucos entendem que essa diferença define como um programa se comporta quando milhares de pessoas o executam simultaneamente em um IBM Z."

Existe um momento curioso na vida de todo desenvolvedor COBOL.

Logo nas primeiras aulas aparece alguém dizendo:

"Working-Storage é memória estática."

Logo depois vem outra frase:

"Local-Storage é memória dinâmica."

O aluno anota.

Decora.

Passa na prova.

Continua programando.

Anos depois ainda acredita que toda a história termina aí.

Mas não termina.

Na verdade, essa é apenas a primeira página de um assunto que influencia desempenho, escalabilidade, consumo de memória, concorrência, reentrância e até mesmo a disponibilidade dos maiores sistemas bancários do planeta.

Hoje vamos tomar um café e conversar sobre algo que acontece silenciosamente dentro do IBM Z toda vez que um programa COBOL começa a executar.

Vamos falar sobre memória.

Mas não qualquer memória.

Vamos falar da memória que ninguém vê.

Aquela que decide se um programa será elegante ou problemático.

Aquela que pode suportar dez usuários... ou cem mil.


O que realmente acontece quando um programa COBOL inicia?

Imagine que um programa COBOL seja um restaurante.

O código-fonte representa o projeto arquitetônico.

As instruções são as receitas.

Os parágrafos são os cozinheiros.

Mas falta algo essencial.

Onde ficam os ingredientes?

Eles precisam ser armazenados em algum lugar.

É exatamente esse o papel das áreas de dados.

Quando o programa é carregado, o sistema operacional reserva espaços de memória para armazenar todas as variáveis declaradas.

Essas áreas são cuidadosamente organizadas.

Algumas existirão durante toda a vida do programa.

Outras nascerão e morrerão constantemente.

É justamente aqui que entram a Working-Storage e a Local-Storage.


Working-Storage: a grande despensa do restaurante

Imagine a despensa principal de um restaurante.

Ela foi montada antes da abertura.

Os armários já estão organizados.

Os ingredientes estão separados.

Os utensílios permanecem sempre nos mesmos lugares.

Nada disso precisa ser montado toda vez que um cliente chega.

É exatamente essa a filosofia da Working-Storage.

WORKING-STORAGE SECTION.

01 WS-CLIENTE.
   05 WS-NOME        PIC X(40).
   05 WS-CONTA       PIC 9(10).

01 WS-SALDO          PIC S9(11)V99 COMP-3.

01 WS-CONTADOR       PIC 9(6).

Quando o programa é carregado pelo z/OS, essa área recebe memória.

A partir desse instante ela permanece disponível.

Não importa quantos PERFORM sejam executados.

Não importa quantos IF existam.

Não importa quantas SECTIONS sejam chamadas.

As variáveis continuam existindo.

O endereço de memória continua sendo o mesmo.

Isso torna o acesso extremamente rápido.


O verdadeiro significado de "estático"

Muitos iniciantes imaginam que "estático" significa "imutável".

Não.

Static não significa que o valor nunca muda.

Significa que o espaço de memória permanece o mesmo.

Observe:

MOVE 100 TO WS-TOTAL.

Depois:

ADD 50 TO WS-TOTAL.

Depois:

SUBTRACT 20 FROM WS-TOTAL.

O conteúdo mudou diversas vezes.

Mas o endereço da variável permaneceu exatamente igual.

É como uma gaveta.

Você pode trocar o que está dentro dela.

Mas continua sendo a mesma gaveta.


O custo invisível da criação de memória

Toda vez que um sistema precisa criar memória, ele realiza diversas operações internas.

Ele precisa:

  • localizar espaço livre;

  • reservar esse espaço;

  • inicializar a região;

  • atualizar tabelas internas;

  • controlar proteção;

  • posteriormente liberar aquela memória.

Tudo isso consome CPU.

A Working-Storage evita esse trabalho.

Ela nasce uma vez.

E permanece viva.

Por isso é extremamente eficiente para dados permanentes durante a execução.


Onde ela é usada?

Praticamente em todos os programas Batch.

Imagine um processamento de folha de pagamento.

Durante horas o programa percorre milhões de registros.

Ele precisa manter:

  • total de salários;

  • quantidade de funcionários;

  • número do lote;

  • estatísticas;

  • indicadores;

  • buffers de leitura;

  • tabelas carregadas em memória.

Nenhum desses dados precisa ser recriado continuamente.

Logo, Working-Storage é perfeita.


Agora imagine outro cenário...

Você desenvolveu um programa chamado CALCULA-IMPOSTO.

Ele é chamado por:

  • Internet Banking;

  • Aplicativo Mobile;

  • Caixa eletrônico;

  • Central de Atendimento;

  • PIX;

  • TED;

  • DOC;

  • Sistema interno do banco.

Milhares de chamadas por segundo.

Todos executando exatamente o mesmo programa.

Agora surge uma pergunta interessante.

Como impedir que um cliente altere os dados do outro?


Bem-vindo ao mundo da Local-Storage

É aqui que entra a Local-Storage Section.

LOCAL-STORAGE SECTION.

01 LS-TOTAL-IMPOSTO PIC S9(9)V99.

Essa variável possui um comportamento completamente diferente.

Ela não existe durante toda a vida do programa.

Ela nasce.

É utilizada.

E desaparece.

Cada nova ativação do programa recebe uma nova cópia.

Não existe compartilhamento.

É como um bloco de anotações entregue individualmente para cada cliente que entra no restaurante.

Quando o cliente vai embora...

O bloco é descartado.

O próximo recebe outro completamente limpo.


A diferença parece pequena...

...mas muda tudo.

Imagine três clientes acessando simultaneamente um sistema bancário.

Cliente A:

Saldo:

R$ 5.000

Cliente B:

Saldo:

R$ 18.000

Cliente C:

Saldo:

R$ 730

Se todos utilizassem exatamente a mesma variável para armazenar o saldo temporário...

Teríamos um desastre.

Enquanto um cliente calcula juros...

Outro altera o saldo.

Enquanto outro faz um PIX...

A variável já contém informações diferentes.

Resultado?

Corrupção de memória.

Valores inconsistentes.

Falhas imprevisíveis.

Esse tipo de problema recebe até um nome em computação:

Race Condition.


O IBM Z foi construído para evitar exatamente isso

O mainframe nasceu para executar milhares de tarefas simultaneamente.

Ele não foi criado pensando em um único usuário.

Foi projetado para atender cidades inteiras.

Bancos.

Companhias aéreas.

Seguradoras.

Governos.

Empresas de cartão.

Operadoras de saúde.

Enquanto você lê este artigo, existem programas COBOL sendo executados milhares de vezes por segundo.

Muitas vezes o mesmo módulo está atendendo centenas de usuários ao mesmo tempo.

Como isso é possível?

Porque o código é compartilhado.

Mas os dados não.


Código compartilhado

Imagine um livro.

Mil pessoas podem ler exatamente o mesmo livro.

O livro não muda.

Agora imagine que todas escrevessem anotações na mesma página.

Seria um caos.

É exatamente essa diferença.

O código pode ser compartilhado.

Os dados precisam ser privados.


Reentrância: um conceito muito maior que Local-Storage

Aqui chegamos a uma palavra que todo programador COBOL deveria conhecer.

Reentrant.

Um programa reentrante pode ser executado simultaneamente por inúmeras tarefas.

Existe apenas uma cópia do código.

Mas cada execução possui sua própria área de dados.

Isso reduz consumo de memória.

Melhora desempenho.

Aumenta escalabilidade.

Permite milhares de transações concorrentes.

Em ambientes CICS, isso é praticamente obrigatório.


O compilador RENT

Quem já compilou programas Enterprise COBOL provavelmente encontrou a opção:

RENT

Muitos imaginam que ela apenas altera um parâmetro de compilação.

Na verdade, ela muda toda a filosofia de utilização da memória.

O compilador passa a gerar código preparado para múltiplas execuções concorrentes.

Isso exige disciplina.

Variáveis compartilhadas precisam ser evitadas.

Dados específicos da execução devem permanecer em áreas privadas.

É justamente aqui que Local-Storage ganha enorme importância.


Batch e CICS não vivem exatamente no mesmo mundo

Essa é outra confusão comum.

No processamento Batch, normalmente existe apenas uma instância daquele programa executando.

Logo, Working-Storage atende perfeitamente.

Já no CICS...

Milhares de usuários podem chamar o mesmo programa.

Ao mesmo tempo.

Nesse ambiente, utilizar inadequadamente áreas compartilhadas pode gerar problemas extremamente difíceis de reproduzir.

Aquele famoso erro que "acontece uma vez por semana" muitas vezes nasce justamente da utilização incorreta da memória.


Um exemplo simples

Imagine este contador.

WORKING-STORAGE SECTION.

01 WS-CONTADOR PIC 9(5).

Cada chamada faz:

ADD 1 TO WS-CONTADOR.

Em Batch isso pode funcionar exatamente como esperado.

Agora imagine centenas de transações simultâneas.

Quem garante qual usuário alterou o contador primeiro?

Quem garante que nenhuma atualização será perdida?

É exatamente por isso que programas transacionais precisam ser cuidadosamente projetados.


Local-Storage também tem custo

É tentador imaginar que basta substituir tudo por Local-Storage.

Não é tão simples.

Toda criação de memória possui custo.

A cada ativação do programa, o sistema precisa:

  • reservar memória;

  • inicializar valores;

  • preparar a área;

  • liberar recursos posteriormente.

Esse trabalho é pequeno.

Mas quando falamos em milhões de chamadas por dia...

Pequenos custos tornam-se grandes números.

É por isso que engenharia de software nunca trabalha com respostas absolutas.

Tudo depende do contexto.


E o VALUE?

Outro detalhe frequentemente ignorado.

Observe:

01 WS-TOTAL PIC 9 VALUE 5.

O valor inicial é definido quando a área é inicializada.

Depois disso, o programa pode alterá-lo quantas vezes desejar.

Já na Local-Storage:

01 LS-TOTAL PIC 9 VALUE 5.

Cada nova ativação recebe novamente o valor cinco.

Parece um detalhe.

Mas em subprogramas reutilizados milhares de vezes isso muda completamente o comportamento da aplicação.


CALL e Subprogramas

Imagine um módulo de cálculo.

CALCULA-JUROS

Ele é chamado por dezenas de sistemas.

Se utilizar apenas Working-Storage para dados temporários, o comportamento dependerá da forma como o ambiente gerencia essa área e do modelo de execução adotado.

Se utilizar Local-Storage para as variáveis temporárias, cada ativação começa com uma área própria e limpa, reduzindo o risco de interferência entre execuções.

Por isso, muitos subprogramas modernos utilizam Local-Storage para cálculos intermediários e Working-Storage apenas para estruturas que realmente precisam existir durante toda a ativação do programa.


A analogia do hotel

Imagine um hotel.

A Working-Storage é o prédio.

Sempre está lá.

As paredes continuam no mesmo lugar.

Os corredores não mudam.

Agora pense nos quartos.

Cada hóspede recebe um quarto limpo.

Quando vai embora...

Outro hóspede ocupa o mesmo espaço.

Não leva a bagagem do anterior.

A Local-Storage funciona exatamente assim.

Cada execução recebe seu próprio "quarto".


E quando falamos de desempenho?

Uma das maiores virtudes do IBM Z sempre foi a eficiência.

O sistema operacional conhece profundamente como seus programas utilizam memória.

O compilador Enterprise COBOL também foi otimizado durante décadas para produzir código extremamente eficiente.

Por isso, a escolha entre Working-Storage e Local-Storage não deve ser feita por hábito, mas por necessidade.

Variáveis permanentes?

Working-Storage.

Dados temporários exclusivos de uma ativação?

Local-Storage.

Subprogramas reentrantes?

Planejamento cuidadoso das áreas de dados.


O erro mais comum dos iniciantes

Existe uma pergunta que aparece frequentemente em cursos.

"Professor, então devo usar Local-Storage em tudo?"

A resposta é simples.

Não.

Assim como não existe um único tipo de dataset para todas as situações, não existe uma única área de memória adequada para todos os cenários.

A escolha depende do ambiente de execução, do tipo de aplicação, da necessidade de concorrência, do ciclo de vida dos dados e dos requisitos de desempenho.

Conhecer essas diferenças é o que separa quem apenas escreve código de quem projeta soluções robustas para ambientes corporativos.


O verdadeiro aprendizado

Aprender COBOL não é decorar sintaxes.

Não é memorizar comandos.

Não é saber onde fica a vírgula do PIC.

Programar COBOL é entender como o sistema operacional conversa com o compilador.

Como o compilador organiza a memória.

Como o programa compartilha recursos.

Como milhares de usuários podem executar exatamente o mesmo módulo sem que um interfira na execução do outro.

Quando compreendemos a diferença entre Working-Storage e Local-Storage, deixamos de enxergar apenas duas seções da Data Division.

Passamos a enxergar a arquitetura invisível que sustenta aplicações responsáveis por movimentar trilhões de dólares diariamente.

Na próxima vez que declarar uma variável em seu programa COBOL, lembre-se: você não está apenas escolhendo onde armazenar um número ou um texto. Está tomando uma decisão de arquitetura que pode impactar desempenho, escalabilidade, reentrância e confiabilidade de todo o sistema.

E é justamente esse tipo de detalhe, aparentemente simples, que transforma um programador COBOL em um verdadeiro engenheiro de software para IBM Z.

Porque, no fim das contas, a excelência no mainframe não nasce das grandes decisões. Ela é construída por milhares de pequenas escolhas corretas, feitas todos os dias, muitas delas invisíveis para quem apenas olha o código.