CANCEL vs INITIAL: Seu subprograma “lembra” da execução anterior

Subprogramas em COBOL: Stateful ou Stateless
Photo by Suzy Hazelwood on Pexels

Entenda por que subprogramas COBOL mantêm estado entre chamadas e como usar CANCEL e IS INITIAL para controlar esse comportamento.

Muita gente imagina que, ao executar um CALL em um subprograma, ele é carregado, executado e, ao terminar, sai da memória. E que, ao chamá-lo novamente, ele começaria do zero.

Mas não é assim que o CALL funciona em COBOL.

Tanto no Linux quanto no mainframe, subprogramas COBOL mantêm estado entre chamadas. Ou seja, eles “lembram” da execução anterior — e isso pode causar comportamentos inesperados se não for levado em consideração.

Subprogramas COBOL são stateful por padrão

Isso significa que o subprograma permanece carregado na memória após o GOBACK, e sua DATA DIVISION mantém os valores da última execução, inclusive na FILE SECTION. Se houver arquivos abertos, eles continuarão abertos.

Esse comportamento pode ser útil em alguns cenários.

Imagine um subprograma que precisa carregar uma tabela interna muito grande. Na primeira execução ele carrega a tabela, mas nas próximas não precisará carregá-la novamente, pois os dados estarão preservados entre cada CALL.

Isso evita processamento repetitivo e melhora a performance geral do sistema.

Quando isso vira problema

Existem casos, porém, em que precisamos que o subprograma seja stateless, ou seja, que ele seja carregado com a garantia de que a DATA DIVISION estará sempre em seu estado original a cada chamada.

Imagine um subprograma complexo, responsável pelo cálculo de juros sobre dívidas atrasadas. Se, por equívoco, o programador esquece de inicializar todas as variáveis de trabalho no início da execução do programa, o resultado gerado em chamadas subsequentes  ao subprograma será afetado.

Para ver isso acontecendo na prática, considere o subprograma abaixo:

identification division.
program-id. y006b.

data division.
working-storage section.
01 wt-contador pic 9(003) value zeros.

procedure division.
add 1 to wt-contador
display "y006b called: contador=" wt-contador
goback.

E um programa principal, que chamará esse subprograma:

identification division.
program-id. y006a.

procedure division.
call "y006b"
call "y006b"
call "y006b"
stop run.

Apesar do subprograma inicializar o contador com VALUE ZEROS, ao ser chamado três vezes pelo programa principal, o que aparecerá na tela é…

y006b called: contador=001
y006b called: contador=002
y006b called: contador=003

Isso acontece porque o valor do contador continuou carregado, entre um CALL e outro.

Mas existem dois métodos em COBOL para evitar que isso aconteça, garantindo que o subprograma se comporte sempre como stateless.

O comando CANCEL

Se você está codificando um programa que vai chamar um subprograma e quer garantir que o subprograma não guarde estados entre cada chamada, você pode usar o comando CANCEL após cada CALL.

Nosso exemplo anterior, ficaria assim:

procedure division.

call "y006b"
cancel "y006b"

call "y006b"
cancel "y006b"

call "y006b"
cancel "y006b"

stop run.

O comando CANCEL retira o subprograma de memória, forçando que ele seja novamente carregado a cada CALL, e isso garante que ele estará em seu estado inicial.

Se você executar o programa novamente, o que aparecerá na tela será:

y006b called: contador=001
y006b called: contador=001
y006b called: contador=001

A opção IS INITIAL

Outra solução para conseguir o mesmo resultado, desta vez atuando diretamente no subprograma, é inserir a cláusula IS INITIAL no PROGRAM-ID do subprograma:

identification division.
program-id. y006b is initial.

Nesse caso, o programa continuará em memória depois do GOBACK, mas sua DATA DIVISION é reiniciada a cada CALL.

Quando usar um ou outro?

Embora o efeito seja semelhante, a performance da solução CANCEL é ligeiramente inferior à solução com IS INITIAL.

Como nem sempre somos autorizados a modificar tanto o programa principal como o subprograma – pois podem fazer parte de sistemas diferentes – meu critério é: se estou codificando o programa principal e quero garantir que o subprograma não guarde estado entre chamadas, eu uso o CANCEL no programa principal.

Se, por outro lado, estou codificando um subprograma que será consumido por diversos sistemas e quero garantir que ele seja stateless,  vou de IS INITIAL.

E, sim, você pode usar os dois métodos se puder alterar os dois programas.

Conclusão

O comportamento padrão do CALL em COBOL pode surpreender. Subprogramas não são reinicializados automaticamente e, por isso, mantêm estado entre execuções.

Se você trabalha com manutenção ou evolução de sistemas COBOL, entender isso nos ajuda a evitar bugs simples, daqueles que nos fazem perder tempo à toa.