Translate

Mostrar mensagens com a etiqueta Árvore B. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta Árvore B. Mostrar todas as mensagens

quarta-feira, 25 de outubro de 2023

COBOL Recursivo sem Mistérios

 

Bellacosa Mainframe dicas e pratica em cobol mainframe recursivo

☕ Um Café no Bellacosa Mainframe

COBOL Recursivo sem Mistérios

Como Programar Funções Recursivas e Percorrer Árvores B como um Oficial da Frota Estelar

"A maioria dos programadores COBOL passa décadas escrevendo programas sem nunca utilizar recursividade. Não porque ela não exista. Mas porque o universo do processamento batch sempre favoreceu algoritmos iterativos. Entretanto, quando você entra no mundo de compiladores, parsers, XML, JSON, árvores de decisão, estruturas hierárquicas e inteligência artificial, descobrirá que existe uma arma secreta escondida dentro do Enterprise COBOL."

Prepare seu café.

Hoje o Capitão Kirk autorizou acesso aos bancos de dados mais profundos da USS Enterprise.

Vamos explorar uma tecnologia que muitos acreditam que COBOL "não possui".

Possui.

E muito bem.


O mito

Existe uma frase repetida há décadas:

"COBOL não suporta recursividade."

Isso era verdade...

...há muitos anos.

Desde o Enterprise COBOL moderno, programas podem chamar a si próprios.

Basta utilizar as opções corretas do compilador.

E entender o que realmente acontece na memória.


O que é recursividade?

Recursividade é quando um programa chama...

...ele mesmo.

Exemplo extremamente simples.

Imagine contar regressivamente.

5
4
3
2
1
Fim

Ao invés de fazer:

PERFORM VARYING

fazemos

CONTAR(5)

↓

CONTAR(4)

↓

CONTAR(3)

↓

CONTAR(2)

↓

CONTAR(1)

Cada chamada cria uma nova execução independente.


Pensando como Spock

Spock não resolveria um problema inteiro.

Ele dividiria.

Sempre.

Existe solução?

↓

Resolva um pedaço

↓

O restante é igual

↓

Chame novamente

Isso é exatamente recursividade.


Como o COBOL consegue fazer isso?

Cada chamada cria uma nova área de trabalho.

Ela contém:

  • variáveis locais

  • parâmetros

  • ponteiros

  • retorno

Tudo fica armazenado na pilha (Stack).

Visualmente.

MAIN

↓

PROGRAMA

↓

PROGRAMA

↓

PROGRAMA

↓

PROGRAMA

Cada nível ocupa memória.


Por isso existe um risco

Se esquecer a condição de parada...

Programa

↓

Programa

↓

Programa

↓

Programa

↓

Programa

↓

Programa

↓

Programa

Nunca termina.

Resultado:

Stack Overflow

Ou

S878

S80A

Storage Exhausted

Dependendo do ambiente.


A regra número 1

Toda função recursiva precisa possuir uma condição de parada.

Sempre.

Exemplo.

IF N = ZERO
    EXIT
END-IF

Sem isso...

adeus memória.


Ativando recursividade

No Enterprise COBOL normalmente utiliza-se

RECURSIVE

na identificação do programa.

IDENTIFICATION DIVISION.

PROGRAM-ID. TREESEARCH
    RECURSIVE.

ou opção equivalente do compilador dependendo da versão.

Outra prática comum é utilizar:

RENT

para permitir reentrância.


Reentrante x Recursivo

São conceitos diferentes.

Reentrante

→ vários usuários usam ao mesmo tempo.

Recursivo

→ o programa chama ele próprio.

Pode existir:

✔ Reentrante

sem ser

✔ Recursivo.


Quando utilizar?

Quando o problema possui natureza hierárquica.

Por exemplo.

Árvore.

XML.

JSON.

AST de compilador.

Pastas.

Menus.

Dependências.

Organogramas.

Genealogia.

Árvore de chamadas.


Imagine uma árvore B

Uma árvore B organiza registros.

             40

      20            60

   10   30      50     70

Encontrar um valor nela é extremamente elegante usando recursividade.


Estrutura lógica

Cada nó possui

Valor

Filho esquerdo

Filho direito

No COBOL real normalmente usamos tabelas e índices.

Exemplo didático.

NODE-ID

LEFT-CHILD

RIGHT-CHILD

Nossa missão

Encontrar

50

Algoritmo

Primeiro olhamos

40

50 é maior.

Então ignoramos todo lado esquerdo.

Seguimos para direita.

60

Agora

50 é menor.

Voltamos para esquerda.

Encontramos

50

Fim.


Em pseudocódigo

SEARCH(NODE)

