Executando verificação de segurança...
4

Criando um processador de carteiras em C++ - Dia 02: O primeiro commit

Olá pessoal :D Recentemente decidi me aventurar num projetinho pessoal para ampliar meu portfolio de backend: criar um processador de carteiras em C++. Para documentar e compartilhar todo esse processo, vou todos os dias escrever um artigo curto relatando o que fiz e adicionando considerações importantes em cada etapa do processo.

O código do processador ficará aberto à todos através deste link. Quem quiser acompanhar mais de perto e quem sabe até colaborar com o projeto, pode ficar à vontade para dar follow, fork ou stars no repositório.

Aprendendo a programar em C++

Acho que algo importante que deixei de comentar no artigo do dia 01 é que eu nunca programei em C++ :D

Aí você me pergunta... então por que raios você escolheu C++ como a stack principal do seu projeto? A resposta é simples: Pelo desafio e porque considero C++ a linguagem mais apropriada para os meus objetivos iniciais. C++ é uma linguagem muito leve e muito performática, têm tipagem estática e me permite controlar com certa precisão todos os meus cálculos. Era exatamente isso que eu precisava: um sistema eficiente e sólido em termos de cálculos financeiros e contratos de entrada e saída.

Preciso admitir que não parti do zero. Como contei aqui neste artigo no último mês mergulhei de cabeça na linguagem C, o que me deu uma boa base inicial para aprender C++.

Comecei os estudos com o curso do Codacademy, mas logo ele ficou bastante entediante e repetindo coisas que eu já sabia. Então optei pelo método clássico: entrei no w3schools e fui passando página por página, conhecendo a semântica da linguagem.

Aqui vai um comentário importante sobre fazer projetos novos em linguagens que você não conhece: o que muda entre a maioria das linguagens é principalmente a semântica, ou seja, o jeito como se escrevem as funções, loops, condicionais, etc. A lógica da programação continua a mesma quase sempre. Alguns detalhes sobre a linguagem em específico podem ser importantes (saber como a linguagem faz a alocação de memória, quais são seus data types e como são implementados, se é possível fazer programação assíncrona, etc), mas eles não te impedem de começar um novo projeto. Essas são coisas que a gente aprende com o uso da linguagem no dia a dia.

Meu primeiro commit

Por fim, depois de aprender como escrever classes, funções e tudo mais em C++, coloquei de fato a mão na massa. Meu primeiro trabalho foi definir a arquitetura do meu sistema. Optei por dividir em módulos. Inicialmente um para as carteiras e outro para as transações.

No meio do caminho, vi que não existia nenhum package de terceiros que implementava as melhores práticas em cálculos financeiros. Então também criei um módulo money onde eu mesma farei essa implementação.

Dessa forma, ficou assim minha estrutura de pastas:

Estrutura de Pastas do Projeto

Dentro de cada um desses módulos, criei um arquivo .h que é a extensão de arquivos de interface em C e C++. Nelas, defini as minhas classes, seus métodos e propriedades, de acordo com o benchmarking que fiz ontem. As interfaces ficaram assim:

// money.h

#include <string>
using namespace std;

class Money {
    public:
        int amount;
        int precision;

        void add(Money money);

        void subtract(Money money);

        void multiply(Money money);

        void divide(Money money);

        void toUnit();
};
// transaction.h

#include <string>
using namespace std;

enum TransactionType {
    INCOME = 'INCOME',
    EXPENSE = 'EXPENSE',
};

struct UserBalance {
    double balance;
};

struct WalletBalance {
    string wallet_name;
    double balance;
};

struct TypeBalance {
    TransactionType transaction_type;
    double balance;
};

struct CategoryBalance {
    string category_name;
    double balance;
};

class Transaction {
    public:
        double amount;
        TransactionType type;
        bool completed;
        string time;
        string category;
        string wallet;
        bool ignore;

        static UserBalance getUserBalance(Transaction transactions[]);

        static WalletBalance getWalletBalance(Transaction transactions[]);

        static TypeBalance getTypeBalance(Transaction transactions[]);

        static CategoryBalance getCategoryBalance(Transaction transactions[]);
};
// wallet.h

#include <string>
#include "transaction/transaction.h"

using namespace std;

class Wallet {
    public:
        string name;
        double initial_balance;
        double current_balance;

        void addTransaction(Transaction transaction);

