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:

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:

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:

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.

Agora, leia a parte 2!