Contabilidade para Programadores, Parte 1: Dinheiro
Já tem algum tempo que sinto vontade de aprender contabilidade.
Existem diversos livros e artigos da internet sobre o assunto, mas eu nunca consegui encontrar algum material de estudos que fosse especificamente voltado para programadores e que também incluísse trechos de código para facilitar o entendimento.
Eu até cheguei a encontrar 4 artigos que se intitulam "accounting for developers" ("contabilidade para programadores"), mas que não possuem trechos de código:
- Accounting for Developers 101
- Accounting for Developers 102
- Accounting for Developers, Part I
- Accounting for Developers, Part II
Encontrei também dois cursos que se propõe a ensinar contabilidade para programadores, mas este assunto em específico eu quero aprender de forma autodidata. De qualquer forma, deixo os links aqui para quem tiver interesse:
- Curso Online de Contabilidade para Programadores PHP - Tudo o que os Programadores PHP Precisam Saber sobre Contabilidade
- Curso de contabilidade para programadores
Abordagem
Não tentarei expor aqui a história da contabilidade, suas subdivisões, ou o porquê ela é necessária. Para isso, eu recomendo ler o artigo da Wikipedia sobre contabilidade, que aborda esses assuntos.
O foco desta série de artigos é expor os conceitos da contabilidade através da utilização de código fonte. Darei exemplos em Typescript.
Esta abordagem é útil para ajudar programadores a fixar melhor os conceitos.
A meta é que no final da série de artigos, tenhamos um sistema web de contabilidade que seja funcional, capaz de realizar atividades básicas.
Os tópicos a seguir não são o foco dos artigos:
- Criar uma arquitetura ideal para um sistema de contabilidade. Os exemplos de código foram feitos para apresentar conceitos e não são, de forma alguma, otimizados. De qualquer forma, tentarei seguir boas práticas na medida do possível - Inclusive, este primeiro post é sobre adotar uma boa prática.
- Avançar muito profundamente nos tópicos de contabilidade. A ideia é somente adquirir uma competência basica.
Lidando com valores monetários programaticamente
Um sistema de contabilidade lida basicamente com dinheiro ou com coisas que possam ser precificadas. Por isso, é muito importante escolher uma estratégia que evite problemas ao calcular valores monetários.
Um erro muito comum em sistemas que lidam com dinheiro é a obsessão por tipos primitivos. Tratar valores monetários como um simples número é um erro porque um valor monetário é mais do que somente um número: Temos de lidar com conversões de moeda, arredondamento, internacionalização (i18n) e localização (i10n).
A maioria das linguagens de programação já possuem bibliotecas que implementam o padrão ISO 4217 para resolver esse problema. Como exemplos, podemos utilizar a dinero.js com Javascript e a Joda-Money com Java. Não entrarei em detalhes sobre como instalar essas bibliotecas, pois não é foco do artigo.
A primeira coisa que vamos fazer em nosso sistema é encapsular essa dependência. Este artigo mostra por quê isso é importante.
// src/dinheiro.ts
import Dinero from "dinero.js";
export class Dinheiro {
private _objetoEncapsulado: Dinero.Dinero;
constructor(private _moeda: Moeda, private _valorEmCentavos: number) {
this._objetoEncapsulado = Dinero({
amount: this._valorEmCentavos,
currency: this._moeda,
});
}
adicionar(other: Dinheiro) {
return this.construirAPartirDoObjetoEncapulado(
this._objetoEncapsulado.add(other._objetoEncapsulado)
);
}
subtrair(other: Dinheiro) {
return this.construirAPartirDoObjetoEncapulado(
this._objetoEncapsulado.subtract(other._objetoEncapsulado)
);
}
multiplicar(factor: number) {
return this.construirAPartirDoObjetoEncapulado(
this._objetoEncapsulado.multiply(factor)
);
}
dividir(factor: number) {
return this.construirAPartirDoObjetoEncapulado(
this._objetoEncapsulado.divide(factor)
);
}
menorQue(other: Dinheiro) {
return this._objetoEncapsulado.lessThan(other._objetoEncapsulado);
}
menorOuIgualA(other: Dinheiro) {
return this._objetoEncapsulado.lessThanOrEqual(other._objetoEncapsulado);
}
igualA(other: Dinheiro) {
return this._objetoEncapsulado.equalsTo(other._objetoEncapsulado);
}
maiorOuIgualA(other: Dinheiro) {
return this._objetoEncapsulado.greaterThanOrEqual(other._objetoEncapsulado);
}
maiorQue(other: Dinheiro) {
return this._objetoEncapsulado.greaterThan(other._objetoEncapsulado);
}
formatar(linguagem: string) {
return new Intl.NumberFormat(linguagem, {
style: "currency",
currency: this._moeda,
// por enquanto, ignoramos o fato de outras moedas terem divisões diferentes
// de centavos para simplificar o código
}).format(this._valorEmCentavos / 100);
}
private construirAPartirDoObjetoEncapulado(obj: Dinero.Dinero) {
return new Dinheiro(obj.getCurrency(), obj.getAmount());
}
}
export type Moeda = Dinero.Currency;
export default function dinheiro(moeda: Moeda, valorEmCentavos: number) {
return new Dinheiro(moeda, valorEmCentavos);
}
// exemplo
const milReais = dinheiro(1000_00, "BRL");
const milEDuzentosReais = milReais.adicionar(dinheiro(200_00, "BRL"));
console.log(milEDuzentosReais.formatar("pt-BR")); // R$ 1.200,00
Legal! Agora que encapsulamos essa dependência, podemos seguir em frente.