Dicas valiosas de programação – Parte 04

Introdução

Continuando o assunto de dicas valiosas de programação, vamos abordar alguns assuntos relacionados a JOBS (Programas sem interface), pontos de atenção, alternativas de controle, etc.

Considerações sobre JOBS

Em tópicos anteriores, vimos que existem várias formas de subir um ou mais jobs em um serviço do Protheus Server. A maior dificuldade dos JOBS consiste em saber o que ele está fazendo, e como ele está fazendo. O fato do Job não ter nenhum tipo de interface torna esse trabalho um pouco diferente dos demais programas.

Quando usamos um pool de JOBS, como por exemplo os processos de WebServices ou de portais WEB (JOB TYPE=WEBEX), definimos o número inicial (mínimo) de processos, numero máximo, e opcionalmente mínimo livre e incremento. Logo, não precisamos nos preocupar se o servidor recebe mais uma requisição e todos os processos que estão no ar estão ocupados — se o número máximo de processos configurado não foi atingido, o próprio Protheus Server coloca um ou mais jobs no ar.

Gerando LOG de um JOB

Normalmente quando precisamos acompanhar o que um determinado JOB está fazendo, podemos usar o IDE e/ou TDS para depurar o Job, ou caso isto não seja uma alternativa para a questão, o programa pode ser alterado para emitir mensagens a cada etapa de processamento. Uma das alternativas normalmente usadas — e mais simples de usar — é usar a função AdvPL conout() nos programas envolvidos, para registar no log de console do Protheus Server mensagens sobre o que cada processo está fazendo. Para diferenciar os processos, podemos usar a função ThreadID() do AdvPL, para identificar o número da thread que gerou a mensagem.

Outra alternativa interessante, inclusive mais interessante que usar o log de console do servidor de aplicação, é fazer com que o job crie um arquivo de LOG dele próprio em disco, usando por exemplo a função fCreate(), criando o arquivo em uma pasta a partir do RootPath do ambiente, usando por exemplo um prefixo mais o numero da thread atual mais o horário de inicio do job como nome do arquivo — para ficar fácil saber quais logs são de quais JOBS — e gravar os dados de LOG dentro desse arquivo usando a função fWrite()  — lembrando de inclusive gravar os caracteres chr(13)+chr(10) ao final de cada linha — estes caracteres de controle indicam uma quebra de linha em um arquivo no padrão Windows. Para Linux, a quebra de linha padrão é apenas chr(10).

Acompanhando a execução de um JOB

Quando você cria um determinado JOB para alguma tarefa longa, pode ser interessante saber em que ponto ou etapa da tarefa o JOB está trabalhando em um determinado momento. A solução mais leve, é você criar um nome de uma variável global — aquelas que são acessadas através das funções PutGlbVars() e GetGlbVars() — e alimentar dentro do JOB a variável global com a etapa atual do processo, enquanto um outro programa (em outro processo, com interface por exemplo) consulta a variável para saber qual é a tarefa interna do Job em andamento.

Desta forma, um programa externo pode consultar — através de variáveis globais com nomes pré-definidos — o status de não apenas um, mas vários jobs sendo executados no servidor de aplicação atual. Basta criar identificadores únicos não repetidos antes de iniciar os processos.

Ocorrências de Erro Críticas

Mesmo que o seu JOB possua um tratamento de erro, cercado com BEGIN SEQUENCE … END SEQUENCE e afins, as ocorrências de erro de criticidade mais alta não são interceptadas ou tratadas. Desse modo, se você apenas consulta uma variável global para pegar o status de um Job, ele pode ter sido derrubado ou ter finalizado com uma ocorrência critica de erro, e o programa que está esperando ou dependendo de um retorno dele nem sabe que ele já não está mais sendo executado.

Não há contorno para tentar manter no ar um JOB que foi finalizado por uma ocorrência crítica, porém você pode descobrir se ele ainda está no ar ou não, usando alguns recursos, por exemplo:

  1. Além da variável global para troca de status, faça o JOB obter por exemplo um Lock Virtual no DBAccess ou um Lock em Disco — busque no blog por “MUTEX” e veja algumas alternativas. A ideia é usar um recurso nomeado em modo exclusivo, que é liberado automaticamente caso o JOB seja finalizado por qualquer razão. Se o seu programa que espera retorno do JOB está sem receber nenhuma atualização, verifique se o JOB está no ar tentando fazer um bloqueio do recurso que o JOB está usando. Se o seu processo conseguiu o bloqueio, o JOB foi pro vinagre…
  2. Verifique se o seu processo ainda está no ar usando — com moderação — por exemplo a função GetUserInfoArray() — ela retorna um array com as informações dos processos em execução na instância atual do Protheus Server. Para isso, pode ser necessário que o JOB que foi colocado em execução use uma variável global para o processo principal e de controle de jobs saber qual é o ThreadID deste processo, para ser possível um match com o retorno da GetUserInfoArray().

Seu processo principal pode não saber o que aconteceu com o processo filho, mas sabe que ele não está mais no ar, e saiu antes de dar um resultado. Isso muitas vezes é suficiente para você estudar uma forma de submeter o processo novamente, ou de encerrar o processo principal informando que houve um término anormal e os logs de erro do sistema devem ser verificados, ao invés de esperar para sempre um JOB que já não está mais lá.

Conclusão

Quanto mais nos aprofundamos em um tema, mais temas aparecem para nos aprofundarmos 🙂 E, é claro que veremos exemplos de uso práticos destes mecanismos, com fonte e tudo, nos próximos posts !!!

Agradeço novamente a audiência, e desejo a todos(as) TERABYTES DE SUCESSO !!! 

Referências

 

Anúncios

Dicas valiosas de programação – Parte 03

Introdução

Neste post, vamos a uma dica muito importante e específica do AdvPL: Como proteger a chamada de novas funções implementadas na linguagem AdvPL — e evitar o erro “function <xxx> has compilation problems. Rebuild RPO”

Funções do AdvPL

Ao escrevermos uma aplicação em AdvPL, os desenvolvedores podem usar funções básicas da linguagem AdvPL, que por serem nativas da linguagem, estão compiladas e publicadas dentro do Application Server (executável/dlls), e as funções de Framework e Produto, compiladas dentro do RPO (Repositório de funções e classes AdvPL).

Compilation Problems …

Quando é criada uma nova função básica da linguagem AdvPL, ela estará disponível para uso apenas quando você atualizar o seu Application Server para uma build igual ou superior a qual a função foi disponibilizada. O problema é que, uma vez que você implemente uma chamada desta nova função dentro de um código AdvPL, para que esta chamada funcione, você precisa compilar o seu fonte usando o binário mais novo — que já têm a função — e executar o seu fonte com ele.

Caso você compile seu fonte com um binário mais antigo — que não tem a função — e tente executar o fonte em um binário mais novo, ou faça o contrário — compile usando um binário novo e tente executar em um binário antigo, ocorre o erro “function <xxx> has compilation problems. Rebuild RPO”. E este erro não ocorre durante a execução do fonte, mas ocorre durante a carga da função na memória para ser executada.

Este comportamento é uma amarração de segurança, para evitar que inadvertidamente uma função AdvPL compilada no RPO possa conflitar — ou mesmo tentar sobrescrever — uma função básica da linguagem AdvPL.

Onde isso pode ser um problema ?

Imagine que você dá manutenção em um código já existente e que funciona, e em uma build mais nova do Application Server, uma função nova foi publicada, que pode tornar o seu processo mais rápido.  A mão “coça” pra implementar a chamada da função, porém você não sabe se esse fonte será compilado com um binário mais velho ou novo, nem como garantir que o seu cliente vá atualizar a Build do Application Server para rodar o código novo. Neste caso, você precisa que seu código continue funcionando em uma build antiga ou nova, e independentemente se ele foi compilado em uma build nova ou não.

Como fazer a implementação protegida ?

