Pragiva — Tecnologia na prática

Condições e laços em AdvPL: If, Do Case, IIf, For e While

Condições e laços em AdvPL: If e Do Case, IIf numa linha, For com Step, While com Exit e Loop, e o laço infinito que trava a thread.

Desenvolvimento Tutorial 4 min de leitura

Assista o vídeo

Resposta rápida

Decidir
If/ElseIf/Else/EndIf ou Do Case/Case/Otherwise/EndCase; nos dois, o primeiro teste verdadeiro vence e os demais são ignorados.
Decidir numa linha
IIf(condição, valorSeVerdadeiro, valorSeFalso), para escolher um valor, não para executar lógica.
Repetir sabendo quantas vezes
For nX := 1 To n [Step k]... Next.
Repetir até uma condição mudar
While condição... EndDo, e o corpo precisa fazer a condição mudar.
Desviar dentro do laço
Exit sai do laço; Loop pula para a próxima volta.
Nesta página8 seções

Este é o sexto texto da série. Os testes que aparecem aqui usam os operadores de Operadores em AdvPL, e os laços percorrem as estruturas de Arrays em AdvPL.

If: a decisão que você vai escrever mil vezes

Exemplo: faixa de desconto por valor do pedido.

ADVPL / TLPP
If nValor >= 10000
    nDesc := 10
ElseIf nValor >= 5000
    nDesc := 5
ElseIf nValor >= 1000
    nDesc := 2
Else
    nDesc := 0
EndIf

A ordem dos testes é a regra de negócio. Um pedido de 12 mil satisfaz as três condições, mas só a primeira executa. Inverta a ordem — >= 1000 no topo — e todo pedido acima de mil ganha 2%, sem erro nenhum de compilação.

A condição precisa ser lógica. If cTipo com cTipo caractere não é “se tiver conteúdo”: é erro de execução. Teste o que você quer dizer — !Empty(cTipo), cTipo == "N". Os tipos estão em Tipos de dados em AdvPL.

Do Case: a mesma coisa, mais legível com muitos ramos

ADVPL / TLPP
Do Case
    Case nValor >= 10000
        nDesc := 10
    Case nValor >= 5000
        nDesc := 5
    Case nValor >= 1000
        nDesc := 2
    Otherwise
        nDesc := 0
EndCase

O comportamento é o do If encadeado: os Case são avaliados de cima para baixo, o primeiro verdadeiro executa e o bloco termina — não existe “cair” para o próximo, como no switch de outras linguagens, e não é preciso break. Otherwise é opcional e faz o papel do Else.

Escolher entre os dois é questão de leitura: com muitos ramos, Do Case deixa as condições alinhadas na margem. Do Case/EndCase é a forma herdada do Clipper, de onde vem boa parte da sintaxe do AdvPL, e por isso ainda é a mais comum em código legado do próprio padrão.

IIf: escolher um valor, não executar lógica

ADVPL / TLPP
cSituacao := IIf(nSaldo > 0, "Em aberto", "Quitado")

IIf() é função da própria linguagem: recebe a condição e dois valores e devolve um deles. Serve para montar texto, escolher um parâmetro, preencher um campo numa linha só.

Dois cuidados. IIf dentro de IIf é um Do Case escondido, que o próximo a mexer no fonte vai ter que desmontar. E não coloque nos ramos chamadas que gravam, travam registro ou mostram mensagem: se um ramo faz alguma coisa, ele pertence a um If.

For/Next: quando você sabe quantas voltas

ADVPL / TLPP
nTotal := 0
For nX := 1 To Len(aItens)
    nTotal += aItens[nX, 2]
Next nX

O nome da variável depois do Next é opcional, mas em laço aninhado ele documenta qual laço fecha ali.

Step muda o incremento. O uso mais útil é o laço de trás para frente, que o padrão usa, por exemplo, na função CTB276AtuPer(), para ajustar diferença de rateio na última linha que comporta:

