Pragiva — Tecnologia na prática

Escopo de variáveis em AdvPL: Local, Private, Static e Public

Local, Private, Static e Public em AdvPL: quem enxerga cada variável, quanto tempo ela vive e por que Local é o padrão.

Desenvolvimento Tutorial 5 min de leitura

Assista o vídeo

Resposta rápida

Padrão
Local, sempre que a variável não precisa sair da função.
Private
visível nas funções chamadas abaixo dela, até a função que a criou terminar.
Static
sobrevive entre chamadas; visível no fonte ou só na função onde foi declarada.
Public
visível na thread inteira até ela acabar. Quase nunca é a resposta.
Constante
#define, trocada pelo pré-compilador antes de compilar.
Nesta página9 seções

Escopo responde a duas perguntas: quem enxerga a variável e até quando ela existe. Errar a primeira gera colisão de nome; errar a segunda gera valor que “aparece do nada” na segunda execução. Este é o segundo texto da série; o primeiro trata da estrutura de um fonte AdvPL.

O sintoma aponta o escopo

Três sintomas comuns, e o escopo que costuma estar por trás de cada um:

TrocaSintoma que aparece
Private por LocalDuas rotinas que deveriam ser independentes leem e sobrescrevem o mesmo valor sem que nenhuma das duas tenha declarado nada em comum
Local por StaticO valor que devia persistir entre chamadas “aparece do nada” — ou some — na execução seguinte, dentro da mesma thread
Local por PrivateA rotina padrão chamada logo abaixo não enxerga a variável que você criou só para ela, e segue como se você não tivesse preenchido nada

A tabela que resume tudo

EscopoQuem enxergaQuanto tempo viveOnde declarar
LocalSó a função que declarouAté o Return delaInício da função
PrivateA função e tudo que ela chamarAté o Return de quem criouInício da função
Static (de arquivo)Todas as funções do mesmo fonteEnquanto a thread existirTopo do fonte, fora de função
Static (de função)Só a função que declarouEnquanto a thread existirInício da função
PublicQualquer fonte da threadAté a thread acabarOnde for criada

A palavra que importa na coluna do meio é thread. No Protheus cada conexão de usuário, job ou requisição REST roda na sua própria thread. Uma Static não é compartilhada entre dois usuários; ela é compartilhada entre duas chamadas do mesmo usuário.

Local: o padrão, e por quê

ADVPL / TLPP
User Function PGV001()
    Local cCliente := "000001"
    Local nTotal   := 0

    nTotal := PGVSoma(cCliente)
Return nTotal

Static Function PGVSoma(cCodigo)
    Local nTotal := 100   // outra variável: não toca na de cima
Return nTotal

As duas nTotal não se conhecem. Cada função é uma caixa fechada: o que entra vem por parâmetro, o que sai vai pelo Return. Lendo só a função, você sabe tudo que ela usa, e isso é o que torna o código revisável.

Private: a variável que atravessa a pilha

Private é criada em tempo de execução e fica visível para toda função chamada abaixo de quem a declarou. Quando essa função termina, a variável some. Se uma Private de mesmo nome já existia mais acima, a nova esconde a antiga até o fim da função.

Ela existe porque o padrão do Protheus depende dela. Dois casos que todo desenvolvedor encontra cedo:

  • lMsErroAuto. Quem chama uma rotina automática declara a variável; a rotina padrão a enxerga e marca o erro nela. Dentro de MsRotAuto(), o padrão testa Type("lMSErroAuto") == "U" e só cria a variável se ela ainda não existir — é por isso que declarar a sua antes da chamada é obrigatório: depois que o padrão cria a dele, a leitura recai sobre essa cópia interna, não sobre a que você criou. A história completa está em MsExecAuto: como descobrir por que a rotina falhou.
  • INCLUI e ALTERA. A rotina padrão cria essas variáveis para dizer em que operação está. A mesma MsRotAuto() usa _SetOwnerPrvt("inclui", nOpcAuto==3) quando INCLUI ainda não é lógica. Seu gatilho, sua validação e seu ponto de entrada leem esse valor sem nunca tê-lo recebido por parâmetro.
ADVPL / TLPP
User Function PGV002(aCabec)
 Private lMsErroAuto := .F. // precisa ser Private: a rotina padrão escreve nela

    MsExecAuto({|x, y| MATA030(x, y)}, aCabec, 3)

    If lMsErroAuto
        MostraErro()
    EndIf
Return !lMsErroAuto

Declarar lMsErroAuto como Local aqui é um erro clássico: a rotina padrão não enxerga a sua variável, cria a dela, e a sua continua .F. mesmo quando a inclusão falhou.

Do lado de quem lê uma Private alheia, a regra é conferir antes de usar, com Type("INCLUI") == "L". Em ponto de entrada isso é obrigatório: o mesmo PE pode ser chamado por tela, por ExecAuto ou por job, e cada caminho cria um conjunto diferente de variáveis. O contexto de quem chama está em Ponto de entrada no Protheus.

Static: memória entre chamadas

No topo do fonte, fora de função, a Static é vista por todas as funções daquele arquivo e por nenhum outro. Declarada dentro de uma função, só aquela função a vê, mas o valor continua lá na chamada seguinte. O próprio padrão usa esse recurso: o fonte que implementa MsRotAuto() declara no topo uma Static de arquivo chamada __aCache.

ADVPL / TLPP
Static __aCacheTes := {}   // uma cópia por thread, visível só neste fonte

Static Function PGVTesCache(cTes)
    Local nPos := aScan(__aCacheTes, {|x| x[1] == cTes})

    If nPos == 0
        aAdd(__aCacheTes, {cTes, PGVLeTes(cTes)})
        nPos := Len(__aCacheTes)
    EndIf
Return __aCacheTes[nPos, 2]

O uso legítimo é esse: cache de algo caro que não muda durante a execução. Num job que roda por horas, a Static acumula valor de uma iteração para a outra, e um cache sem limpeza vira dado velho ou memória que só cresce.

Public: quase nunca

Public fica visível em qualquer fonte da thread até ela acabar. Ninguém sabe, lendo uma função, quem mais escreve naquela variável. Duas customizações de fornecedores diferentes que escolhem o mesmo nome se sobrescrevem em silêncio.

Se o valor precisa atravessar fontes, passe por parâmetro. Se precisa durar entre chamadas do mesmo fonte, use Static de arquivo. Sobra muito pouco caso real para Public.

Notação húngara: o tipo no nome

O AdvPL não obriga a declarar o tipo, então a convenção carrega essa informação no prefixo do nome:

PrefixoTipoExemplo
cCaracterecCodCli
nNumériconValor
lLógicolAchou
dDatadEmissao
aArrayaItens
oObjetooModel
bBloco de códigobValida
xIndefinido, variaxRetorno

Não é enfeite. Um If nValor salta aos olhos como erro; um If valor não. Os tipos em si estão no próximo texto da série, tipos de dados em AdvPL.

Constantes com #define

ADVPL / TLPP
#Include "Protheus.ch"

#define PGV_OPC_INCLUIR  3

MsExecAuto({|x, y| MATA030(x, y)}, aCabec, PGV_OPC_INCLUIR)

#define não é variável: o pré-compilador troca o nome pelo valor antes de compilar. Não ocupa memória, não muda em execução e vale só no fonte onde foi definido (ou em quem incluir o .ch que o contém). A convenção é caixa alta, para que ninguém tente atribuir valor a ela. O número mágico 3 vira uma palavra que explica a si mesma.

Em uma frase

Comece tudo como Local. Use Private só quando o padrão exige, Static só para cache consciente, e desconfie de qualquer Public que encontrar. Depois dos tipos, a série segue para arrays.


Se uma customização sua se comporta diferente quando chamada por outra rotina, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — AdvPL, documentação da linguagem
  2. TDNtdn.totvs.com

    TDN — Local
  3. TDNtdn.totvs.com

    TDN — Private
  4. TDNtdn.totvs.com

    TDN — Static
  5. TDNtdn.totvs.com

    TDN — Public

Continue por aqui