☕ Um Café no Bellacosa Mainframe
Gostou do conteúdo? Ajude a manter o café quente, o COBOL compilando e o mainframe acordado. 😄
☕ Pague um café ao Bellacosa

Translate

Mostrar mensagens com a etiqueta LOCAL-STORAGE. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta LOCAL-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.


Exemplo pratico de recursividade

Veja uma algoritmo em arvore B (B-Tree) search

https://eljefemidnightlunch.blogspot.com/2023/10/cobol-recursivo-sem-misterios.html


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.


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.


sexta-feira, 22 de novembro de 2019

Data Division : Quando um Programador Descobre que Cada Byte Deixa uma Impressão Digital — e que a Cena do Crime Começa Antes da PROCEDURE DIVISION

 

Bellacosa Mainframe apresenta a Data Division no COBOL

☕ Um Café no Bellacosa Mainframe

Data Division sem Mistérios para Programadores COBOL

Quando um Programador Descobre que Cada Byte Deixa uma Impressão Digital — e que a Cena do Crime Começa Antes da PROCEDURE DIVISION

Nova York, 02h17 da manhã.

A chuva escorre pelas paredes de vidro do laboratório forense enquanto os reflexos azuis das viaturas atravessam a sala. Sobre uma bancada de aço inoxidável existe algo aparentemente simples: um programa COBOL encontrado em produção depois de um processamento noturno terminar com dados inconsistentes.

Nenhum cadáver.

Nenhuma arma.

Nenhuma testemunha disposta a falar.

Apenas milhões de bytes, um arquivo sequencial, algumas áreas de memória e um saldo bancário que apareceu negativo onde jamais deveria existir um valor menor que zero.

O programador iniciante olha para o código e afirma:

— O problema deve estar na PROCEDURE DIVISION.

O investigador veterano do mainframe coloca as luvas, aproxima-se do terminal 3270 e responde:

— Esse é o primeiro erro de quem ainda não aprendeu a investigar sistemas legados. A lógica pode executar o crime, mas é na DATA DIVISION que encontramos as impressões digitais.

Bem-vindo ao laboratório forense do Bellacosa Mainframe.

Hoje não examinaremos sangue, fibras, pólvora ou DNA. Examinaremos registros, campos, buffers, parâmetros, tabelas, condições, formatos numéricos e áreas de armazenamento.

Porque, em COBOL, nenhum byte desaparece sem deixar vestígios.


Bellacosa Mainframe em DATA DIVISION em destaque

A cena do crime: o que é a DATA DIVISION?

Um programa COBOL tradicional costuma ser organizado em grandes divisões:

       IDENTIFICATION DIVISION.
       ENVIRONMENT DIVISION.
       DATA DIVISION.
       PROCEDURE DIVISION.

A IDENTIFICATION DIVISION identifica o programa.

A ENVIRONMENT DIVISION descreve aspectos do ambiente onde ele será executado.

A DATA DIVISION define os dados utilizados pelo programa.

A PROCEDURE DIVISION contém as instruções que manipulam esses dados.

Uma comparação simples seria:

DivisãoFunção
IDENTIFICATION DIVISIONIdentificação do caso
ENVIRONMENT DIVISIONDescrição da cena
DATA DIVISIONCatálogo de evidências
PROCEDURE DIVISIONReconstrução dos acontecimentos

Muitos iniciantes resumem a Data Division como “o lugar onde declaramos variáveis”.

Isso está correto, mas é incompleto.

A Data Division não apenas declara variáveis. Ela especifica:

  • o tamanho de cada campo;

  • o formato dos dados;

  • a representação física em memória;

  • a hierarquia entre campos;

  • o tempo de vida das informações;

  • a origem dos dados;

  • a forma de compartilhamento entre programas;

  • a organização de registros em arquivos;

  • as áreas utilizadas como entrada e saída;

  • as diferentes interpretações possíveis para os mesmos bytes.

É quase como se cada campo viesse acompanhado de uma ficha criminal completa.

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

Esta linha não diz apenas que existe um saldo.

Ela revela que:

  • o campo é numérico;

  • aceita sinal;

  • possui sete posições inteiras;

  • possui duas posições decimais implícitas;

  • utiliza formato decimal compactado;

  • ocupa uma quantidade específica de bytes;

  • precisa ser manipulado respeitando sua representação interna.

O compilador utiliza essa descrição para gerar as instruções adequadas.

Portanto, a Data Division é um contrato de memória.


O laboratório possui diferentes salas

A Data Division pode conter várias seções:

       DATA DIVISION.

       FILE SECTION.

       WORKING-STORAGE SECTION.

       LOCAL-STORAGE SECTION.

       LINKAGE SECTION.

       SCREEN SECTION.

       REPORT SECTION.

Nem todas precisam aparecer no mesmo programa.

Em sistemas corporativos atuais, as mais frequentes são:

  • FILE SECTION;

  • WORKING-STORAGE SECTION;

  • LINKAGE SECTION.

A LOCAL-STORAGE SECTION também é muito importante em programas reutilizáveis e reentrantes.

SCREEN SECTION e REPORT SECTION são mais específicas e hoje aparecem com menor frequência em ambientes IBM z/OS tradicionais.

