Translate

Mostrar mensagens com a etiqueta COBOL Recursivo. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta COBOL Recursivo. Mostrar todas as mensagens

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.


sexta-feira, 31 de março de 2023

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


 

Bellacosa Mainframe apresenta cobol recursivo parte II sec b

☕ Um Café no Bellacosa Mainframe

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

Quando a Recursividade Deixa de Ser Exercício e Passa a Resolver Problemas Reais no IBM Mainframe

"Depois que um programador entende o Call Stack, surge uma pergunta inevitável: se a recursão é mais lenta que um PERFORM, por que compiladores, bancos de dados, processadores XML e até o próprio z/OS utilizam algoritmos recursivos? A resposta está no tipo de problema que estamos tentando resolver."


Introdução

Na Parte 2A utilizamos o exemplo do fatorial.

Foi uma excelente ferramenta didática.

Mas sejamos honestos.

Você dificilmente encontrará um sistema bancário calculando fatoriais durante o fechamento do dia.

Da mesma forma, poucas empresas utilizam Fibonacci em produção.

Esses algoritmos são importantes porque ensinam os fundamentos.

Entretanto, a verdadeira força da recursividade aparece quando o problema deixa de ser linear.

É exatamente aí que muitos programadores COBOL começam a enxergar a diferença entre escrever código e modelar problemas.

Hoje vamos abandonar os exemplos acadêmicos e explorar situações que realmente aparecem em software corporativo.


O primeiro sinal de que um problema pode ser recursivo

Existe uma pergunta simples.

"O objeto que estou processando pode conter outro objeto igual a ele?"

Se a resposta for sim...

Existe uma grande chance de a recursividade ser uma excelente solução.

Observe.

Uma pasta contém...

Pastas.

Um departamento possui...

Subdepartamentos.

Um XML contém...

Outros elementos XML.

Um menu possui...

Submenus.

Uma árvore possui...

Galhos.

Cada galho possui...

Outros galhos.

O próprio problema descreve sua solução.


Fibonacci: o exemplo que ensina e também alerta

Depois do fatorial, Fibonacci é provavelmente o algoritmo recursivo mais famoso.

F(0)=0

F(1)=1

F(2)=1

F(3)=2

F(4)=3

F(5)=5

F(6)=8

Sua definição é extremamente elegante.

F(N)=F(N-1)+F(N-2)

Visualmente.

               F(6)

          /            \

      F(5)            F(4)

     /    \          /    \

 F(4)    F(3)    F(3)    F(2)

Perceba algo importante.

A mesma conta aparece diversas vezes.

F(4).

F(3).

F(2).

Tudo é recalculado.


O lado sombrio da recursividade

Embora o algoritmo seja bonito...

Ele é extremamente ineficiente.

Imagine calcular:

F(50)

Milhares de chamadas serão repetidas.

A árvore cresce exponencialmente.

É um excelente exemplo de como uma solução elegante pode se tornar um desastre de desempenho.

É por isso que muitos algoritmos recursivos modernos utilizam Memoization (armazenar resultados já calculados) ou são convertidos em versões iterativas quando desempenho extremo é necessário.

Essa é uma lição importante para qualquer Padawan: nem toda recursão elegante é uma boa solução para produção.


Árvores: onde a recursividade realmente brilha

Imagine o organograma de uma empresa.

                Presidente

        /          |           \

 Financeiro       RH          Tecnologia

                 /   \

           Folha     Recrutamento

Cada departamento pode possuir outros departamentos.

Cada um deles pode possuir outros.

Não existe um limite fixo.

Como percorrer isso?


Solução utilizando PERFORM

É possível.

Mas rapidamente surgem:

  • tabelas auxiliares;

  • índices;

  • controle de profundidade;

  • pilhas artificiais;

  • lógica de retorno.

O código cresce.

A manutenção se torna complicada.


Solução recursiva

A lógica passa a ser quase uma descrição em português.

Processe este departamento.

Para cada filho

    processe o filho.

Observe.

A estrutura do algoritmo é praticamente igual à estrutura da árvore.

Esse é o verdadeiro poder da recursividade.


XML: um dos melhores exemplos no Mainframe

Hoje praticamente todo ambiente IBM Z conversa com XML.

Imagine.

<empresa>

   <departamento>

      <funcionario>

         <dependente/>

      </funcionario>

   </departamento>

</empresa>

Cada elemento pode conter outros elementos.

Que podem conter outros elementos.

Que podem conter outros elementos.

Até centenas de níveis.

Como percorrer isso?

Recursivamente.


Visualmente.

Empresa

↓

Departamento

↓

Funcionário

↓

Dependente

Cada elemento executa exatamente o mesmo algoritmo.

"Procure meus filhos."

Se encontrar.

Execute novamente.


JSON segue exatamente a mesma ideia

Imagine.

{
   "cliente":
   {
      "endereco":
      {
         "cidade":
         {
            "bairro":"..."
         }
      }
   }
}

Cada objeto contém outro objeto.

Cada objeto utiliza exatamente a mesma lógica.

É uma árvore.

E árvores gostam de recursão.


O XML PARSER do Enterprise COBOL

Poucos desenvolvedores sabem disso.

Quando utilizamos o recurso XML PARSE do Enterprise COBOL, internamente existe um processamento altamente estruturado para percorrer nós, atributos e elementos aninhados.

Embora a implementação interna da IBM utilize diversas otimizações, o conceito fundamental continua sendo o de navegar por uma estrutura hierárquica.

Esse é um excelente exemplo de como compreender recursividade ajuda a entender tecnologias que usamos diariamente, mesmo sem escrever chamadas recursivas explicitamente.


Sistemas de menus

Imagine um sistema.

Cadastros

↓

Clientes

↓

Endereços

↓

Histórico

Outro menu.

Financeiro

↓

Pagamentos

↓

Boletos

Tudo isso pode ser representado por árvores.

A rotina que desenha um menu pode chamar a si própria sempre que encontrar um submenu.


Diretórios do z/OS UNIX

Embora muitos profissionais associem Mainframe apenas a datasets, o z/OS possui um ambiente UNIX extremamente robusto.

Imagine.

/

├── u

│     ├── vagner

│     ├── projetos

│     └── scripts

└── tmp

Uma rotina que lista diretórios faz exatamente isto.

Entre.

Liste arquivos.

Encontrou pasta?

Entre novamente.

Esse algoritmo praticamente escreve sozinho utilizando recursividade.


Busca em Profundidade (DFS)

Um dos algoritmos mais importantes da Computação.

Depth First Search.

Visualmente.

          A

      /      \

     B        C

   /   \       \

  D     E       F

A busca acontece assim.

A

↓

B

↓

D

↑

B

↓

E

↑

A

↓

C

↓

F

Observe.

Sempre mergulhamos o máximo possível.

Depois retornamos.

Depois continuamos.

Isso acontece naturalmente utilizando recursão.


Busca em Largura (BFS)

Já a Breadth First Search prefere percorrer nível por nível.

Ela normalmente utiliza filas.

Não recursão.

Esse é um ótimo exemplo de que nem todo algoritmo sobre árvores precisa ser recursivo.

O desenvolvedor deve escolher a ferramenta adequada ao problema.


QuickSort

Outro algoritmo clássico.

Imagine.

15

↓

Escolha pivô

↓

Menores

Maiores

↓

Repita para cada lado

Cada metade repete exatamente o mesmo algoritmo.

Recursivamente.


MergeSort

Visualmente.

64 15 10 5

↓

64 15

10 5

↓

64

15

10

5

Quando cada pedaço está ordenado.

Eles são reunidos novamente.

O algoritmo divide.

Depois conquista.

Exatamente como o fatorial.


Divide and Conquer

Existe uma família inteira de algoritmos baseada nesse princípio.

  1. Dividir o problema.

  2. Resolver problemas menores.

  3. Unir os resultados.

Sempre que enxergamos esse padrão, vale a pena perguntar:

"A recursividade simplifica esta solução?"


Árvore B e índices de banco de dados

Db2.

IMS.

VSAM.

Sistemas de arquivos.

Todos utilizam árvores internamente.

Imagine procurar um registro.

Você começa na raiz.

Depois escolhe um galho.

Depois outro.

Depois outro.

Até encontrar a folha.

Embora o código interno dos produtos IBM seja altamente otimizado e frequentemente utilize abordagens iterativas para reduzir consumo de pilha, a lógica conceitual é equivalente ao caminhamento recursivo de uma árvore.


Compiladores

