Técnicas de Programação Eficiente em AdvPL
Abrangência
Versão 5.07 | Versão 5.08 | Versão 6.09 | Versão 7.10 | Versão 8.11 |
Para o desenvolvimento de sistemas e a programação de rotinas, sempre é esperado que qualquer código escrito seja:
de correto funcionamento
eficiente
legível
reutilizável
extensível
portável
Após anos de experiência na utilização de linguagens padrão xBase e do desenvolvimento da linguagem AdvPl, algumas técnicas para uma programação otimizada e eficiente foram reconhecidas. A utilização das técnicas a seguir, visa buscar o máximo aproveitamento dos recursos da linguagem com o objetivo de criar programas com estas características.
Criação de Funções Segundo a Necessidade
Observe o código de exemplo:
User Function GetAnswer(lDefault)
Local lOk
lOk := GetOk(lDefault)
If lOk
Return .T.
Else
Return .F.
Endif
Return nil
Utilizando-se apenas o critério "a função funciona corretamente?", a função GetAnswer é perfeita. Recebe um parâmetro lógico com a resposta padrão e retorna um valor lógico dependente da opção escolhida pelo usuário em uma função de diálogo "sim/não" designada para isso. Pode entretanto ser melhorada, particularmente se eficiência for considerada como um critério para um código melhor. Eficiência tipicamente involve a utilização de poucos recursos de máquina, poucos chamadas de funções ou tornar mais rápido um processo.
Segundo esse raciocínio, poderia se produzir o seguinte código:
User Function GetAnswer(lDefault)
Return If( GetOk(lDefault), .T., .F.)
Ou melhor:
User Function GetAnswer(lDefault)
Return GetOk(lDefault)
Com a otimização do código da função GetAnswer, pode facilmente verificar que a mesma não realiza nada adicional à chamada de GetOk, podendo ser substituída por uma chamada direta desta, continuando a funcionar corretamente.
Codificação Auto-Documentável
Nenhum comentário substitui um código claramente escrito, e este não é um um acidente. Considere o exemplo:
cVar := " " // 11 espaços
O tamanho da variável cVar não é evidente por si só e não é facilmente verificado. Estes mesmos 10 espaços estariam mais óbvios e ainda assim garantidos se a instrução fosse escrita como:
cVar := Space(11)
O mesmo princípio pode ser aplicado para qualquer string longa de caracteres repetidos. A função Replicate pode ser utilizada como a seguir:
cVar := Replicate( "*", 80 )
Este tipo de programação deixa o código fácil de digitar, fácil de ler e mais flexível.
Utilização de Soluções Simples
Simplicidade na criação de instruções torna a programação e até mesmo a execução mais rápida. Considere a linha de código:
If nVar > 0 .Or. nVar < 0
Se o valor da variável nVar for igual a zero (0) no momento da execução desta linha de código, ambas as comparações separadas pelo operador lógico .Or. serão efetuadas: Após ser avaliada, a primeria comparação irá falhar. A segunda comparação será então avaliada e falhará também. Como resultado, o código existente dentro da estrutura de fluxo If não será executado. Tal código somente será executado quando o valor desta variável for maior OU menor do que zero. Ou seja, sempre que for DIFERENTE de zero, o que torna a linha a seguir mais eficiente:
If nVar != 0
Este tipo de alteração torna o código mais legível e o processamento mais rápido, evitando a avaliação de instruções desnecessariamente.
Existem outras situações onde a simplificação pode ser utilizada. A expressão de avaliação a seguir:
If cVar == "A" .Or. cVar == "B" .Or ;
cVar == "C" .Or. cVar == "D"
Pode ser substituído pelo operador de contenção:
If cVar $ "ABCD"
Opção por Flexibilidade
A melhor solução é aquela que envolve o problema imediato e previne problemas no futuro. Considere o exemplo:
@nRow,nCol PSAY cVar Picture "!!!!!!!!!!!!!!!!!!!!"
Exceto contando-se os caracteres, não existe maneira de saber se o número de caracteres de exclamação é o esperado. Enquanto isto é um problema, existem algo mais grave. A expressão de picture é estática. Se no futuro for necessário ajustar o tamanho da variável cVar, será necessário localizar todos os lugares no código onde esta máscara de picture está sendo utilizada para ajuste manual. Existe uma opção dsolução de de auto-ajuste disponível que é fácil de digitar e tem a garantia de executar a tarefa igualmente (tornar todos os caracteres maiúsculos):
@nRow,nCol PSAY cVar Picture "@!"
Opção da Praticidade ao Drama
Se a solução parece complexa, provavelmente é porque o caminho escolhido está levando a isso. Deve-se sempre se perguntar porque alguém desenvolveria uma linguagem que requisite tantos comandos complicados para fazer algo simples. Na grande maioria dos casos, existe uma solução mais simples. O exemplo abaixo deixa isso bem claro:
@ 10,25 Say Substr(cCep,1,5) + "-" + Substr(cCep,6,3) Picture "!!!!!!!!!"
Que pode ficar mais simples assim:
@ 10,25 Say cCep Picture "@R 99999-999"
Utilização de Operadores de Incremento/Decremento
Utilizados devidamente, os operadores de incremento e decremento tornam o código mais fácil de ler e possivelmente um pouco mais rápidos. Ao contrário de escrever adições simples como:
nVar := nVar + 1
nVar := nVar -1
Pode-se escrevê-las assim:
++nVar
--nVar
Deve-se apenas tomar cuidado com a precedência destes operadores, pois o "++" ou o "--" podem aparecer antes ou depois de uma variável, e em alguns casos quando a variável for utilizada dentro de uma expressão, a prefixação ou sufixação destes operadores afetará o resultado. Para maiores detalhes, consulte a documentação de operadores da linguagem AdvPl.
Evitar Passos Desnecessários
Existe uma diferença entre um bom hábito e perda de tempo. Algumas vezes estes conceitos podem estar muito próximos, mas um modo de diferenciá-los é balancear os benefícios de realizar alguma ação contra o problema que resultaria se não fosse executada. Observe o exemplo:
Local nCnt := 0
For nCnt := 1 To 10
<código>
Next nCnt
Inicializar a variável no momento da declaração não é um problema. Se o 0 fosse necessário no exemplo, teria sido útil a inicialização na declaração. Mas neste caso a estrutura de repetição For... Next atribui o seu valor imediatamente com 1, portanto não houve ganho em atribuir a variável com 0 no começo.
Neste exemplo não há nenhum ponto negativo e nada errado ocorrerá se a variável não for inicializada, portanto é aconselhável evitar este tipo de inicialização, pois não torna o código mais seguro e também não expressa a intenção do código mais claramente.
Porém note este exemplo, onde a variável não é inicializada:
Local nCnt
While ( nCnt++ < 10 )
<código>
EndDo
Em AdvPl, variáveis não inicializadas sempre tem seu valor contendo nulo (nil) a princípio, o que fará com que uma exceção em tempo de execução aconteça quando a instrução de repetição while for executada.
Diferentemente do primeiro exemplo, onde a inicialização da variável não fazia diferença alguma, neste segundo exemplo a inicialização é absolutamente necessária. Deve-se procurar inicializar variáveis numéricas com zero (0) e variáveis caracter com string nula ("") apenas quando realmente necessário.
Utilização de Alternativas
Quando se está trabalhando em uma simples rotina, deve-se tomar algum tempo para explorar duas ou três diferentes abordagens. Quando se está trabalhando em algo mais complexo, deve-se planejar prototipar algumas a mais. Considere o seguinte código:
If cHair = "A"
Replace hair With "Loira"
Else
If cHair = "B"
Replace hair With "Morena"
Else
If cHair = "C"
Replace hair With "Ruiva"
Else
If cHair = "D"
Replace hair With "Grisalho"
Else
Replace hair With "Preto"
Endif
Endif
Endif
Endif
Um código de uma única letra, (A até E), foi informado para indicar a cor de cabelo. Este código foi então convertido e armazenado como uma string. Pode-se notar que a cor "Preto" será atribuída se nenhuma outra opção for verdadeira.
Uma alternativa que reduz o nível de identação torna o código mais fácil de ler enquanto reduz o número de comandos replace:
Do Case
Case cHair == "A"
cColor := "Loira"
Case cHair == "B"
cColor := "Morena"
Case cHair == "C"
cColor := "Ruiva"
Case cHair == "D"
cColor := "Grisalho"
OtherWise
cColor := "Preto"
EndCase
Replace hair With cColor
Utilização de Arquivos de Cabeçalho Quando Necessário
Se um arquivo de código criado se referencia a comandos para interpretação e tratamento de arquivos XML, este deve se incluir o arquivo de cabeçalho próprio para tais comandos (XMLXFUN.CH no exemplo). Porém não deve-se incluir arquivos de cabeçalho apenas por segurança. Se não se está referenciando nenhuma das constantes ou utilizando nenhum dos comandos contidos em um destes arquivos, a inclusão apenas tornará a compilação mais demorada.
Constantes em Maiúsculo
Isto é uma convenção que faz sentido. Em AdvPl, como em C por exemplo, a regra é utilizar todos os caracteres de uma constante em maiúsculo, a fim de que possam ser claramente reconhecidos como constantes no código, e que não seja necessários lembrar onde foram declarados.
Utilização de Identação
Este é um hábito que todo programador deve desenvolver. Não consome muito esforço para manter o código alinhado durante o trabalho, porém quando necessário pode-se utilizar AP6 IDE para a reidentação de código.
Considere o exemplo:
While !SB1->(Eof())
If mv_par01 = SB1->B1_COD
dbSkip()
Loop
Endif
Do Case
Case SB1->B1_LOCAL == "01" .Or. SB1->B1_LOCAL == "02"
TrataLocal(SB1->B1_COD,SB1->B1_LOCAL)
Case SB1->B1_LOCAL == "03"
TrataDefeito(SB1->B1_COD)
OtherWise
TrataCompra(SB1->B1_COD,SB1->B1_LOCAL)
EndCase
dbSkip()
EndDo
A utilização da identação seguindo as estruturas de controle de fluxo (while, if, case, etc) torna a compreensão do código muito mais fácil:
While !SB1->(Eof())
If mv_par01 = SB1->B1_COD
dbSkip()
Loop
Endif
Do Case
Case SB1->B1_LOCAL == "01" .Or. SB1->B1_LOCAL == "02"
TrataLocal(SB1->B1_COD,SB1->B1_LOCAL)
Case SB1->B1_LOCAL == "03"
TrataDefeito(SB1->B1_COD)
OtherWise
TrataCompra(SB1->B1_COD,SB1->B1_LOCAL)
EndCase
dbSkip()
EndDo
Utilização de Espaços em Branco
Espaços em branco extras tornam o código mais fácil para a leitura. Não é necessário imensas áreas em branco, mas agrupar pedaços de código através da utilização de espaços em branco funciona muito bem. Costuma-se separar parâmetros com espaços em branco.
Quebra de Linhas Muito Longas
Com o objetivo de tornar o código mais fácil de ler e imprimir, as linhas do código não devem estender o limite da tela ou do papel. Podem ser "quebradas" em mais de uma linha de texto utilizando o ponto-e-vírgula (;).
Capitulação de Palavras-Chave
Uma convenção amplamente utilizada é a de capitular as palavras chaves, funções, variáveis e campos utilizando uma combinação de caracteres em maiúsculo e minúsculo, visando facilitar a leitura do código fonte. O código a seguir:
local ncnt
while ( ncnt++ < 10 )
ntotal += ncnt * 2
enddo
Ficaria melhor com as palavras chaves e variáveis capituladas:
Local nCnt
While ( nCnt++ < 10 )
nTotal += nCnt * 2
EndDo
Utilização da Notação Húngara
A Notação Húngara é muito comum entre programadores xBase e de outras linguagens. A documentação do AdvPl utiliza esta notação para a descrição das funções e comandos e é aconselhável sua utilização na criação de rotinas, pois ajuda a evitar pequenos erros e facilita a leitura do código. Para maiores detalhes, consulte a documentação sobre a Notação Húngara disponível na documentação da linguagem AdvPl.
Utilização de Nomes Significantes para Variáveis
A principal vantagem da liberdade na criação dos nomes de variáveis é a facilidade de identificação da sua utilidade. Portanto deve-se utilizar essa facilidade o máximo possível. Nomes sem sentido apenas tornarão difícil a identificação da utilidade de uma determinada variável, assim como nomes extremamente curtos. Nem sempre a utilização de uma variável chamada i é a melhor saída. Claro, não convêm criar uma variável com um nome muito longo que será utilizada como um contador, e referenciada muitas vezes no código. O bom senso deve ser utilizado.
Criar variáveis como nNumero ou dData também não ajudam na identificação. A Notação Húngara já está sendo utilizada para isso e o objetivo do nome da variável deveria ser identificar sua utilização, não o tipo de dado utilizado. Deve-se procurar substituir tais variáveis por algo como nTotal ou dCompra.
O mesmo é válido para nomes de funções, que devem descrever um pouco sobre o que a função faz. Novamente nomes extremamente curtos não são aconselháveis.
Utilização de Comentários
Comentários são muito úteis na documentação de programas criados e para facilitar a identificação de processos importantes no futuro. Devem sempre ser utilizados.
Sempre que possível, funções criadas devem ter uma breve descrição do seu objetivo, parâmetros e retorno. Além de servir como documentação, os comentários embelezam o código ao separar as funções umas das outras.
Os comentários devem ser utilizados com bom senso, pois reescrever a sintaxe AdvPl em português torna-se apenas perda de tempo:
#EX
If nLastKey == 27 // Se o nLastKey for igual a 27
#EX
Criação de Mensagens Sistêmicas Significantes e Consistentes
Seja oferecendo assistência, exibindo mensagens de aviso ou mantendo o usuário informado do estado de algum processo, as mensagens devem refletir o tom geral e a importância da aplicação. Em termos gerais, deve-se evitar ser muito informal e ao mesmo tempo muito técnico.
#EX
"Aguarde. Reindexando (B1_FILIAL+B1_COD+B1_LOCAL) do arquivo: \DADOSADV\SB1990.DBF"
#/EX
Esse tipo de mensagem pode dar informações demais para o usuário e deixá-lo sentindo-se desconfortável se não souber o que significa "reindexando", etc. E de fato, o usuário não devia ser incomodado com tais detalhes. Apenas a frase "Aguarde, indexando." funcionaria corretamente, assim como palavras "processando" ou "reorganizando".
Outra boa idéia é evitar a referencia a um item corrente de uma tabela como um "registro":
#EX
"Deletar este registro?"
#/EX
Se a operação estiver sendo efetuada em um arquivo de clientes, o usuário deve ser questionado sobre a remoção do cliente corrente, se possível informando valores de identificação como o código ou o nome.
Evitar Abreviação de Comandos em 4 letras
Apesar do AdvPl suportar a abreviação de comandos em quatro letras (por exemplo, repl no lugar de replace) não há necessidade de utilizar tal funcionalidade. Isto apenas torna o código mais difícil de ler e não torna a compilação mais rápida ou simples.
Evitar "Disfarces" no Código
Não deve-se criar constantes para expressões complexas. Isto tornará o código muito difícil de compreender e poderá causar erros primários, pois pode-se imaginar que uma atribuição é efetuada a uma variável quando na verdade há toda uma expressão disfarçada:
#define NUMLINES aPrintDefs[1]
#define NUMPAGES aPrintDefs[2]
#define ISDISK aReturn[5]
If ISDISK == 1
NUMLINES := 55
Endif
NUMPAGES += 1
A impressão que se tem após uma leitura deste código é de que valores estão sendo atribuidos às variáveis ou que constantes estão sendo utilizadas. Se o objetivo é flexibilidade, o código anterior deve ser substituído por:
#define NUMLINES 1
#define NUMPAGES 2
#define ISDISK 5
If aReturn[ISDISK] == 1
aPrintDefs[ NUMLINES ] := 55
Endif
aPrintDefs[ NUMPAGES ] += 1Evitar Código de Segurança Desnecessário
Dada sua natureza binária, tudo pode ou não acontecer dentro de um computador. Adicionar pedaços de código apenas para "garantir a segurança" é freqüentemente utilizado como uma desculpa para evitar corrigir o problema real. Isto pode incluir a checagem para validar intervalos de datas ou para tipos de dados corretos, o que é comumente utilizando em funções:
Static Function MultMalor( nVal )
If ValType( nVal ) != "N"
nVal := 0
Endif
Return ( nVal * nVal )
O ganho é irrisório na checagem do tipo de dado do parâmetro já que nenhum programa corretamente escrito em execução poderia enviar uma string ou uma data para a função. De fato, este tipo de "captura" é o que torna a depuração difícil, já que o retorno será sempre um valor válido (mesmo que o parâmetro recebido seja de tipo de dado incorreto). Se esta captura não tiver sido efetuada quando um possível erro de tipo de dado inválido ocorrer, o código pode ser corrigido para que este erro não mais aconteça.Isolamento de Strings de Texto
No caso de mensagens e strings de texto, a centralização é um bom negócio. Pode-se colocar mensagens, caminhos para arquivos, e mesmo outros valores em um local específico. Isto os torna acessíveis de qualquer lugar no programa e fáceis de gerenciar.
Por exemplo, se existe uma mensagem comum como "Imprimindo, por favor aguarde..." em muitas partes do código, corre-se o risco de não seguir um padrão para uma das mensagens em algum lugar do código. E mantê-las em um único lugar, como um arquivo de cabeçalho, torna fácil a produção de documentação e a internacionalização em outros idiomas.