Vamos entrar em cada sala do laboratório.


FILE SECTION: o depósito de evidências externas

A FILE SECTION descreve os registros dos arquivos utilizados pelo programa.

Um detalhe crucial: ela não armazena o arquivo inteiro na memória.

Ela normalmente representa uma área onde um registro é disponibilizado a cada operação de leitura ou gravação.

Considere o seguinte arquivo de clientes:

00001JOAO DA SILVA                 00000150000
00002MARIA OLIVEIRA                00000275050
00003PEDRO SANTOS                  00000098075

Um arquivo pode possuir milhões de registros. O programa não precisa carregar todos de uma vez.

Na FILE SECTION, descrevemos o formato de um registro:

       FILE SECTION.

       FD  ARQ-CLIENTES.

       01  REG-CLIENTE.
           05 REG-CODIGO       PIC 9(5).
           05 REG-NOME         PIC X(30).
           05 REG-SALDO        PIC 9(9)V99.

O FD significa File Description.

Ele introduz a descrição lógica do arquivo.

       FD ARQ-CLIENTES.

Logo abaixo aparece o registro:

       01 REG-CLIENTE.

Quando o programa executa:

       READ ARQ-CLIENTES

o próximo registro disponível é colocado na área associada ao arquivo.

Em termos simplificados:

Arquivo em disco
      |
      v
Sistema de arquivos
      |
      v
Buffer de entrada
      |
      v
REG-CLIENTE

Depois, o programa pode examinar os campos:

       DISPLAY REG-CODIGO
       DISPLAY REG-NOME
       DISPLAY REG-SALDO

Uma correção importante sobre a imagem

Na imagem apresentada, a File Section aparece relacionada a “variáveis temporárias”. Isso pode causar confusão.

A FILE SECTION não é normalmente utilizada como área genérica de variáveis temporárias. Sua função principal é descrever registros associados a arquivos.

As variáveis temporárias ficam geralmente na WORKING-STORAGE SECTION ou na LOCAL-STORAGE SECTION.

Em uma investigação técnica, essa distinção é importante. Colocar a etiqueta errada em uma evidência pode comprometer todo o caso.


O arquivo não existe sozinho: SELECT e FD

Em muitos programas, a declaração do arquivo possui duas partes.

Na ENVIRONMENT DIVISION, encontramos o SELECT:

       ENVIRONMENT DIVISION.

       INPUT-OUTPUT SECTION.

       FILE-CONTROL.

           SELECT ARQ-CLIENTES
               ASSIGN TO CLIENTES
               ORGANIZATION IS SEQUENTIAL
               FILE STATUS IS WS-FILE-STATUS.

Na DATA DIVISION, encontramos o FD:

       FILE SECTION.

       FD  ARQ-CLIENTES.

       01  REG-CLIENTE.
           05 REG-CODIGO       PIC 9(5).
           05 REG-NOME         PIC X(30).
           05 REG-SALDO        PIC 9(9)V99.

O SELECT descreve a relação lógica com o ambiente.

O FD descreve o registro.

É como se o SELECT informasse onde a evidência foi encontrada, enquanto o FD explicasse sua estrutura física.


FILE STATUS: o laudo que muitos esquecem de consultar

Um programa robusto não deve assumir que toda operação de arquivo funcionará corretamente.

Por isso utilizamos:

       FILE STATUS IS WS-FILE-STATUS

E declaramos:

       01 WS-FILE-STATUS       PIC XX.

Após um OPEN, READ, WRITE, REWRITE, DELETE ou CLOSE, o campo pode indicar o resultado da operação.

Exemplo:

       READ ARQ-CLIENTES
           AT END
               SET FIM-ARQUIVO TO TRUE
       END-READ

Ou:

       IF WS-FILE-STATUS NOT = '00'
           DISPLAY 'ERRO DE ARQUIVO: ' WS-FILE-STATUS
       END-IF

O valor 00 normalmente representa sucesso.

Mas atenção: os significados exatos podem variar conforme a operação e a organização do arquivo.

Uma boa prática é nunca tratar o status de arquivo como um detalhe decorativo.

Um READ malsucedido não é silêncio. É uma testemunha tentando falar.


WORKING-STORAGE SECTION: a sala central do laboratório

A WORKING-STORAGE SECTION é uma das áreas mais utilizadas em programas COBOL.

Ela contém dados que geralmente existem durante toda a execução da unidade de programa.

       WORKING-STORAGE SECTION.

       01 WS-CONTADOR          PIC 9(5) VALUE ZERO.
       01 WS-TOTAL             PIC S9(9)V99 COMP-3 VALUE ZERO.
       01 WS-FIM-ARQUIVO       PIC X VALUE 'N'.

Enquanto o programa estiver ativo, essas áreas permanecem disponíveis.

Podemos utilizar a Working-Storage para:

  • contadores;

  • acumuladores;

  • flags;

  • mensagens;

  • campos auxiliares;

  • datas;

  • áreas de entrada e saída;

  • tabelas;

  • constantes;

  • códigos de retorno;

  • estruturas temporárias;

  • copybooks;

  • campos utilizados em cálculos.