ADVPL / TLPP
For nX := Len(aItens) To 1 Step -1
    If aItens[nX, 2] <= 0
        aDel(aItens, nX)
        aSize(aItens, Len(aItens) - 1)
    EndIf
Next nX

Removendo elementos, andar de trás para frente é o que evita pular posição: cada remoção desloca só o que já foi visitado.

While/EndDo: quando a parada depende do dado

O caso clássico é percorrer os itens de um pedido na SC6 pelo índice 1 (C6_FILIAL+C6_NUM+C6_ITEM+C6_PRODUTO):

ADVPL / TLPP
Local nTotPed := 0

SC6->(DbSetOrder(1))
SC6->(DbSeek(xFilial("SC6") + cPedido))

While SC6->(!Eof()) .And. SC6->C6_FILIAL == xFilial("SC6") ;
        .And. SC6->C6_NUM == cPedido

    nTotPed += SC6->C6_VALOR

    SC6->(DbSkip())
EndDo

A condição tem três partes e todas importam. !Eof() para no fim da tabela; filial e número param no último item deste pedido — sem eles, o laço segue somando os pedidos seguintes até o fim do arquivo. Como esse laço move o ponteiro da SC6, quem chama costuma esperar a área restaurada: isso é GetArea e RestArea. E se a pergunta for só “quanto soma”, uma consulta SQL resolve sem laço.

Confira no seu ambiente:

Confira no seu ambienteSQL
SELECT C6_NUM, COUNT(*) AS ITENS, SUM(C6_VALOR) AS TOTAL
  FROM SC6010
 WHERE D_E_L_E_T_ = ' '
 GROUP BY C6_NUM

O laço infinito

ADVPL / TLPP
While SC6->(!Eof()) .And. SC6->C6_NUM == cPedido
    If SC6->C6_VALOR <= 0
        Loop
    EndIf
    nTotPed += SC6->C6_VALOR
    SC6->(DbSkip())
EndDo

Parece certo. O primeiro item com valor zerado prende a thread para sempre: Loop volta para o teste sem passar pelo DbSkip(), e a condição continua verdadeira. Não há erro nem log — a tela congela, ou o job roda até alguém derrubar a conexão no monitor.

A regra prática: em While, o avanço vem antes de qualquer Loop. Ou o DbSkip() sobe para antes do Loop, ou o teste inverte e o acúmulo fica dentro do If:

ADVPL / TLPP
While SC6->(!Eof()) .And. SC6->C6_NUM == cPedido
    If SC6->C6_VALOR > 0
        nTotPed += SC6->C6_VALOR
    EndIf
    SC6->(DbSkip())
EndDo

O mesmo vale para contador: While nTent < 3 sem nTent++ no corpo não termina nunca.

Exit e Loop

Exit encerra o laço mais interno e segue para a linha depois do Next ou do EndDo — é o jeito limpo de parar uma busca quando o item já foi achado (para busca simples em array, aScan() resolve em uma linha). Loop pula o resto do corpo e vai para a próxima volta; no For, o incremento acontece normalmente, então o risco de laço infinito é só do While.

Com laços aninhados, Exit sai apenas do laço onde está. Se o achado precisa encerrar os dois, use uma variável lógica e teste-a no laço de fora.

Qual usar

SituaçãoEstrutura
Dois ou três caminhosIf/ElseIf/Else
Muitos caminhos com testes diferentesDo Case
Escolher um valor numa expressãoIIf()
Percorrer array ou repetir n vezesFor/Next
Remover itens de arrayFor... Step -1
Percorrer registros até mudar a chaveWhile/EndDo com DbSkip()

A estrutura do fonte onde tudo isso mora está em Estrutura de um fonte AdvPL, e o escopo das variáveis dos exemplos, em Escopo de variáveis em AdvPL.


Laço travando thread em produção? Fale com a gente.

Referências

  1. TDNtdn.totvs.com

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

    TDN — IIf

Continue por aqui