Pouca gente imagina.

Mas compiladores adoram recursão.

Imagine.

A+B*C

Transforma-se em.

          +

      /       \

     A         *

            /     \

           B       C

Cada nó da árvore representa uma operação.

O compilador percorre.

Depois gera código.

Esse processo é conhecido como navegação por uma Árvore Sintática Abstrata (AST).

Mesmo quando a implementação final utiliza otimizações sofisticadas, pensar recursivamente torna a construção dessas estruturas muito mais natural.


Interpretadores

Motores de regras.

Interpretadores.

Calculadoras.

Expressões matemáticas.

Todos trabalham sobre árvores.

Por isso a recursão aparece com frequência.


Onde isso aparece no IBM Z?

Mais do que muitos imaginam.

Alguns exemplos.

  • Processamento XML.

  • Navegação JSON.

  • Ferramentas de análise sintática.

  • Produtos que interpretam linguagens.

  • Utilitários de transformação de documentos.

  • Ferramentas de administração de diretórios USS.

  • Alguns componentes de compiladores e analisadores.

Mesmo que você nunca implemente esses produtos, entender como eles funcionam amplia sua capacidade de projetar soluções.


Um erro muito comum

Depois de aprender recursão.

Alguns desenvolvedores tentam utilizá-la em tudo.

Por exemplo.

Ler arquivo VSAM

↓

Processar registro

↓

Chamar novamente

Não.

Esse é um processamento linear.

O correto continua sendo.

PERFORM UNTIL EOF

Recursão aqui apenas aumenta:

  • consumo de memória;

  • número de chamadas;

  • profundidade da pilha;

  • tempo de execução.

Sem qualquer benefício.


Um teste simples

Antes de decidir.

Pergunte.

Meu problema é:

  • Linear?

ou

  • Hierárquico?

Se for linear.

Use PERFORM.

Se for uma estrutura em árvore.

A recursão provavelmente merece consideração.


A maior lição desta parte

A recursividade não existe para substituir laços.

Ela existe para modelar problemas cuja própria natureza é recursiva.

Quando a estrutura do problema se repete dentro dela mesma, a solução recursiva tende a ser mais elegante, mais legível e mais próxima da forma como pensamos o domínio do problema.

É por isso que ela continua relevante, mesmo em uma linguagem tradicionalmente associada ao processamento sequencial como o COBOL.


Easter Egg Bellacosa ☕

Existe uma curiosidade interessante que raramente aparece em cursos de COBOL.

Quando um programador aprende recursão, normalmente acredita que o objetivo é escrever programas que chamam a si mesmos.

Na prática, o maior ganho costuma ser outro.

Depois de estudar recursividade, muitos desenvolvedores passam a compreender muito melhor:

  • por que existe LOCAL-STORAGE;

  • como funciona o CALL BY REFERENCE;

  • por que o Language Environment (LE) precisa administrar uma pilha de execução;

  • como compiladores preservam contexto entre chamadas;

  • por que produtos como Db2, CICS, XML PARSER e diversos componentes do z/OS conseguem processar estruturas complexas de forma organizada.

Curiosamente, alguns dos programadores COBOL mais experientes que você encontrará talvez nunca tenham escrito uma rotina recursiva em produção. Ainda assim, eles entendem profundamente o conceito porque sabem que recursão é uma ferramenta para compreender arquitetura, não apenas um estilo de programação.


Encerrando o Café

Se o fatorial nos ensinou como uma chamada recursiva nasce e morre, os exemplos desta parte mostram por que essa técnica continua viva mais de sessenta anos depois do surgimento do COBOL.

Árvores, XML, JSON, compiladores, algoritmos de busca e estruturas hierárquicas compartilham uma característica em comum: todos descrevem problemas que contêm versões menores de si mesmos.

É exatamente nesses cenários que a recursividade deixa de ser um exercício de faculdade e passa a ser uma poderosa ferramenta de modelagem.

No próximo café, vamos olhar para a recursão sob a ótica de um Programador Mainframe Sênior. Analisaremos desempenho, consumo de memória, profundidade de pilha, Tail Recursion, otimizações do compilador, depuração, análise de dumps, interação com o Language Environment e diversas dicas práticas para decidir, com segurança, quando usar — e principalmente quando evitar — a recursividade em aplicações Enterprise COBOL.

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.