Classes em Advpl – Parte 04

E, para finalizar a a introdução do tópico “Classes em Advpl”, hoje vamos abordar algumas boas práticas da orientação a objeto, com foco no uso com ADVPL.

Simplicidade

A correta representação do domínio do problema deve ser simples, mesmo para um problema complexo. Por exemplo, fazer um jogo de damas ou xadrez no computador pode parecer algo muito complexo, certo ? Com apenas uma classe de tabuleiro, uma classe de jogador, uma classe base (ou abstrata) para uma peça genérica, e uma classe para cada peça do tabuleiro, onde a instância de tabuleiro é responsável por fazer a interface com o usuário e permitir ele mover uma peça de um lugar de origem para um lugar de destino, eu garanto que fica mais simples. A interface recebe o input do jogador, e aciona o método de mover peça do tabuleiro, que verifica se tem uma peça na posição de origem, e chama um método da peça para listar as posições válidas para onde a peça pode ser movida. Cada objeto tem a sua camada de inteligência (métodos) e validações.

A implementação feita desta forma fica isolada em cada peça, afinal você precisa escrever apenas um método para cada peça para determinar as posições possiveis de movimento a partir do tabuleiro em um determinado estado, onde um metodo do tabuleiro se encarrega de varrer a lista de peças em jogo de um dos jogadores e perguntar para cada uma para onde ela pode mover-se. Com isso é mais fácil implementar a mecânica dos movimentos das peças, e até um mecanismo de projeção de movimentos possíveis do adversário.

Nível de abstração e herança adequadas

Quanto maior o detalhamento que você precisa, maior será a quantidade de classes e propriedades necessárias para lhe atender. Atenha-se ao que você precisa, e de forma ordenada. Por exemplo, ao prototipar três classes, A , B, e C, onde B e C herdam A, na classe superior (A) você deve colocar propriedades que são comuns a todas as classes da herança, e nas classes filhas apenas as propriedades e métodos específicos que somente caberiam na instância da classe filha, permanecendo os métodos comuns na classe pai. Muitas vezes implementamos uma herança sem ter propriedades específicas, mas implementações de métodos com comportamentos diferenciados por instância.

Destrutores e limpeza de memória

Uma instância de uma classe na linguagem ADVPL não possui declaração explícita de métodos destrutores, porém o kernel do ADVPL realiza um controle de reaproveitamento de memória da instância da classe, e mantém a instância na memória, mesmo que ela não seja mais referenciável, apenas eliminando a memória consumida pela instância quando a função que cria a instância da classe é chamado e cria uma nova instância. A memória ocupada pela instância envolve todas as propriedades da instância.

Logo, é elegante e saudável para a memória você criar um método “CleanUp” na classe para limpar as propriedades que não estão sendo mais referenciadas desta instância, uma vez que a mesma não seja mais necessária, e após chamar o CleanUp() da instância, você executa a função FreeObj(), passando a variável que contém a instância como parâmetro.

Se você executar um FreeObj() em uma instância de classe ADVPL, mas ela ainda estava sendo referenciada em uma ou mais varíaveis ou propriedades de outras classes, automaticamente estas referências tornam-se nulas (NIL). Caso algum programa tente acessá-las, será gerada uma ocorrência de erro “Variable is not an object”.

A função FreeObj() também serve para eliminar uma classe da interface visual do Advpl. Neste caso, muito cuidado com o seu uso, pois se você por exemplo executar um FreeObj() em uma instância de tWindow, tDialog, tPanel, ou qualquer instância de container de interface, que está ativa na tela e na pilha de execuções, você pode provocar uma invasão de memória ( Access Violation ou Segment Fault ).

A dica de limpeza vale também para funções em Advpl, onde as variáveis locais daquela execução permanecem alocadas na memória, somente sendo desalocadas em uma próxima execução da função. Por exemplo, uma função de processamento intermediário cria um array local dentro do fonte, e popula este array para fazer um cálculo. Se o retorno desta função não for o próprio array, o conteúdo alocado na memória pelos elementos não será necessário e nem acessível quando a função retornar o valor calculado, mas a área de memória ocupada vai permanecer alocada. Neste caso, você deve limpar o array, usando a função de redimensionamento de array da seguinte forma: aSize(aVarArray,0) — onde aVarArray é a variável que contém o array a ser limpo.

