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:
| Troca | Sintoma que aparece |
|---|---|
Private por Local | Duas rotinas que deveriam ser independentes leem e sobrescrevem o mesmo valor sem que nenhuma das duas tenha declarado nada em comum |
Local por Static | O valor que devia persistir entre chamadas “aparece do nada” — ou some — na execução seguinte, dentro da mesma thread |
Local por Private | A 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
| Escopo | Quem enxerga | Quanto tempo vive | Onde declarar |
|---|---|---|---|
Local | Só a função que declarou | Até o Return dela | Início da função |
Private | A função e tudo que ela chamar | Até o Return de quem criou | Início da função |
Static (de arquivo) | Todas as funções do mesmo fonte | Enquanto a thread existir | Topo do fonte, fora de função |
Static (de função) | Só a função que declarou | Enquanto a thread existir | Início da função |
Public | Qualquer fonte da thread | Até a thread acabar | Onde 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ê
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 nTotalAs 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 deMsRotAuto(), o padrão testaType("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.INCLUIeALTERA. A rotina padrão cria essas variáveis para dizer em que operação está. A mesmaMsRotAuto()usa_SetOwnerPrvt("inclui", nOpcAuto==3)quandoINCLUIainda 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.
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 !lMsErroAutoDeclarar 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.
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:
| Prefixo | Tipo | Exemplo |
|---|---|---|
c | Caractere | cCodCli |
n | Numérico | nValor |
l | Lógico | lAchou |
d | Data | dEmissao |
a | Array | aItens |
o | Objeto | oModel |
b | Bloco de código | bValida |
x | Indefinido, varia | xRetorno |
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
#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
TDNtdn.totvs.com
TDN — AdvPL, documentação da linguagemTDNtdn.totvs.com
TDN — LocalTDNtdn.totvs.com
TDN — PrivateTDNtdn.totvs.com
TDN — StaticTDNtdn.totvs.com
TDN — Public
Continue por aqui
- Vídeo
DesenvolvimentoTutorial
Arrays em AdvPL: índice em 1, aDel que não encolhe e cópia por referência
Como criar, crescer, procurar, ordenar e remover itens de um array em AdvPL, por que aDel precisa de aSize e por que atribuir um array não copia nada.