O que Softwares Financeiros, Martin Fowler, Uncle Bob e Alexander Hamilton têm em comum?
Published: 2021-01-26 14:10:02

Como disse **Martin Fowler** no livro [_Patterns of Enterprise Application Architecture_] (https://martinfowler.com/eaaCatalog/money.html):
> "Uma boa parte dos computadores neste mundo manipula dinheiro, então sempre me intrigou que _Money_ não seja um tipo de dados em qualquer linguagem de programação convencional."

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/e4slck3pp4fdmaa8nwdw.png)

_Patterns of Enterprise Application Architecture_ sugere a implementação de uma classe _Money_, com atributos _currency_ e _amount_, bem como um método **_allocate_** que recebe uma lista de proporções e **distribui esse dinheiro sem perder valor com arredondamentos**. Nesse artigo vou mostrar como implementamos nosso _Money_ pra resolver um problema real de perda de centavos em rateio, bem como resolver o mistério do título.

Já tínhamos uma classe _Money_, o que facilitou muito na escrita do _Money Pattern_. Então se eu puder ter a pretensão de dar uma primeira dica é: **não use _Double_ ou _BigDecimal_ pra valores financeiros**. Construa seu _Value Object_. Depois de ter valores financeiros espalhados por todo seu sistema, vai ser bem difícil - mas não impossível - refatorar.

Nossa classe _Money_ não tem o atributo _currency_ pois não temos a necessidade de internacionalização, pelo menos não ainda. E não vamos ter implementação de _currency_ enquanto não precisarmos de fato. Assim como também não tínhamos o método allocate. Até que precisamos. Portanto nosso _Money_ era assim:

```Java
public class Money implements Serializable {

  private final BigDecimal amount;

  private Money(BigDecimal amount) {
    if (amount == null) {
      throw new AmountCantBeEmptyException();
    }
    this.amount = amount;
  }

  public static Money of(BigDecimal amount) {
    return new Money(amount);
  }

  public Money plus(Money addition) {
    return Money.of(this.amount.add(addition.amount));
  }

  public Money minus(Money discount) {
    return Money.of(this.amount.subtract(discount.amount));
  }

  public Money times(BigDecimal factor) {
    return Money.of(this.amount.multiply(factor));
  }

  public BigDecimal getAmount() {
    return amount;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null || !(obj instanceof Money)) {
      return false;
    }
    return this.amount.compareTo(((Money) obj).amount) == 0;
  }

  @Override
  public int hashCode() {
    return this.amount.hashCode();
  }

}
```
Isso resolvia boa parte dos nossos problemas financeiros. Até que o fatídico dia em que precisamos do **allocate** chegou. Uma **despesa de R$ 1000,20 deveria ser rateada entre várias contas** da seguinte maneira:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gsr9oqko2k8nikjictfe.png)

