| Bellacosa Mainframe apresenta um programa cobol recursivo |
COBOL Recursivo: Quando o Padawan Descobre que um Programa Pode Invocar a Si Mesmo
O Poder Proibido do CALL Infinito na Galáxia IBM Z
Por Bellacosa Mainframe
"O medo leva ao ABEND. O ABEND leva ao dump. O dump leva ao sofrimento."
— Mestre Sysprog Yoda/390
Durante muitos anos, programadores COBOL cresceram acreditando em uma verdade absoluta:
"Programa COBOL não faz recursão."
Era praticamente um dogma do templo Jedi dos mainframes.
E, honestamente?
Durante décadas isso foi quase verdade.
A maioria dos sistemas bancários, seguradoras, governo e processamento batch nunca precisou de algoritmos recursivos.
Mas então surgiram:
XML
JSON
árvores hierárquicas
parsers
estruturas dinâmicas
APIs modernas
integração com Java
serviços z/OS Connect
E um dia o jovem Padawan perguntou:
"Mestre Bellacosa...
Posso fazer um programa COBOL chamar ele mesmo?"
A resposta é:
Sim.
Mas como todo poder da Força, existe um preço.
O que é Recursividade?
Recursão significa:
Um procedimento invoca ele próprio.
Exemplo clássico:
Calcular fatorial.
5! = 5 × 4 × 3 × 2 × 1
Matematicamente:
Fatorial(n)
se n=1 retorna 1
senão
n * Fatorial(n-1)
Exemplo:
F(5)
↓
5 × F(4)
↓
5 × 4 × F(3)
↓
5 × 4 × 3 × F(2)
↓
5 × 4 × 3 × 2 × F(1)
↓
120
É uma técnica extremamente elegante.
COBOL Sempre Teve Recursão?
Não.
Historicamente COBOL nasceu em 1959.
E sua filosofia era:
Processamento comercial.
Arquivos.
Registros.
Batch.
Volumes enormes.
Não havia necessidade prática.
Durante décadas COBOL assumia:
Programa residente
Uma única Working Storage
Compartilhada
Exemplo:
WORKING-STORAGE SECTION.
01 CONTADOR PIC 9(5).
Só existe uma instância.
Todo mundo usa a mesma.
Problema:
Programa chama ele mesmo.
Segunda chamada sobrescreve variáveis.
Primeira execução quebra.
Caos.
Quando Surgiu?
IBM Enterprise COBOL começou suportar adequadamente recursão através da opção:
RECURSIVE
ou
RENT
Dependendo da geração do compilador.
Hoje encontramos suporte em:
Enterprise COBOL 4
Enterprise COBOL 5
Enterprise COBOL 6.x
COBOL 6.3
COBOL 6.4
COBOL 6.5
Totalmente suportado.
Inclusive no IBM z17.
Como Declarar um Programa Recursivo
No cabeçalho:
IDENTIFICATION DIVISION.
PROGRAM-ID. FATORIAL RECURSIVE.
ou
PROGRAM-ID. FATORIAL.
RECURSIVE.
Depende do compilador.
Exemplo Completo
Passo 1
Definir LINKAGE
LINKAGE SECTION.
01 LK-NUMERO.
PIC 9(4).
01 LK-RESULTADO.
PIC 9(10).
Passo 2
Procedure Division
PROCEDURE DIVISION USING
LK-NUMERO
LK-RESULTADO.
Passo 3
Caso base
IF LK-NUMERO <= 1
MOVE 1 TO LK-RESULTADO
ELSE
...
Passo 4
Criar variável local
LOCAL-STORAGE SECTION.
01 WS-TEMP.
PIC 9(10).
01 WS-N.
PIC 9(4).
Local Storage é fundamental.
Já veremos por quê.
Passo 5
Preparar próxima chamada
COMPUTE WS-N = LK-NUMERO -1
Passo 6
Invocar a si mesmo
CALL 'FATORIAL'
USING
WS-N
WS-TEMP
Passo 7
Calcular
COMPUTE LK-RESULTADO =
LK-NUMERO * WS-TEMP
Fim.
Programa Completo
IDENTIFICATION DIVISION.
PROGRAM-ID. FATORIAL RECURSIVE.
DATA DIVISION.
LOCAL-STORAGE SECTION.
01 WS-N.
PIC 9(4).
01 WS-TEMP.
PIC 9(10).
LINKAGE SECTION.
01 LK-NUMERO.
PIC 9(4).
01 LK-RESULTADO.
PIC 9(10).
PROCEDURE DIVISION USING
LK-NUMERO
LK-RESULTADO.
IF LK-NUMERO <= 1
MOVE 1 TO LK-RESULTADO
ELSE
COMPUTE WS-N =
LK-NUMERO -1
CALL 'FATORIAL'
USING
WS-N
WS-TEMP
COMPUTE LK-RESULTADO =
LK-NUMERO * WS-TEMP
END-IF.
GOBACK.
O que Acontece na Memória?
Aqui mora a magia.
Imagine:
FATORIAL(5)
Stack:
Frame 1
n=5
Chama:
Frame 2
n=4
Depois:
Frame 3
n=3
Depois:
Frame 4
n=2
Depois:
Frame 5
n=1
Retorno:
Frame 5 → 1
Frame 4 → 2
Frame 3 → 6
Frame 2 →24
Frame 1 →120
Visualmente
STACK
F(5)
F(4)
F(3)
F(2)
F(1)
Depois:
POP
F(1)
F(2)
F(3)
F(4)
F(5)
O Papel da LOCAL-STORAGE
Muitos Padawans cometem um erro fatal.
Usar:
WORKING-STORAGE
Exemplo:
01 WS-N.
Errado.
Por quê?
Porque Working Storage é única.
Compartilhada.
Exemplo:
Primeira chamada
WS-N=5
Segunda
WS-N=4
Terceira
WS-N=3
Sobrescreve.
Tudo quebra.
LOCAL-STORAGE
Cria uma cópia nova.
Para cada chamada.
Exemplo:
Frame 1
WS-N=5
Frame 2
WS-N=4
Frame 3
WS-N=3
Independentes.
Como o Compilador Faz Isso?
Ele cria activation records.
Também chamados:
Stack frames
Contém:
Variáveis locais
Endereço retorno
Parâmetros
Estado execução
Exatamente igual:
C
C++
Java
Pascal
CALL Estático
Mais eficiente.
CALL 'FATORIAL'
Resolvido no linkedit.
Menos overhead.
CALL Dinâmico
MOVE 'FATORIAL'
TO WS-PGM
CALL WS-PGM
Mais flexível.
Menos rápido.
Existe Limite?
Sim.
STACK.
Região LE.
Memory limits.
Exemplo:
F(1000000)
Boom.
SOC1
SOC4
S878
S80A
Dependendo ambiente.
Como Evitar?
Caso base.
Sempre.
Sempre.
Sempre.
Exemplo ruim:
CALL FATORIAL
CALL FATORIAL
CALL FATORIAL
Nunca termina.
Cuidados Importantes
1 Não usar Working Storage
Errado.
2 Ter condição parada
Obrigatório.
3 Validar entrada
Exemplo:
IF NUMERO < 0
Fatorial negativo?
Não faz sentido.
4 Cuidado com profundidade
100 níveis
Ok.
500
Talvez.
10000
Perigoso.
5 Testar LE Runtime
HEAP
STACK
ALL31
Importante.
Performance
Aqui muitos ficam decepcionados.
Recursão é elegante.
Mas nem sempre rápida.
Cada chamada cria:
Frame
Parâmetros
Contexto
Retorno
Overhead.
Loop tradicional:
PERFORM
UNTIL
Quase sempre vence.
Exemplo
Fatorial iterativo.
MOVE 1 TO WS-TOTAL
PERFORM VARYING I
FROM 1
BY 1
UNTIL I > N
MULTIPLY I
BY WS-TOTAL
END-PERFORM
Muito eficiente.
Quando Vale a Pena?
Árvores.
XML.
JSON.
DOM.
Grafos.
Estruturas hierárquicas.
Menus.
Organogramas.
Filesystem.
Dependências.
Exemplo XML
empresa
departamento
funcionario
departamento
funcionario
Percorrer árvore.
Recursão fica linda.
Segurança
Sim.
Existe aspecto de segurança.
Ataque:
Stack exhaustion.
Negação de serviço.
DoS.
Programa recebe:
99999999
Explode stack.
Proteção:
IF NIVEL > 1000
DISPLAY "ERRO"
STOP RUN
Debug
Debug recursivo é divertido.
E assustador.
Ferramentas:
IBM Debug Tool
Fault Analyzer
Abend Aid
Mostram:
Call Stack
Frame atual
Parâmetros
Muito útil.
Curiosidades
Poucos sistemas bancários usam recursão pesada.
COBOL nasceu para processamento sequencial.
Por isso muitos programadores veteranos passam décadas sem escrever uma única rotina recursiva.
Outra curiosidade:
COBOL OO suporta métodos recursivos naturalmente.
INVOKE SELF
Também funciona.
Recursão de Cauda (Tail Recursion)
Alguns compiladores modernos fazem otimizações.
Mas COBOL IBM geralmente não realiza Tail Call Optimization de forma agressiva como linguagens funcionais.
Logo:
RETURN F(N-1)
Ainda pode consumir stack.
Não confie nisso.
Padrão Recomendado pelo Mestre Bellacosa
Para o jovem Padawan COBOL, eu costumo ensinar uma regra bastante prática:
Use recursão apenas quando ela deixar a solução mais clara do que um PERFORM VARYING.
Se a lógica for:
Fatorial
Somatório
Contador
Prefira iteração.
Se a lógica envolver:
Árvores
XML
JSON aninhado
Catálogos IMS
Dependências complexas
Estruturas organizacionais
Menus multiníveis
Então a recursividade torna-se uma excelente aliada.
Considerações Finais
A recursão em COBOL é quase como encontrar um antigo holocron escondido nos corredores de um datacenter IBM Z.
Muitos veteranos nunca precisaram utilizá-la. Outros a evitam por receio de consumir stack, degradar performance ou provocar abends difíceis de diagnosticar.
No entanto, compreender programas recursivos amplia significativamente o repertório técnico de um desenvolvedor COBOL moderno. Em um mundo onde o mainframe conversa diariamente com APIs REST, processa documentos JSON complexos, integra-se com microsserviços e participa de arquiteturas híbridas, dominar recursividade deixa de ser apenas curiosidade acadêmica e passa a ser uma habilidade estratégica.
O verdadeiro Padawan não utiliza recursão para demonstrar inteligência. Ele a utiliza porque compreende profundamente o problema, conhece os limites da memória, respeita a pilha de execução do Language Environment, escolhe LOCAL-STORAGE sabiamente e sabe exatamente quando um simples PERFORM VARYING é a solução mais elegante.
E essa talvez seja a maior lição do COBOL recursivo:
Nem todo poder disponível deve ser usado. Mas todo poder disponível deve ser compreendido.
No templo Bellacosa Mainframe, conhecer a recursividade significa possuir mais uma ferramenta no cinto utilitário do Sysprog Jedi, pronta para ser acionada quando a missão exigir atravessar estruturas tão profundas quanto os corredores infinitos de uma galáxia IBM Z em plena era do z17.