Contador

       01 WS-QTD-CLIENTES      PIC 9(7) VALUE ZERO.

Uso:

       ADD 1 TO WS-QTD-CLIENTES

Acumulador

       01 WS-TOTAL-SALDOS      PIC S9(11)V99 COMP-3
                               VALUE ZERO.

Uso:

       ADD REG-SALDO TO WS-TOTAL-SALDOS

Flag

       01 WS-FIM-ARQUIVO       PIC X VALUE 'N'.

Uso:

       IF WS-FIM-ARQUIVO = 'S'
           DISPLAY 'PROCESSAMENTO ENCERRADO'
       END-IF

Funciona, mas pode ser melhorado com nível 88.


Nível 88: o testemunho semântico

O nível 88 não reserva uma nova área de memória.

Ele fornece nomes significativos para valores ou condições de outro campo.

       01 WS-FIM-ARQUIVO       PIC X VALUE 'N'.
           88 FIM-ARQUIVO      VALUE 'S'.
           88 NAO-FIM-ARQUIVO  VALUE 'N'.

Agora podemos escrever:

       SET FIM-ARQUIVO TO TRUE

E testar:

       PERFORM UNTIL FIM-ARQUIVO

Isso é muito mais expressivo do que:

       PERFORM UNTIL WS-FIM-ARQUIVO = 'S'

Os dois funcionam, mas o nível 88 aproxima o código da linguagem do negócio.

Outro exemplo:

       01 WS-TIPO-CLIENTE      PIC X.
           88 CLIENTE-COMUM    VALUE 'C'.
           88 CLIENTE-VIP      VALUE 'V'.
           88 CLIENTE-BLOQUEADO VALUE 'B'.

Uso:

       IF CLIENTE-BLOQUEADO
           DISPLAY 'OPERACAO NAO AUTORIZADA'
       END-IF

O nível 88 é como uma legenda pericial: ele transforma valores brutos em significados compreensíveis.


VALUE: o estado inicial da evidência

A cláusula VALUE define um conteúdo inicial.

       01 WS-CONTADOR          PIC 9(5) VALUE ZERO.
       01 WS-MENSAGEM          PIC X(30)
                               VALUE 'INICIO DO PROCESSAMENTO'.
       01 WS-STATUS            PIC X VALUE 'A'.

Sem uma inicialização adequada, o programa pode trabalhar com conteúdo inesperado.

Em muitos ambientes, determinadas áreas podem receber valores previsíveis conforme o compilador, o runtime e a forma de carregamento. Porém, um programa profissional não deve depender de suposições frágeis.

Sempre que o valor inicial for importante, declare-o ou inicialize-o explicitamente.

       INITIALIZE WS-AREA-TRABALHO

Mas investigue o uso de INITIALIZE com cuidado: ele não é necessariamente equivalente a mover espaços ou zeros para todos os campos indiscriminadamente. Seu comportamento respeita as categorias dos itens e pode ser alterado com cláusulas adicionais.


LOCAL-STORAGE SECTION: cada chamada recebe uma cena isolada

A LOCAL-STORAGE SECTION é semelhante à Working-Storage, mas possui uma diferença essencial: suas áreas são alocadas para cada ativação do programa.

       LOCAL-STORAGE SECTION.

       01 LS-CONTADOR          PIC 9(5) VALUE ZERO.
       01 LS-MENSAGEM          PIC X(40).

Em termos conceituais:

Primeira chamada:
LS-CONTADOR próprio

Segunda chamada:
LS-CONTADOR próprio

Terceira chamada:
LS-CONTADOR próprio

Cada invocação recebe seu próprio conjunto de dados locais.

Isso é especialmente útil em:

  • programas reentrantes;

  • rotinas reutilizáveis;

  • ambientes com várias chamadas simultâneas;

  • aplicações servidoras;

  • processamento sob Language Environment;

  • componentes que não devem preservar estado entre ativações.

Working-Storage versus Local-Storage

Uma simplificação útil:

CaracterísticaWorking-StorageLocal-Storage
ExistênciaAssociada à instância carregada do programaCriada para cada invocação
InicializaçãoNormalmente ocorre quando o programa é carregadoOcorre em cada entrada
Uso típicoEstado geral, constantes, áreas persistentes durante execuçãoDados privados de uma chamada
ReentrânciaExige maior cuidadoFacilita isolamento por ativação

Entretanto, não reduza a discussão a “Working-Storage é compartilhada e Local-Storage nunca é”.

O comportamento real depende de fatores como:

  • forma de chamada;

  • programas estáticos ou dinâmicos;

  • uso de CANCEL;

  • opções de compilação;

  • ambiente CICS;

  • atributos de reentrância;

  • runtime;

  • arquitetura da aplicação.

No laboratório do mainframe, frases absolutas são frequentemente suspeitas.


Um caso clássico: o contador que se recusava a voltar para zero

Imagine um subprograma:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CALCULA01.

       DATA DIVISION.

       WORKING-STORAGE SECTION.

       01 WS-CONTADOR          PIC 9(4) VALUE ZERO.

       PROCEDURE DIVISION.

           ADD 1 TO WS-CONTADOR
           DISPLAY 'CONTADOR: ' WS-CONTADOR

           GOBACK.