São apenas alguns passos simples, mas que precisam ser feitos desta forma.

  • Verifique se a nova função existe em tempo de execução, chamando a função FindFunction(), passando como parâmetro entre aspas o nome da função.  Caso a função exista na build em uso, ela retorna .T.
  • Coloque o nome da função nova da linguagem AdvPL que você quer chamar, dentro de uma variável local caractere — por exemplo cFuncName
  • Após verificar se a função existe, faça a chamada para esta função usando o operador de macro-execução, com a seguinte sintaxe:

[<retorno> := ] &cFuncName.([parm1][,parm2][,…])

Por exemplo, vamos imaginar que você quer usar uma nova função do AdvPL, chamada FindClass() — que serve para dizer se uma determinada classe existe no ambiente onde a função está sendo executada.

user function tstfindcls()
Local cFnName := 'findclass'
Local lExist := .F.
If FindFunction(cFnName)
   lExist := &cFnName.("TGET")
   If lExist
      MsgInfo("A Classe TGET existe")
   Else
      MsgStop("A classe TGET nao existe")
   Endif
Else
   MsgStop("Função "+cFnName+" não encontrada", ;
           "Atualize o Application Server")
Endif
Return

Dessa forma, mesmo que você compile esse fonte AdvPL em uma build antiga ou nova, ele vai rodar corretamente em uma build nova, e caso seja executado em uma build antiga — que ainda não tenha a função FindClass — o fonte não vai apresentar erro de carga, mas vai identificar que a função não existe e mostrar a mensagem desejada.

Conclusão

Espero que esta dica, mesmo que  “curtinha”, seja de grande valia para todos os desenvolvedores AdvPL. Desejo novamente a todos TERABYTES de sucesso !!! 😀

Referências

 

 

Acelerando o AdvPL – Parte 02 (ERRATA)

Pessoal, boa tarde,

Hoje eu estava lendo novamente o código do post anterior, referente ao exemplo de um cache em array, com tamanho limitado de elementos, e uma optimização para subir no array cada item pesquisado, para que os itens mais pesquisados sejam encontrados sequencialmente em primeiro lugar, e encontrei um erro de lógica em um ponto da rotina … vamos ver apenas este pedaço do código:

Function GetJurosPad( cTipoCtr )
Local nJuros := -1
Local cChave := xFilial('ZZ1')+cTipoCtr
Local nPos := ascan(aJurosPad,{ |x| x[1] == cChave })
Local aTmp
If nPos > 0 
 // Achou no cache. 
 If nPos > 1 
 // nao esta na primeira posição, sobe uma 
   aTmp := aJurosPad[nPos]
   aJurosPad[nPos] := aJurosPad[nPos-1]
   aJurosPad[nPos-1] := aTmp
 Endif
 // Incrementa cache HITS -- achei no cache, economia de I/O
 nJurosHit++
 Return aJurosPad[nPos][2]
Endif

Reparem que a busca foi feita na declaração da variável nPos, usando Ascan(). Porém, caso o item encontrado não seja o primeiro elemento do array, o elemento encontrado sobe uma posição (usando a variável aTmp para fazer o SWAP). E, no final das contas, o valor retornado é a segunda coluna do elemento após a troca. Neste caso, a função somente vai retornar o valor correto caso o valor buscado seja encontrado no primeiro elemento do array.

Para  corrigir isto, precisamos armazenar o resultado encontrado antes de fazer a troca no array, e retornar este valor, veja a correção abaixo:

Function GetJurosPad( cTipoCtr )
Local nJuros := -1
Local cChave := xFilial('ZZ1')+cTipoCtr
Local nPos := ascan(aJurosPad,{ |x| x[1] == cChave })
Local aTmp
If nPos > 0 
 // Achou no cache. 
 nJuros := aJuros[nPos][2]
 If nPos > 1 
   // nao esta na primeira posição, sobe uma 
   aTmp := aJurosPad[nPos]
   aJurosPad[nPos] := aJurosPad[nPos-1]
   aJurosPad[nPos-1] := aTmp
 Endif
 // Incrementa cache HITS -- achei no cache, economia de I/O
 nJurosHit++
 Return nJuros