IF NODE = NULL
    NÃO EXISTE

IF NODE = CHAVE
    ENCONTROU

SE CHAVE < NODE
    SEARCH(LEFT)

SENÃO
    SEARCH(RIGHT)

Perceba.

O algoritmo inteiro possui poucas linhas.

Porque ele reutiliza a própria lógica.


Exemplo COBOL simplificado

IDENTIFICATION DIVISION.
PROGRAM-ID. TREESEARCH RECURSIVE.

WORKING-STORAGE SECTION.

01 WS-KEY          PIC 9(4).

LINKAGE SECTION.

01 LK-NODE.
   05 LK-VALUE     PIC 9(4).
   05 LK-LEFT      POINTER.
   05 LK-RIGHT     POINTER.

PROCEDURE DIVISION USING LK-NODE.

    IF LK-NODE = NULL
        GOBACK
    END-IF

    IF WS-KEY = LK-VALUE
        DISPLAY "ENCONTRADO"
        GOBACK
    END-IF

    IF WS-KEY < LK-VALUE
        CALL "TREESEARCH"
             USING LK-LEFT
    ELSE
        CALL "TREESEARCH"
             USING LK-RIGHT
    END-IF.

    GOBACK.

Este exemplo é conceitual. Em aplicações reais, árvores costumam ser representadas por tabelas indexadas, estruturas dinâmicas com ALLOCATE/FREE (quando suportado) ou áreas obtidas por serviços do sistema.


Observe a mágica

O programa nunca pergunta

Estou no nível 2?

Estou no nível 5?

Estou no nível 30?

Ele simplesmente chama ele mesmo.


Visualizando a pilha

SEARCH(40)

↓

SEARCH(60)

↓

SEARCH(50)

↓

Encontrado

Depois começa retornar.

SEARCH(50)

↓

SEARCH(60)

↓

SEARCH(40)

↓

MAIN

É literalmente uma subida e descida.


O retorno automático

Cada chamada lembra onde parou.

Imagine.

A chama B

↓

B chama C

↓

C chama D

Quando D termina.

Volta para C.

Depois B.

Depois A.

Sem que você precise controlar isso.


Onde COBOL utiliza isso na prática?

Mais do que muitos imaginam.

Ferramentas IBM fazem uso intenso.

Compiladores COBOL.

Parser SQL.

Parser XML.

JSON Parser.

XPath.

XSD.

Analisadores sintáticos.

Motores de regras.


Árvore B em bancos

Db2 utiliza árvores B (B-Trees).

Quando fazemos

SELECT

WHERE CPF

O banco NÃO lê milhões de registros.

Ele navega pela árvore.

Raiz

↓

Nó

↓

Folha

Pouquíssimos acessos.


Curiosidade

Quando você cria

CREATE INDEX

Na prática.

Está construindo uma enorme árvore balanceada.


Então...

Todo programador COBOL usa árvore B.

Mesmo sem perceber.


Mas...

Devemos escrever árvore recursiva sempre?

Não.

Existe um preço.


CPU

Cada chamada possui custo.

Salvar registradores

↓

Criar stack frame

↓

Passar parâmetros

↓

Retornar

Tudo isso consome CPU.


Memória

Cada chamada cria.

Variáveis

Endereço retorno

Parâmetros

Estado

Imagine 100.000 níveis.

Pode explodir.


Comparando

Iterativo

WHILE

Consome

CPU menor

Memória fixa

Recursivo

CPU maior

Stack crescente

Então por que usar?

Porque alguns problemas ficam absurdamente mais simples.

Exemplo.

Árvore.

Iterativo

300 linhas

Recursivo

40 linhas

Mais fácil.

Mais elegante.

Menos bugs.


Quando evitar?

Processamento sequencial.

Leitura VSAM.

Arquivo QSAM.

Loops simples.

Relatórios.

Batch tradicional.

Nestes casos.

PERFORM VARYING

vence.


Tail Recursion

Existe uma otimização famosa.

Tail Recursion.

Função termina chamando ela mesma.

Alguns compiladores eliminam o crescimento da pilha.

Infelizmente.

Nem todo compilador COBOL faz isso.

Portanto.

Nunca conte com essa otimização.


Cuidado com milhões de chamadas

Imagine uma árvore degenerada.

10

 \

 20

   \

   30

     \

     40

Ela parece uma lista.

A recursividade fará milhares de chamadas.

Ruim.

Árvores balanceadas evitam isso.


B-Tree resolve exatamente este problema

Ela mantém altura pequena.

Mesmo com milhões de registros.

É justamente por isso que bancos usam B-Tree.

Não Binary Tree simples.


Dica de ouro

Nunca escreva recursividade sem antes responder:

Qual é minha condição de parada?

Se não conseguir responder.

Ainda não terminou o algoritmo.


Outra dica

Desenhe.

Sempre.

Árvores ficam muito mais fáceis no papel.


Debug

Durante testes faça:

DISPLAY

Mostrando o nível.

DISPLAY "LEVEL=" WS-NIVEL

Assim você visualiza a profundidade.


Performance em Mainframe

No IBM Z.

CPU é dinheiro.

Cada microssegundo importa.

Por isso.

Recursividade costuma aparecer mais em:

  • middleware

  • compiladores

  • parsers

  • XML

  • JSON

  • IA

  • engines

Do que em batch financeiro.


Curiosidade histórica

Nos anos 70.

Poucos compiladores COBOL aceitavam recursividade.

A memória era extremamente cara.

Muitas máquinas tinham poucos megabytes.

Era impensável desperdiçar stack.

Hoje.

Servidores IBM Z possuem centenas de gigabytes.

O cenário mudou.


Boas práticas

✔ Sempre tenha condição de parada clara.

✔ Documente a lógica antes de codificar.

✔ Prefira árvores balanceadas.

✔ Limite profundidade quando possível.

✔ Evite variáveis globais compartilhadas.

✔ Teste casos extremos.

✔ Monitore consumo de CPU e memória.

✔ Utilize recursividade apenas quando ela realmente simplifica o problema.

✔ Faça revisão de código focando em chamadas recursivas.

✔ Meça desempenho antes de concluir que a solução é "rápida".


Armadilhas comuns

❌ Esquecer a condição de parada.

❌ Modificar dados globais inesperadamente.

❌ Assumir que toda árvore é balanceada.

❌ Ignorar consumo de stack.

❌ Trocar elegância por complexidade desnecessária.

❌ Usar recursividade onde um PERFORM VARYING resolveria de forma mais simples.


Recursividade e Enterprise COBOL

As versões modernas do IBM Enterprise COBOL oferecem suporte a programas recursivos, mas é importante observar alguns detalhes:

  • Declare o programa como RECURSIVE quando necessário.

  • Utilize opções de compilação adequadas ao ambiente, frequentemente combinadas com RENT em aplicações compartilhadas.

  • Consulte sempre o padrão adotado pela sua empresa e a documentação da versão do compilador em uso, pois políticas de compilação variam entre instalações.

Em ambientes CICS, IMS ou aplicações de alta concorrência, também é essencial compreender os conceitos de reentrância, armazenamento automático e áreas de trabalho para evitar efeitos colaterais entre execuções simultâneas.


Missão para o Padawan COBOL

Depois de dominar este artigo, experimente implementar os seguintes desafios:

  1. Fatorial usando recursividade.

  2. Sequência de Fibonacci (comparando desempenho com versão iterativa).

  3. Percorrer uma árvore binária em ordem (in-order).

  4. Percorrer uma árvore em pré-ordem (pre-order).

  5. Percorrer uma árvore em pós-ordem (post-order).

  6. Simular um índice de clientes usando uma árvore binária simples.

  7. Comparar o tempo de busca entre uma tabela sequencial e uma árvore.

  8. Criar um visualizador com DISPLAY mostrando o nível de cada chamada recursiva.

Cada exercício ajudará você a entender não apenas como a recursividade funciona, mas quando ela é realmente a melhor ferramenta.

Conclusão — O Holodeck da Recursividade

Existe uma lição que diferencia um programador comum de um verdadeiro oficial da Frota Estelar.

O iniciante procura resolver problemas escrevendo mais código.

O engenheiro experiente procura resolver problemas encontrando a estrutura correta.

A recursividade é exatamente isso: uma mudança de perspectiva. Em vez de atacar um problema gigantesco de uma única vez, você o divide em pequenas partes idênticas, permitindo que o próprio algoritmo repita a solução até alcançar a condição de parada.

No universo do COBOL, ela não substitui os tradicionais PERFORM VARYING, nem foi criada para processar milhões de registros sequenciais de um batch financeiro. Seu verdadeiro poder aparece quando trabalhamos com estruturas hierárquicas: árvores B, XML, JSON, compiladores, interpretadores, mecanismos de regras, grafos e diversos algoritmos modernos que fazem parte da computação atual.

Como diria o Sr. Spock:

"A solução mais elegante normalmente é aquela que respeita a estrutura natural do problema."

Quando você compreender essa filosofia, deixará de enxergar a recursividade como um truque de linguagem e passará a vê-la como uma ferramenta de modelagem.

E esse é um dos momentos em que um Padawan COBOL começa a trilhar o caminho para se tornar um verdadeiro Mestre do Mainframe.