O programa chamador executa várias chamadas.

Um iniciante pode esperar:

CONTADOR: 0001
CONTADOR: 0001
CONTADOR: 0001

Mas, dependendo da forma de carregamento e das condições de execução, pode observar:

CONTADOR: 0001
CONTADOR: 0002
CONTADOR: 0003

Isso ocorre porque a Working-Storage pode manter seu conteúdo entre chamadas enquanto o programa permanecer carregado.

Mover o contador para Local-Storage muda a intenção:

       LOCAL-STORAGE SECTION.

       01 LS-CONTADOR          PIC 9(4) VALUE ZERO.

Agora cada chamada recebe uma nova área inicializada.

Esse tipo de comportamento parece sobrenatural apenas até encontrarmos a seção correta.

Depois disso, torna-se evidência técnica.


LINKAGE SECTION: dados que pertencem a outra pessoa

A LINKAGE SECTION descreve dados fornecidos por outra unidade de execução.

Esses dados não são, em princípio, áreas comuns criadas e pertencentes ao subprograma da mesma forma que itens da Working-Storage.

Exemplo de programa chamador:

       WORKING-STORAGE SECTION.

       01 WS-CLIENTE.
           05 WS-CODIGO        PIC 9(5).
           05 WS-NOME          PIC X(30).
           05 WS-SALDO         PIC S9(7)V99 COMP-3.

       PROCEDURE DIVISION.

           MOVE 12345 TO WS-CODIGO
           MOVE 'STELLA BONASERA' TO WS-NOME
           MOVE 1500.75 TO WS-SALDO

           CALL 'ATUALIZA1'
               USING WS-CLIENTE

           GOBACK.

Programa chamado:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ATUALIZA1.

       DATA DIVISION.

       LINKAGE SECTION.

       01 LK-CLIENTE.
           05 LK-CODIGO        PIC 9(5).
           05 LK-NOME          PIC X(30).
           05 LK-SALDO         PIC S9(7)V99 COMP-3.

       PROCEDURE DIVISION USING LK-CLIENTE.

           ADD 100 TO LK-SALDO

           GOBACK.

O chamador entrega uma área.

O subprograma descreve essa área na Linkage Section.

A PROCEDURE DIVISION USING estabelece a associação entre os argumentos recebidos e os itens de Linkage.


BY REFERENCE, BY CONTENT e BY VALUE

O método de passagem de parâmetros muda completamente a investigação.

BY REFERENCE

É a forma tradicional e muito comum.

       CALL 'ATUALIZA1'
           USING BY REFERENCE WS-CLIENTE

O subprograma recebe acesso à área do chamador.

Alterações podem ser percebidas pelo chamador.

Chamador possui WS-SALDO
        |
        v
Subprograma acessa a mesma área
        |
        v
Alteração volta para o chamador

BY CONTENT

O subprograma recebe uma cópia lógica do argumento.

       CALL 'CONSULTA1'
           USING BY CONTENT WS-CLIENTE

O programa chamado pode trabalhar com o conteúdo, mas mudanças não devem retornar para a área original como numa passagem por referência.

BY VALUE

Utilizado para passar o valor diretamente, sendo especialmente relevante na interoperabilidade com outras linguagens e APIs.

       CALL 'ROTINAC'
           USING BY VALUE WS-CODIGO

BY VALUE exige atenção a tamanho, representação e convenções de chamada.

Não basta o número “parecer igual”. Os bytes precisam ser compatíveis.

Em integração, o assassino frequentemente se chama “layout incompatível”.


O contrato precisa ser idêntico

Considere o chamador:

       01 WS-CLIENTE.
           05 WS-CODIGO        PIC 9(5).
           05 WS-NOME          PIC X(30).
           05 WS-SALDO         PIC S9(7)V99 COMP-3.

E o chamado:

       01 LK-CLIENTE.
           05 LK-CODIGO        PIC 9(5).
           05 LK-NOME          PIC X(20).
           05 LK-SALDO         PIC S9(7)V99 COMP-3.

O nome possui 30 bytes no chamador, mas apenas 20 na visão do chamado.

Isso desloca o campo seguinte.

O subprograma pode interpretar os últimos dez bytes do nome como parte do saldo.

Não existe magia.

Existe desalinhamento.

Em sistemas reais, esse erro pode gerar:

  • valores corrompidos;

  • dados aparentemente aleatórios;

  • S0C7;

  • sobreposição de campos;

  • gravações incorretas;

  • falhas intermitentes;

  • resultados diferentes após recompilações.

Por isso copybooks compartilhados são tão importantes.


COPYBOOK: o laudo oficial do layout

Em vez de repetir a mesma estrutura em dezenas de programas, podemos centralizá-la em um copybook.

Arquivo CPYCLI01:

       01 CLIENTE-DADOS.
           05 CLIENTE-CODIGO   PIC 9(5).
           05 CLIENTE-NOME     PIC X(30).
           05 CLIENTE-SALDO    PIC S9(7)V99 COMP-3.

No chamador:

       WORKING-STORAGE SECTION.

       COPY CPYCLI01.

No chamado:

       LINKAGE SECTION.

       COPY CPYCLI01.

Assim, ambos utilizam a mesma definição.

Mas existe uma armadilha: alterar o copybook e recompilar apenas um dos programas.

O fonte parece correto.

O copybook parece correto.

O programa recompilado parece correto.

Mas o módulo antigo continua esperando o layout anterior.

E então nasce um crime perfeito: duas versões corretas individualmente, incompatíveis quando trabalham juntas.

A solução envolve disciplina de gestão de dependências, recompilação, testes e controle de versões.


SCREEN SECTION: a antiga sala de interrogatório

A SCREEN SECTION permite definir telas em implementações COBOL que oferecem esse recurso.

Exemplo genérico:

       SCREEN SECTION.

       01 TELA-CLIENTE.
           05 BLANK SCREEN.
           05 LINE 3 COLUMN 10
              VALUE 'CODIGO DO CLIENTE:'.
           05 LINE 3 COLUMN 30
              PIC 9(5)
              USING WS-CODIGO.

Ela foi utilizada em sistemas interativos de diversas plataformas.

No universo IBM mainframe, especialmente em aplicações CICS, interfaces 3270 são frequentemente definidas por mapas BMS.

Em IMS, pode-se encontrar MFS.

Em arquiteturas modernas, o COBOL pode funcionar atrás de:

  • APIs REST;

  • aplicações web;

  • serviços de integração;

  • mensageria;

  • z/OS Connect;

  • CICS Transaction Gateway;

  • aplicativos móveis.

A tela pode ter desaparecido do COBOL, mas o programa continua processando o coração da transação.


REPORT SECTION: o especialista aposentado que ainda sabe demais

A REPORT SECTION está associada ao Report Writer, um recurso criado para facilitar a produção de relatórios estruturados.

Ela podia ajudar a organizar:

  • cabeçalhos;

  • detalhes;

  • rodapés;

  • totais;

  • quebras de grupo;

  • paginação.

Exemplo conceitual:

       REPORT SECTION.

       RD RELATORIO-VENDAS.

       01 TYPE IS PAGE HEADING.
          05 COLUMN 1 PIC X(20)
             VALUE 'RELATORIO DE VENDAS'.

Atualmente, muitos relatórios são produzidos por:

  • programas tradicionais;

  • DFSORT;

  • ferramentas de BI;

  • bancos de dados;

  • aplicações distribuídas;

  • geradores de documentos;

  • plataformas analíticas.

Mesmo assim, sistemas antigos podem utilizar Report Writer até hoje.

Em mainframe, “antigo” não significa necessariamente “inútil”.

Às vezes significa apenas “estável há 35 anos e ainda fechando a contabilidade antes do amanhecer”.


A hierarquia dos níveis: reconstruindo o corpo do registro

COBOL utiliza números de nível para representar a hierarquia dos dados.

       01 WS-CLIENTE.
           05 WS-IDENTIFICACAO.
               10 WS-CODIGO    PIC 9(5).
               10 WS-CPF       PIC 9(11).
           05 WS-NOME          PIC X(30).
           05 WS-ENDERECO.
               10 WS-RUA       PIC X(30).
               10 WS-NUMERO    PIC 9(5).
               10 WS-CIDADE    PIC X(20).

Visualmente:

WS-CLIENTE
├── WS-IDENTIFICACAO
│   ├── WS-CODIGO
│   └── WS-CPF
├── WS-NOME
└── WS-ENDERECO
    ├── WS-RUA
    ├── WS-NUMERO
    └── WS-CIDADE

Os itens de grupo não possuem PIC.

Eles são formados pelos campos subordinados.

Podemos mover ou exibir o grupo inteiro:

       DISPLAY WS-CLIENTE

Mas isso manipula a sequência física dos bytes, não uma abstração independente.


Os níveis especiais

Além dos níveis comuns, COBOL possui níveis especiais.

Nível 01

Define uma estrutura principal ou registro.

       01 WS-CLIENTE.

Níveis 02 a 49

Definem itens subordinados.

           05 WS-NOME PIC X(30).

Nível 66

Utilizado com RENAMES.

       66 WS-DADOS-BASICOS RENAMES
          WS-CODIGO THRU WS-NOME.

Hoje é pouco usado e deve ser tratado com cuidado, pois pode dificultar a leitura.

Nível 77

Define um item elementar independente.

       77 WS-CONTADOR PIC 9(5).

Ainda pode ser encontrado em programas antigos, embora muitos padrões modernos prefiram organizar itens sob níveis 01.

Nível 78

Em algumas implementações, é utilizado para constantes de compilação.

Sua disponibilidade e detalhes dependem do compilador.

Nível 88

Define nomes de condição.

           88 CLIENTE-ATIVO VALUE 'A'.

PICTURE: o retrato falado do dado

A cláusula PIC, abreviação de PICTURE, descreve a categoria e o formato do campo.

Alfanumérico

       05 WS-NOME PIC X(30).

Pode conter letras, números, espaços e outros caracteres compatíveis com a codificação utilizada.

Numérico

       05 WS-IDADE PIC 9(3).

