Caso de Estudo – Performance em Plano de Contas

Introdução

Dentro da teorida da contabilidade, o controle contábil de qualquer entidade é realizado através da análise das contas contábeis. Essas contas nada mais são do que depósitos de valores, estruturados em um formato de árvore – ou seja, uma estrutura onde uma conta contábil é “pai”  de outra conta contábil, que por sua vez pode ser pai de outra, assim, sucessivamente.

A estruturação das contas em um formato de árvore é o que chamamos de plano de contas. Ele dá a flexibilidade necessária para a organização dessas contas, conforme a necessidade de cada entidade.

Cada conta contábil possui a finalidade de reter valores. Esses valores podem ser de duas naturezas diferentes: valores de débitos, ou valores de créditos. As naturezas dos valores são significativas porque, embora não podemos dizer que uma natureza é positiva e a outra negativa, quando existe uma operação de naturezas diferentes, elas se subtraem, ao contrário das operações de naturezas iguais, que se somam.

Além da natureza, uma conta contábil ainda possui uma “meta informação”, que seria o tipo da conta contábil. Contas contábeis que possuem “filhas” são chamadas de contas sintéticas, e as contas contábeis que não possuem “filhas”, são chamadas de contas analíticas. Além do carácter classificatório, essa característica interfere no funcionamento da lógica contábil, pois apenas contas analíticas podem participar de operações contábeis – também chamadas de lançamentos contábeis – enquanto contas sintéticas não podem, servindo apenas para a formação da estrutura da organização do plano de contas.

Uma vez que as contas sintéticas não participam de operações contábeis, elas não recebem valores de débitos nem de créditos, todavia, seu valor corresponde ao somatório dos valores de todas as suas contas filhas.

Outra característica das contas contábeis diz respeito ao seu nível dentro da estrutura do plano de contas. O nível corresponde ao grau de aprofundamento dentro do plano de contas, onde, na seguinte estrutura abaixo, temos contas de diferentes níveis.

  • Conta 1
    • Conta 1.1
      • Conta 1.1.1
      • Conta 1.1.2
    • Conta 1.2
      • Conta 1.2.1
  • Conta 2
    • Conta 2.1
      • Conta 2.1.1

Conforme a estrutura acima, podemos dizer que a Conta 1 e a Conta 2 são de grau 1 (ou nível 1). A Conta 1.1, Conta 1.2, Conta 2.1, são de grau 2, e as contas Conta 1.1.1, Conta 1.1.2, Conta 1.2.1 e Conta 2.1.1, são de grau 3.

Problema

Como os valores das contas contábeis sintéticas são determinados pela somatória dos valores de suas contas “filhas”, não há uma forma de buscar seu valor, a não ser somando os valores de cada conta filha. Contudo, em uma estrutura de árvore onde os níveis das contas não são fixos, normalmente chegando ao grau 5 ou 6, essa tarefa torna-se interessante, porque a busca desses valores deve ser flexível suficiente para encontrar os valores adequados, na ordem correta.

Imagine que no exemplo do plano de contas àcima, seja necessário descobrir qual o valor da  conta “Conta 1”. Para conseguirmos isso, teríamos que somar o valor de todas as suas contas filhas. Porém, as suas contas filhas também são contas sintéticas, onde, para descobrirmos seu valor, precisaríamos somar todos os valores de suas contas filhas (no caso, das contas “netas”). Isso pode ocorrer sucessivamente, até o momento onde não hajam mais contas filhas. De qualquer forma, não existe a possibilidade, partindo-se da primeira conta, de saber exatamente quantas são as contas filhas, sem que para isso, se percorra conta a conta em busca de seus relacionamentos.

Em um cenário onde todas as informações dos valores das contas são armazenados em um banco de dados – imagino que o cenário mais comum entre os sistemas dessa natureza – temos um grande problema quando precisamos fazer um relatório que exponha os valores atuais de todas essas contas.

Primeira solução

Por se tratar de uma estrtura em árvore, que precisa ser percorrida, uma boa estratégia é a utilização de uma função recursiva que busque os valores das contas.

Primeiramente, ela identifica se é uma conta analítica. Se for, pega o valor do seu saldo. Para isso, temos que realizar a consulta no banco da dados em uma tabela que contenha essa informação e retornar esse valor.

Se for uma conta sintética, identifica todas as suas contas filhas e chama a sí mesmo, para cada conta filha, somando os resultados. Isso fará com que cada conta filha execute a mesma lógica, retornando os valores se forem analíticas, ou buscando também em suas contas filhas, se forem sintéticas.

Problemas com a primeira solução

Embora essa abordagem seja muito prática, pois em poucas linhas de código temos todos os valores necessários, ela não é performática, porque para cada busca do saldo, é necessário realizar uma chamada ao banco de dados. Em um plano de contas que contenha 200 contas analíticas de grau 5, por exemplo – o que é um cenário muito otimista – teríamos milhares de consultas no banco de dados para a formação dos valores. Isso mesmo, milhares, porque para cada conta do grau 5, teriamos as consultas, depois as teríamos novamente para as contas de grau 4, e depois para as contas de graus 3, e assim até o primeiro grau.

Longe de mostrar-lhes isso na prática, já é possível identificar um grande problema de performance, pois se é certo evitarmos o máximo possível as chamadas aos banco de dados, evitar milhares delas é mais certo ainda.