Endif

Lembrem-se da importância de revisar e testar o código. Ser um bom programador e ter experiência não vai te livrar de cometer erros, uma revisão de código e um bom teste nunca é uma questão de desconfiança, mas sim procedimento.

Até a próxima, pessoal 😀

Pense fora da caixa e resolva problemas

Hoje não veremos nenhuma linha de código, separei este post para compartilhar um pouco das experiências diárias do profissional de TI em lidar com problemas, e contar um “causo” (pelo menos pra mim) interessante, onde pensar fora da caixa foi fundamental para chega a uma solução elegante.

Problemas existem, e podem acontecer

Na trajetória do desenvolvimento de Software, volta e meia aparecem problemas de diferentes magnitudes e complexidades. Normalmente existe solução para todos os problemas, porém algumas soluções podem ser mais caras do que conviver com o problema ou contorná-lo. A primeira coisa que aprendemos na informática é como utilizá-la para resolver problemas. A segunda coisa é como resolver o problema quando uma falha na implementação de uma solução informatizada torna-se o problema.

A primeira coisa que devemos entender sobre o problema é a sua gravidade. E, é claro que isto é uma medida totalmente relativa. Um cliente não conseguir emitir uma nota fiscal pode parecer algo não grave, porém se este cliente depende da emissão desta nota para liberar um caminhão com produtos perecíveis, com data e horário certos para entrega sob pena de multa ou rescisão do contrato, não emitir essa nota é gravíssimo.

Depois, precisamos pensar em uma solução para o problema. Se o problema é grave e urgente, precisamos de um contorno para o problema. Dá-se preferência a um contorno elegante, mas se o que você têm para o momento é um arame e um durepoxi, e isso vai contornar o problema, apodere-se do espírito McGuyver que existe em você, e contorne. Mas pelo amor de Deus, não venda a gambiarra que você fez como a solução do problema. Explique que você está botando uma fita isolante no problema para o cliente não ficar parado, e que você vai trabalhar na solução definitiva assim que você entender as causas do problema.

Se você já está na fase de trabalhar no problema, ou teve que ir pra ela direto pois nenhum contorno passa pela sua cabeça, pesquise o que você puder sobre o problema. Procure entender o problema e suas causas, a partir de que momento ele manifestou-se, sob que circunstâncias, quando e onde ocorre, com que frequência, se é generalizado ou localizado, se ocorre em outros pontos do sistema, em um servidor ou em todos, em um tipo de programa ou qualquer programa, quais foram os eventos recentes que antecederam o início do surgimento do problema. É quase uma atividade de detetive. Procure as pessoas certas para levantar estas informações, limpe eventuais “sujeiras” na comunicação, e não tenha medo de perguntar “o que você quer dizer com isso ?”, quando você tiver qualquer dúvida a respeito das informações que permeiam o assunto em foco.

Muitas vezes você encontra um contorno para o problema — e posteriormente a solução — durante a análise dos detalhes do problema, ou tomando banho, ou tomando um café depois do almoço — sim, essas coisas acontecem. Se você já avaliou as possíveis causas e tudo parece certo, procure alguém mais experiente para trocar uma ideia, ou divida o problema com seus colegas de equipe, às vezes um detalhe que você não viu nas informações fornecidas  é visto na hora por outro analista, e dá um novo norte na busca pela causa do problema.

Alguns tipos de problemas são mais difíceis de reproduzir em um ambiente controlado do que Tamanduá-Bandeira em cativeiro. A causa pode estar relacionada a alguma particularidade do ambiente ou da configuração do sistema, ou mesmo até um defeito físico em uma parte da infra-estrutura. Quando as causas prováveis foram analisadas sem sucesso, comece a analisar as improváveis. Olhe tudo com uma lupa, revalide as possibilidades que foram eliminadas durante a análise, mas não desista. Existem pequenos problemas que se escondem por trás de um problema maior, e grandes problemas por trás de pequenos problemas. Não está achando o problema grande, verifique se existem problemas menores, eles podem ser consequência ou parte da causa do problema grande.