        void removeTransaction(Transaction transaction);
};

Conclusão

Por hoje, foi isso. Acho que o próximo passo é melhorar essas interfaces, populando com mais métodos e adicionando a documentação de cada propriedade e função através dos comentários. Veja aqui mais sobre API docs de projetos em C++.

Além disso, amanhã já devo poder começar a escrever a lógica das funções previamente definidas. Um desafio, é entender como é a melhor forma de fazer nossa aplicação consumir dados e fazer um parser para inicializar nossas classes a partir deles.

Carregando publicação patrocinada...
3

Bacana a iniciativa, vou acompanhando daqui!
Gostaria de deixar alguns pontos pra discussão e um link de material sobre boas práticas em C++: https://github.com/cpp-best-practices/cppbestpractices
Esse link tem basicamente o fonte de um livro feito pelo Jason Turner, bem conhecido dentro da comunidade de C++. Tente ler o que puder nesse link/livro e quase qualquer coisa que eu escreva abaixo vai ser redundante. :)

  • Evite usar using namespace xxx: seu código vai crescer. Namespaces em C++ existem, entre outras coisas, pra que se evite colisão entre nomes. Sim, as declarações ficam maiores, mas é importante manter claresa sobre o que está onde e de onde vem algo.
  • Evite arrays puros: array em C++ não é extensível. Em geral você vai usar quando sabe exatamente quanto elementos uma sequencial vai ter (nem mais, nem menos). Em geral, você vai acabar usando muito mais tipos como std::vector ou std::list.
    • Se for possível adotar C++20, você pode optar por implementar funções que recebam um tipo std::range (https://www.modernescpp.com/index.php/c-20-the-ranges-library), que é basicamente uma view para diversos objetos que buscam abstrair sequencias de outros objetos. Com isso você pode chamar a mesma função se seu objeto inicial for um std:vector, srd::list ou std::array.
  • Prefira receber refêrencias para objetos do que cópias dos mesmos: esse tópico é um pouco extenso pra entrar no detalhe, Por exemplo, o método addTransaction poderia ter a assinatura: bool addTransaction(const Transaction& transaction);, pos desta maneira você passa apenas um endereço de memória para a função e não o objeto completo.
  • NUNCA use double para dados financeiros: prefira usar um int64_s e formatar de acordo na camada de exibição.

Boa sorte! :D

2

Caramba, muitíssimo obrigada pelas dicas. Já dei uma olhada por cima no repositório e achei super interessante. Com certeza vou absorver o máximo que puder de lá.

Sobre usar double para dados financeiros, sei que essa é uma péssima prática e gera diversas inconsistências nos cálculos. Por isso a ideia de criar um módulo money. Nesse módulo, todas as operações serão feitas com inteiros (guardados na propriedade amount) e apenas na hora de salvar na carteira ou na transação será convertido para double na função toUnit().

Pelo que conheço de padrões em cálculos financeiros, essa é a melhor prática, não?

1

Olha, que eu conheça fica tudo sempre como int64_t e na camada de apresentação você formata usando uma callback de formato: centavos -> divide por 100, se for bitcoin por exemplo divide por 10^8 e por aí vai.

1

Uma sugestão parça, o arquivo .h foi criado para ser cabeçalho de C, quando se programa em cpp utilizamos .hpp. Isso não é uma unamidade, .h pode funcionar mas convencionalmente usamos .hpp

2

Na verdade, não acredito que convecionalmente é usado .hpp, pois eu já vi vários repositórios que usam .h, por exemplo stockfish ou Hazel.

Outra referêcia que costumo usar para estilo de código em C++ é o Google C++ Style Guide. Ele recomenda:

Header files should be self-contained (compile on their own) and end in .h. Non-header files that are meant for inclusion should end in .inc and be used sparingly.

1

Você já considerou reescrever em Rust? Bricadeiras a parte, considere minha igonorancia no seu contexto, mesmo lendo seu primeiro post. Pra mim ficou claro que você gostaria de apliar seu portifolio, aprender mais sobre uma nova linguagem e que C++ ajudaria com os calculos. No entanto, Rust traria esses beneficios, introduziria vc aos conceitos de C++ e a muitos outros bem mais modernos, além de ser uma linguagem extremamente promissora. Longe de mim de desmotivar você, talvez esse seja um próximo passo. Sucesso!