Representa três posições numéricas.

Alfabético

       05 WS-INICIAL PIC A.

Seu uso é menos comum em programas modernos.

Com sinal

       05 WS-SALDO PIC S9(7)V99.

O S indica sinal.

Decimal implícito

       05 WS-VALOR PIC 9(5)V99.

O V não ocupa uma posição física.

Ele indica onde o programa deve interpretar a casa decimal.

O valor:

0012345

pode ser interpretado como:

123,45

de acordo com o layout.

Esse é um dos grandes suspeitos em integrações.

Se um sistema envia centavos e outro espera reais, um pagamento de R$ 150,00 pode ser interpretado como R$ 15.000,00 ou R$ 1,50.

O byte está correto.

A interpretação está errada.


DISPLAY, COMP, COMP-3: mesma informação, corpos diferentes

Um número pode possuir diferentes representações internas.

DISPLAY

       05 WS-VALOR PIC 9(5).

Cada dígito costuma ocupar uma posição de caractere.

COMP ou BINARY

       05 WS-VALOR PIC S9(9) COMP.

O valor é armazenado em representação binária conforme as regras do compilador e opções aplicáveis.

COMP-3

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

Utiliza decimal compactado.

Dois dígitos normalmente são armazenados por byte, com uma parte do último byte usada para o sinal.

É muito comum em aplicações financeiras porque representa valores decimais com precisão e economia de espaço.

A pista do S0C7

Um S0C7 normalmente aponta para uma exceção de dados durante uma operação decimal.

Causas comuns:

  • campo numérico contém bytes inválidos;

  • layout incorreto;

  • deslocamento provocado por copybook incompatível;

  • campo não inicializado;

  • arquivo lido com definição errada;

  • tentativa de calcular usando dados alfanuméricos;

  • erro de REDEFINES;

  • parâmetros desalinhados.

O erro aparece na Procedure Division porque uma instrução tentou utilizar o campo.

Mas o verdadeiro criminoso pode estar na Data Division.


REDEFINES: duas interpretações para a mesma evidência

REDEFINES permite que a mesma área de memória seja vista de maneiras diferentes.

       01 WS-DATA-NUMERICA.
           05 WS-DATA-AAAAMMDD PIC 9(8).

       01 WS-DATA-DETALHADA
          REDEFINES WS-DATA-NUMERICA.
           05 WS-ANO           PIC 9(4).
           05 WS-MES           PIC 9(2).
           05 WS-DIA           PIC 9(2).

Se:

       MOVE 20260725 TO WS-DATA-AAAAMMDD

então podemos acessar:

WS-ANO = 2026
WS-MES = 07
WS-DIA = 25

Nenhum dado foi copiado.

A mesma sequência de bytes recebeu duas descrições.

É poderoso, eficiente e perigoso.

Um REDEFINES incorreto pode transformar:

  • nome em número;

  • data em saldo;

  • código em endereço;

  • payload em campos incompatíveis.

No CSI do mainframe, REDEFINES é aquele suspeito elegante que parece sempre ter um álibi.


OCCURS: a formação da quadrilha

OCCURS define repetições de uma estrutura, criando tabelas.

       01 WS-TABELA-CLIENTES.
           05 WS-CLIENTE OCCURS 100 TIMES.
               10 WS-CODIGO    PIC 9(5).
               10 WS-NOME      PIC X(30).

Isso reserva espaço para cem ocorrências.

Podemos acessar:

       MOVE 12345 TO WS-CODIGO (1)
       MOVE 'MAC TAYLOR' TO WS-NOME (1)

Subscript

       01 WS-I PIC 9(3) COMP VALUE 1.

       DISPLAY WS-NOME (WS-I)

Index

A tabela pode ser definida com índice:

       05 WS-CLIENTE OCCURS 100 TIMES
           INDEXED BY IDX-CLIENTE.

Uso:

       SET IDX-CLIENTE TO 1
       DISPLAY WS-NOME (IDX-CLIENTE)

O índice possui natureza própria e pode ser otimizado para o deslocamento interno da tabela.

Nunca ultrapasse os limites do OCCURS.

Acessar a ocorrência 101 de uma tabela com cem elementos pode atingir memória pertencente a outro campo.

É o equivalente digital de atravessar a fita amarela da cena do crime.


Passo a passo: montando um programa completo

Vamos construir um pequeno processamento sequencial.

1. Declarar o arquivo no ambiente

       ENVIRONMENT DIVISION.

       INPUT-OUTPUT SECTION.

       FILE-CONTROL.

           SELECT ARQ-CLIENTES
               ASSIGN TO CLIENTES
               ORGANIZATION IS SEQUENTIAL
               FILE STATUS IS WS-FILE-STATUS.

2. Descrever o registro

       DATA DIVISION.

       FILE SECTION.

       FD  ARQ-CLIENTES.

       01  REG-CLIENTE.
           05 REG-CODIGO       PIC 9(5).
           05 REG-NOME         PIC X(30).
           05 REG-SALDO        PIC S9(7)V99 COMP-3.