O caso do CNAB

No final das contas, entrei tanto dentro do problema que quase fugi do foco principal do post, o pensamento “fora da caixa”. Um cliente do ERP estava com dificuldade de utilizar a integração CNAB (padrão Febraban para remessa e retorno de títulos para compensação bancária). Quando o arquivo de remessa era gerado e enviado ao banco, alguns títulos do arquivo eram rejeitados pelo banco, pois o código de verificação do título que era enviado no arquivo, segundo o banco, não estava com os valores corretos.

O código de verificação de cada título era gerado pelo ERP, na geração do arquivo de remessa, e este código era composto por uma série de operações aritméticas de soma e multiplicação a partir de determinadas informações do título, como valor, vencimento, código do cliente e afins. A conta realizada com os valores daquele título resultava em um número inteiro muito grande, que esbarrava em um limite aritmético da linguagem AdvPL. O cálculo feito pela calculadora do Windows batia com o valor esperado pelo banco, que estava correto, mas o cálculo feito dentro do programa usando a aritmética da linguagem não comporta um número com mais de 16 dígitos significativos, havendo perda de precisão e consequentemente valor incorreto do código.

Isto foi antes do uso do Protheus Server, hoje chamado de TOTVS Application Server, muito anos antes de serem implementadas funções no AdvPL para lidar com números decimais de ponto fixo, que possuem precisão de até 64 dígitos. No final das contas, a fórmula matemática de cálculo escrita em AdvPL da forma originalmente proposta somente seria executada corretamente se a linguagem usasse um tipo de dado numérico com precisão superior ao ponto flutuante.

Depois de sair da minha mesa e tomar um café, levei meu caderno de rabiscos para passear, e já estava pensando em fazer um programa em outra linguagem e chamar ele de dentro do ERP para fazer aquela conta, quando eu olhei para os números e percebi o óbvio: Eu consigo fazer essa conta em um pedaço de papel, usando um lápis e as mesmas regrinhas básicas de soma e multiplicação que aprendemos no colégio primário. Se dá pra fazer no papel, dá pra fazer no computador, exatamente da mesma forma.

Eu só precisava de soma e multiplicação, e não importa se a operação ficasse um pouco mais lenta do que um cálculo em ponto flutuante nativo da linguagem, afinal seriam apenas algumas chamadas para cada título do borderô. Então, em menos de uma hora eu fiz duas funções para somar e multiplicar números recebidos como String. Bastou re-escrever as fórmulas para o CNAB daquele banco para pegar os dados do título e passar para as novas funções, trabalhando com os números como String.

Conclusão

Análise, programação e desenvolvimento de sistemas é uma atividade que requer jogo de cintura e criatividade para lidar com o universo de desafios de resolução de problemas, é saber usar o conhecimento e as ferramentas que se têm na mão, ser capaz de apontar mais de uma alternativa para resolver um problema, e escolher a que melhor atende a necessidade.

Pensar fora da caixa é uma expressão onde a caixa normalmente significa o limite do seu pensamento criativo, ou os paradigmas assimilados e embutidos nos problemas que você normalmente lida no dia a dia. A busca por novas soluções é constante, e avaliar um problema por novos ângulos e abordagens pode fazer toda a diferença. Não desista, a resposta está lá fora, esperando que você a encontre !!!

Até o próximo post, pessoal 😉

Informações Adicionais

As funções desenvolvidas para cálculo numérico de soma e multiplicação com strings estão disponíveis no ERP Microsiga, e documentadas na TDN nos links http://tdn.totvs.com/display/public/mp/FUNCAO+GENERICA+-+SOMASTR
e http://tdn.totvs.com/pages/releaseview.action?pageId=6814818 , e foram mantidas por compatibilidade. As funções implementadas na linguagem AdvPL para cálculo com números que exigem precisão maior do que a suportada por variáveis numéricas com ponto flutuante lidam com o tipo numérico “F” do AdvPl (Fixed size decimals), e estão também documentadas na TDN, no link http://tdn.totvs.com/display/tec/Decimais+de+Ponto+Fixo