Como podem perceber, se aplicarmos o percentual de proporção de rateio em cada item, nunca chegaremos em **R$ 1000,20** - isso porque **centavo é uma medida indivisível**, então um valor financeiro representado por _Money_ **não pode ser R$ 229,0458** por exemplo. Se arredondarmos pra baixo, "perdemos" 3 centavos. Se arredondarmos pra cima, "ganhamos" 2 centavos. E note que mesmo usando o Round Half Even, perdemos precisão. Esse último é o modo de arredondamento comumente utilizado em sistemas financeiros, inclusive homologado pela ABNT através da norma [NBR 5891](https://pt.wikipedia.org/wiki/Arredondamento#Regras_de_arredondamento).

E sabem quem enfrentou um problema parecido? Ele mesmo, George Washington, o primeiro presidente dos Estados Unidos, e uma das soluções propostas foi de **Alexander Hamilton**, que veio a ser o Primeiro Secretário do Tesouro dos EUA, e seu _Largest Remainder Method_.

O problema enfrentado por eles à época era resumidamente o seguinte, descrito no [U.S. Census Bureau](https://www.census.gov/history/www/reference/apportionment/methods_of_apportionment.html):

Um país com 4 estados, com diferentes populações em cada estado, e 20 assentos no Senado. Quantos senadores cada estado teria para que eles fossem representados proporcionalmente? Um senador, assim como um centavo, também não pode ser dividido em partes. Certo? Só pra confirmar aqui... =D

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/slzfsqoc8b9n1axephlz.png)

O método de Hamilton consiste em calcular a proporção da **população total de 11882** pela quantidade de **20 assentos disponíveis** chegando no **divisor** de 594,1. Agora pra cada estado **divide-se sua população por esse divisor**, por exemplo no estado 1 **2560/594,1** chegando no **quociente de 4,31**.

Como já concordamos, **não podemos ter 4 senadores e 0,31 de um senador** para um estado. Então o número de senadores do estado 1 é **4**, que é a **parte inteira** do resultado. E isso acontece para cada um dos outros resultados. Ao final, por conta dos arredondamentos da parte inteira, o **total de senadores alocados** nos estados **é de 18**. Pra quais estados vão os outros 2? É aí que **Hamilton** entra com uma **possível solução**. A proposta dele é que distribuamos esses 2 assentos para os estados que ficaram com a maior parte decimal antes do arredondamento.

Explico. Se suprimirmos a parte inteira da coluna **_Senadores por Estado_** e ordenarmos pela parte decimal, o resto, em ordem decrescente, ficaríamos com:

1. 1,**67**
2. 5,**58**
3. 8,**44**
4. 4,**31**

Como temos um total de **2 senadores pra redistribuir**, os estados que recebem esses senadores são os **2 primeiros da lista**. Resultando na coluna **_Senadores por estado após Método Hamilton_**.

Existe um paradoxo a se notar que foi descoberto ao aplicar o método Hamilton na população do Alabama, por isso também é chamado de [Paradoxo do Alabama](https://en.wikipedia.org/wiki/Apportionment_paradox#Alabama_paradox), que não será coberto aqui. Mas é onde entra o nosso querido **Uncle Bob** no artigo.

Entre suas **inúmeras contribuições** para a comunidade de desenvolvimento de software, Uncle Bob popularizou os **princípios SOLID**. Era relevante lá na década de 90, é relevante hoje. O princípio que iremos focar agora é o Open-Closed Principle.

Precisamos alterar nossa classe _Money_ para comportar o método **allocate** pra conseguir distribuir seu valor em propoções sem perder centavos. Então criamos o seguinte método:

```Java
public class Money implements Serializable {
  private static final BigDecimal ONE_HUNDRED = new BigDecimal(100);

  public List<Money> allocate(List<BigDecimal> ratios, RemainderDistribution distribution) {
    long amountInCents = toCents();
    List<Quota> quotas = new ArrayList<>();
    for (BigDecimal ratio: ratios) {
      quotas.add(new Quota(amountInCents, ratio));
    }
    distribution.distribute(quotas, amountInCents);
    return quotas.stream().map(Quota::toMoney).collect(Collectors.toList());
  }

  public long toCents() {
    return this.amount.multiply(ONE_HUNDRED).longValue();
  }
}
```
```Java
public class Quota {
  private static final BigDecimal ONE_HUNDRED = new BigDecimal(100);

  private long amount;
  private long total;
  private BigDecimal ratio;

  public Quota(long total, BigDecimal ratio) {
    this.total = total;
    this.ratio = ratio;
    this.amount = ratio.multiply(BigDecimal.valueOf(total)).longValue();
  }
}
```
```Java
public interface RemainderDistribution {
  void distribute(List<Quota> quotas, long total);
}
```

Lembra do Paradoxo do Alabama? Mesmo tendo validado com nosso Product Manager que adotaríamos o método de Hamilton ainda temos dúvidas sobre quais consequências esse paradoxo, ou até outro, pode ter. Então não queríamos "chumbar" a distribuição no código do _Money_. Queríamos deixar nosso _Value Object_ aberto para extensão mas fechado para modificação. Por isso _RemainderDistribution_ é uma interface. Uma das suas implementações, a que utilizamos, é a _HamiltonApportionmentDistribution_.

```Java
public class HamiltonApportionmentDistribution implements RemainderDistribution {

  @Override
  public void distribute(List<Quota> quotas, long total) {
    long remain = total;
    for (Quota quota : quotas) {
      remain = remain - quota.getAmount();
    }
    List<Quota> sortedQuotas = quotas.stream().sorted(Comparator.comparing(Quota::getFractionalPart).reversed()).collect(Collectors.toList());
    Iterator<Quota> iterator = sortedQuotas.iterator();
    while(remain > 0) {
      remain = remain - 1;
      iterator.next().addRemain(1);
    }
  }

}
```

Dessa maneira **resolvemos** aquele problema inicial, onde **arredondando pra baixo "perdíamos" 3 centavos** (lembre-se que o arredondamento pra baixo é por conta dos centavos, similar a quando "arredondamos um senador" pra baixo). **O cálculo deve ser feito utilizando a menor unidade de medida, ou seja, centavos**. E chegamos no seguinte resultado:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ndkz2etabtjx8es07g8o.png)

Espero que até aqui eu tenha desmistificado a intersecção entre **Alexander Hamilton**, **Martin Fowler** e **Uncle Bob** com os **sistemas financeiros**. E, de quebra, você ganha uns códigos pra poder aplicar na sua linguagem favorita. Ah, todo esse código foi **coberto por testes unitários**. E sabe quando você leu as sentenças na **primeira pessoa do plural**? É porque **desenvolvemos essa solução em 3**, então todo o crédito também ao [Victor Sales de Brito](https://www.linkedin.com/in/victorsalesbrito/) e [Lucelia Siqueira](https://www.linkedin.com/in/lucelia-siqueira-ab454919/). Valeu, time!

**Bônus**: pra saber quem foi **Alexander Hamilton**, existe um musical disponível na **Disney+**, chamado Hamilton. Conta a história desse que foi o **Primeiro Secretário do Tesouro Americano** em forma de rap, hip hop e jazz, gravado diretamente da Broadway em 2016. Segue trailer desse musical fantástico:

{% youtube DSCKfXpAGHc %}