3. Criar áreas auxiliares

       WORKING-STORAGE SECTION.

       01 WS-FILE-STATUS       PIC XX.

       01 WS-CONTROLES.
           05 WS-FIM           PIC X VALUE 'N'.
               88 FIM-ARQUIVO  VALUE 'S'.
               88 HA-REGISTRO  VALUE 'N'.

       01 WS-TOTAIS.
           05 WS-QTD           PIC 9(7) VALUE ZERO.
           05 WS-TOTAL-SALDO   PIC S9(13)V99 COMP-3
                               VALUE ZERO.

4. Abrir o arquivo

       PROCEDURE DIVISION.

       0000-PRINCIPAL.

           OPEN INPUT ARQ-CLIENTES

           IF WS-FILE-STATUS NOT = '00'
               DISPLAY 'ERRO NO OPEN: ' WS-FILE-STATUS
               MOVE 12 TO RETURN-CODE
               GOBACK
           END-IF

5. Ler o primeiro registro

           PERFORM 1000-LER-CLIENTE

6. Processar até o fim

           PERFORM UNTIL FIM-ARQUIVO

               ADD 1 TO WS-QTD
               ADD REG-SALDO TO WS-TOTAL-SALDO

               DISPLAY REG-CODIGO
                       ' '
                       REG-NOME

               PERFORM 1000-LER-CLIENTE

           END-PERFORM

7. Encerrar

           CLOSE ARQ-CLIENTES

           DISPLAY 'CLIENTES: ' WS-QTD
           DISPLAY 'TOTAL:    ' WS-TOTAL-SALDO

           GOBACK.

8. Criar o parágrafo de leitura

       1000-LER-CLIENTE.

           READ ARQ-CLIENTES
               AT END
                   SET FIM-ARQUIVO TO TRUE
               NOT AT END
                   SET HA-REGISTRO TO TRUE
           END-READ.

O programa completo demonstra como cada seção coopera.

A File Section fornece o registro.

A Working-Storage guarda o controle e os totais.

A Procedure Division conduz o processamento.

Cada elemento possui responsabilidade própria.


Checklist forense para analisar uma DATA DIVISION

Quando um programa apresenta erro de dados, examine:

1. O PIC está correto?

Compare tamanho, sinal e casas decimais.

PIC 9(7)V99

não é equivalente a:

PIC 9(9)

2. O formato interno é compatível?

PIC S9(7)V99 COMP-3

não possui a mesma representação física de:

PIC X(5)

3. O arquivo está com o LRECL esperado?

Se o programa espera 100 bytes, mas o arquivo possui 120, parte do registro pode ser ignorada ou interpretada incorretamente.

4. O copybook é a versão correta?

Verifique data, biblioteca, concatenação e versão usada na compilação.

5. Chamador e chamado foram recompilados?

Uma mudança na interface pode exigir recompilar todos os consumidores.

6. Existe REDEFINES?

Descubra qual visão está sendo usada em cada ponto.

7. Existe OCCURS?

Verifique limites, índices e subscritos.

8. O campo foi inicializado?

Não presuma que contém zeros ou espaços.

9. O parâmetro foi passado pelo método correto?

Confirme BY REFERENCE, BY CONTENT ou BY VALUE.

10. O campo possui alinhamento ou sincronização especial?

Cláusulas como SYNCHRONIZED podem introduzir bytes de preenchimento dependendo do compilador e da estrutura.


Curiosidades da sala de evidências

Curiosidade 1: o nome do campo não existe no arquivo

Um arquivo físico não contém necessariamente nomes como:

REG-CODIGO
REG-NOME
REG-SALDO

Ele contém bytes.

Os nomes existem no programa para que os humanos e o compilador interpretem as posições.

O mesmo arquivo pode receber layouts diferentes.

Um deles estará correto.

Os demais produzirão ficção científica.

Curiosidade 2: PIC não é uma máscara de exibição apenas

Muitos iniciantes associam PICTURE apenas à aparência.

Na verdade, ela define categoria, tamanho e interpretação do item.

Curiosidade 3: o ponto decimal pode não existir fisicamente

No V implícito, nenhum caractere de ponto ou vírgula é armazenado.

PIC 9(5)V99

ocupa sete dígitos, não oito posições contendo um separador.

Curiosidade 4: um item de grupo pode misturar categorias

       01 WS-REGISTRO.
           05 WS-CODIGO PIC 9(5).
           05 WS-NOME   PIC X(20).

O grupo inteiro pode ser tratado como uma sequência de bytes para certas operações, apesar de conter campos de diferentes categorias.

Curiosidade 5: a memória é inocente

A memória não sabe que aqueles bytes representam CPF, saldo ou data.

Ela apenas armazena bits.

O significado vem do layout.

Quando o layout mente, a memória obedece.


Easter eggs do laboratório CSI Mainframe

Em homenagem ao clima de investigação, os nomes dos exemplos esconderam algumas referências.

  • MAC TAYLOR aparece como líder da equipe forense de Nova York.

  • STELLA BONASERA foi usada em uma estrutura de cliente.

  • O horário de 02h17 lembra a rotina dos processamentos batch que terminam quando a cidade dorme.

  • O terminal 3270 funciona como a mesa de autópsia digital.

  • O S0C7 é o equivalente mainframe de encontrar uma impressão digital impossível na arma do crime.

  • O copybook incompatível é o “suspeito que possuía duas identidades”.

  • O REDEFINES representa a evidência que muda de aparência dependendo do ângulo da luz.

  • O OCCURS é a fotografia de uma quadrilha perfeitamente alinhada na parede do distrito policial.

