Translate

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.

Sem comentários:

Enviar um comentário