Caso as propriedades da classe apontem para arrays de outros objetos, que estão compartilhados com outros componentes e não exatamente devem ser destruídos, é interessante e elegante que você atribua NIL nestas propriedades, para elas deixarem de referenciar os arrays e objetos em questão.

Performance

Existe um overhead na performance na chamada de um método, em comparação com a chamada de uma função. Fiz um teste de performance de chamadas de uma função e de um método de classe, onde a função e a classe realizam um processamento – o retorno de um valor constante. O loop de processamento demorou em média 1,5 segundos para chamar um milhão de vezes uma função, e demorou 3,8 segundos para chamar um milhão de vezes um método. Parece uma grande diferença, certo ? Bem, estamos falando aqui de 666 mil RPS(requisições por segundo) de função, contra 263 mil RPS em chamadas de método, usando uma CPU de 1.8 GHZ.

Lembre-se da Lei de Amdahl, já mencionada aqui em outra postagem sobre escalabilidade e performance: “O ganho de desempenho que pode ser obtido melhorando uma determinada parte do sistema é limitado pela fração de tempo que essa parte é utilizada pelo sistema durante a sua operação.” Logo, a relevância do tempo em uma etapa específica de um processo somente é perceptível caso ela seja representativa no tempo total do processo.

Por exemplo, em uma rotina de processamento onde os métodos realizam tarefas, decisões e manipulações de dados e propriedades, e podem acessar banco de dados ou informações no disco, o tempo de processamento do método vai ser muito maior do que o tempo da chamada. Ao calcularmos o tempo total de processamento de 1 milhão de requisições de um determinado método, onde cada requisição demora em média 1/10 de segundo, serão 166 minutos (duas horas e 46 minutos) de processamento, mais quatro segundos do tempo gasto com as chamadas dos métodos. Se este loop fosse feito com chamadas de função, acrescentaríamos ao tempo total apenas 1,5 segundo. Esta diferença de tempo, em um processo de 166 minutos, não quer dizer nada.

Este overhead somente torna-se significativo quando os métodos são extremamente curtos, como por exemplo operações aritméticas ou apenas encapsulamento para retorno de propriedades. E mesmo assim, são necessárias milhões de requisições para isso tornar-se perceptível.

Conclusão

A orientação a objetos é um paradigma muito interessante de ser explorado, mas como toda a solução em tecnologia da informação, existem casos onde uma abordagem com funções pode ser mais interessante, até mesmo dentro do mesmo aplicativo. Prevalece sempre a análise de caso, use um paradigma ou abordagem para resolver os problemas onde ela apresenta a melhor relação custo x benefício.

Posteriormente eu devo voltar no tema de orientação a objetos, focando mais em exemplos práticos e casos de uso em ADVPL.

Até o próximo post, pessoal 😉

Anúncios

2 comentários sobre “Classes em Advpl – Parte 04

  1. Olá,

    eu não entendi direito a parte do CleanUp.

    Eu devo implementar um método CleanUp nas minhas classes, e ele será chamado pelo ADVPL, como um destrutor ? Ou eu implemento, e chamo ele quando não precisar mais daquela instância ?

    Curtido por 1 pessoa

    • A segunda alternativa 😉 Se você não vai mais precisar da instancia, e ela não está sendo referenciada, é elegante você limpar as propriedades ( atribuindo NIL nelas, por exemplo, e zerando arrays com aSIze() ), e para terminar de limpar a instância, você pode usar a FreeObj() pra isso.

      Normalmente isto não traz diferenças visíveis de ocupação de memória para a maioria dos casos, exceto quando a classe usa propriedades com strings muito grandes e arrays com muitos elementos. A memória usada pela instancia e suas propriedades permanece alocada para aquela instância, e somente é redimensionada caso a mesma instancia volte a ser criada pelo programa.

      Vou fazer um teste prático com isso e algumas medições, acho que um exemplo vale por mil explicações 😀

      Abraços

      Curtido por 1 pessoa

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s