E existe um easter egg maior.

Em quase todo mistério COBOL, o programador procura primeiro a linha que falhou.

O investigador experiente procura primeiro o dado que chegou até ela.


Dicas do Bellacosa Mainframe para o iniciante

Use nomes que revelem intenção

Evite:

       01 X1 PIC X.

Prefira:

       01 WS-STATUS-CLIENTE PIC X.

Agrupe campos relacionados

       01 WS-CONTROLES.
           05 WS-FIM-ARQUIVO PIC X.
           05 WS-ERRO        PIC X.
           05 WS-RETORNO     PIC 9(4).

Use níveis 88

Eles tornam o código mais claro e reduzem comparações espalhadas.

Padronize prefixos

Exemplo:

  • WS- para Working-Storage;

  • LS- para Local-Storage;

  • LK- para Linkage;

  • REG- para registros de arquivo;

  • CT- para constantes;

  • IDX- para índices.

Isso não é uma regra universal da linguagem, mas ajuda muito na leitura.

Documente formatos numéricos

Especialmente:

  • sinal;

  • casas decimais;

  • COMP;

  • COMP-3;

  • valores enviados a outros sistemas.

Trate copybooks como interfaces

Uma alteração em copybook não é apenas uma mudança de layout.

Pode ser uma mudança de contrato entre dezenas ou centenas de programas.

Nunca ignore o tamanho total

Calcule o comprimento do registro.

Confirme com:

  • LRECL;

  • documentação;

  • catálogo;

  • ferramenta de browse;

  • definição do dataset;

  • programa produtor;

  • programa consumidor.

Teste limites

Para OCCURS, teste:

  • primeira posição;

  • última posição válida;

  • tabela vazia;

  • tabela completa;

  • tentativa de ultrapassagem;

  • chave não encontrada.


A conclusão da investigação

A equipe se reúne diante do painel de vidro.

O programa não falhou porque o ADD estava errado.

A instrução era perfeitamente válida.

O problema estava em um copybook antigo usado por um subprograma que não havia sido recompilado.

O chamador enviava:

       05 CLIENTE-NOME PIC X(30).

O chamado ainda esperava:

       05 CLIENTE-NOME PIC X(20).

Os dez bytes excedentes deslocaram o saldo.

Quando o programa tentou somar aquele conteúdo como decimal compactado, ocorreu o S0C7.

A linha da Procedure Division apenas encontrou o corpo.

O crime havia sido planejado na Data Division.

Essa é a lição central.

A DATA DIVISION não é um depósito passivo de declarações. Ela é a fundação arquitetural do programa COBOL.

A FILE SECTION descreve os registros que chegam de arquivos.

A WORKING-STORAGE SECTION mantém controles, acumuladores, tabelas e áreas utilizadas durante a execução.

A LOCAL-STORAGE SECTION fornece áreas independentes para cada ativação.

A LINKAGE SECTION estabelece contratos de comunicação entre programas.

A SCREEN SECTION pode descrever interfaces interativas.

A REPORT SECTION pode organizar relatórios estruturados.

Os níveis constroem hierarquias.

A cláusula PIC define formatos.

VALUE estabelece estados iniciais.

OCCURS cria tabelas.

REDEFINES oferece diferentes interpretações para a mesma memória.

Os níveis 88 transformam códigos em significado.

COMP e COMP-3 revelam que dois campos numericamente semelhantes podem possuir corpos físicos completamente diferentes.

Para o programador iniciante, dominar a Data Division significa começar a enxergar além da aparência do código.

Você deixa de ver apenas:

       05 SALDO PIC S9(7)V99 COMP-3.

E passa a enxergar:

Campo assinado
Decimal
Sete posições inteiras
Duas casas decimais
Representação compactada
Contrato físico de armazenamento
Possível ponto de integração
Possível origem de S0C7
Possível incompatibilidade com copybook

Esse é o momento em que o estudante deixa de apenas escrever COBOL e começa a investigar sistemas.

Porque na programação corporativa os defeitos raramente anunciam sua origem.

Eles aparecem em outro módulo.

Falham horas depois.

Dependem de um registro específico.

Só acontecem em produção.

Desaparecem durante o teste.

Retornam no fechamento mensal.

E deixam para trás apenas um dump, um código de retorno, algumas mensagens no spool e um campo cheio de bytes que ninguém deveria ter interpretado daquela maneira.

Mas não se preocupe.

Coloque as luvas.

Abra o compile listing.

Verifique o offset.

Compare o copybook.

Calcule o comprimento.

Analise o formato.

Siga o rastro dos bytes.

No laboratório do Bellacosa Mainframe, toda variável possui uma história.

Todo registro possui um passado.

Todo ABEND possui uma causa.

E toda Data Division, quando examinada com atenção, acaba confessando.

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
GitHub LinkedIn
Inicializando conteúdo...