Segunda solução

Assim que identificamos a quantidade de consultas necessárias com a abordagem da recursividade, notamos a necessidade de diminuir o máximo possível essas consultas.

Aqui, desde já, peço desculpas aos puristas de banco de dados, mas se existe um motivo que me faz duplicar informações, esse motivo é a performance que essa duplicação pode causar – e somente em casos extremos e realmente necessários.

Uma nova abordagem seria criar uma tabela específica para esse relatório, cujo saldo já fosse armazenado correto nas contas sintéticas a cada nova movimentação. Se essa tabela existisse, o relatório seria praticamente instantâneo, porque com apenas uma única consulta, em uma única tabela, com filtros simples, teríamos todas as informações do relatório.

Problemas com a segunda solução

Porém, para que isso funcione, o mecanismo dos lançamentos contábeis precisaria atualizar constantemente essas tabelas, a cada nova movimentação, assim como para cada estorno. Sempre que houvesse algum lançamento contábil, essa tabela deveria ser atualizada.

Algo não cheira bem com essa estratégia. Embora ela seja funcional, ela pode ser catastrófica se não perfeitamente executada. Sem contar que é um design feio para uma aplicação.

De qualquer forma, poderia ser executada criando-se os mecanismos necessários dentro da aplicação – muito mais fácil se for orientado a objetos, visto que provavelmente apenas um ponto seria alterado – ou implementando em banco de dados o que chamamos de “views materializadas“.

Novamente, embora seja funcional, não é uma boa abordagem, porque além de duplicar as informações do banco de dados, qualquer problema de implementação dessa abordagem levará a sérios problemas que não serão claros no primeiro momento, levando a informações erradas em uma ferramenta de tomada de decisão.

Terceira solução

Uma outra abordagem possível é partir de uma perspectiva down-top, ou seja, partir das contas filhas para as contas pais. Para isso, vamos seguir os seguintes passos:

Primeiro passo:  Montar uma lista de contas com todas as contas existentes e criar uma lista auxiliar que servirá como um índice para a lista de contas principal.

A ideia por trás disso é a realização de uma única consulta no banco de dados para obtenção das informações das contas. Assim, com uma única consulta, montaremos uma lista de contas na memória.

Vamos utilizar as seguintes estruturas:

TContaContabil representa todas as informações necessárias para o relatório.

TListaConta será nossa lista de contas.

TIndiceConta representa as informações necessárias para criarmos um índice da lista.

TListaIndiceConta é a lista de TIndiceConta. É o próprio índice em sí.

Mas por que o índice? Bom, essa é a grande jogada para termos desenpenho. Uma vez que eu tenha a lista de contas, ter uma lista auxiliar cuja ordenação possa ser sequencial, mas ainda mantendo uma referência com a lista principal que mantém sua ordenação natural, faz com que consiga utilizar algorítmos de alta performance na lista auxiliar, quando necessário.

A primeira coisa é gerar a lista de contas. Ao mesmo tempo em que uma conta é adicionado nessa lista, você deve também adicioná-la no índice:

A ordenação do índice é muito importante, porque é através dela que poderemos utilizar a busca binária.

Agora já existe uma lista com as informações necessárias, corretamente ordenadas.

Segundo passo: Buscar o valor dos lançamentos contábeis e já vinculá-lo com as contas contábeis, utilizando a performance da busca binária.

A busca binária é algorítmo excelente para termos performance em buscas. Para saber mais a respeito, click aqui!

A busca binária que utilizaremos aqui será implementada manualmente, visto as necessidades particulares dessa situação. Por isso, não estaremos utilizando a busca binária nativa do próprio Delphi, mas sim uma implementação específica:

As informações das contas precisam ser atualizadas. Cada cenário é um cenário específico, mas imaginando um onde a busca das contas seja feita obtendo essas informações do banco de dados, temos algo como:

O código acima simplesmente alimenta a lista já existente com as informações dos saldos das contas, sendo que o saldo de cada conta foi obtido por uma consulta no banco de dados. Assim, temos uma única select trazendo o saldo de todas as contas, mas o relacionamento com a nossa lista principal é realizado através da busca binária, nos dando uma enrome performance nesse relacionamento.

Terceiro passo: Montar a lista dos saldos iniciais – mesma operação anterior, só que com os valores anteriores ao período.

O terceiro passo é igual ao segundo, sendo que os valores dos saldos iniciais (ou o saldo anterior ao período) que também somam-se ao valor total da conta contábil devem ser adicionados na lista. Não irei repetir o processo aqui porque a lógica é a mesma, alterando-se apenas a forma como obtem esses valores.

Quarto passo: Partindo dos filhos, montar através de uma função recursiva, todos os valores das contas pais, somando-se sempre o valor atual com o valor dos filhos.

Uma vez que você possui uma lista com os valores de cada conta analítica já alimentados, é possível, utilizando-se da recursividade, alimentar o valor de cada conta pai, visto que os valores das contas pai é o somatório dos valores das contas filhas. Assim, temos:

Pronto.

Conclusão

A utilização da busca binária foi, sem dúvida, o grande fator para o aumento da performance. Trouxemos o custoso processo de vínculo dos valores com suas devidas contas para a memória e tratamos isso em alta performance. Assim, um processo que chegava ao ponto de durar alguns minutos, pôde ser realizado em apenas alguns segundos.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *