Programação Funcional em C# — Parte 02

Alex Alves
4 min readMar 15, 2021

--

Ir para Parte 01

Agora, vamos ver alguns exemplos comuns, que esboçam pontos de “falhas”, onde não atendem a esse paradigma e como alterá-los para estar em conformidade com a Programação Funcional.

Atendendo os princípios da imutabilidade

Identificando alguns pontos de falha no exemplo acima:

  • Os métodos estão, teoricamente, acoplados entre si, onde um depende do outro para sua execução. Porém não está claro isso, pois pode-se alterar, facilmente, a ordem deles sem apresentar erros.
  • Os side effects estão ficam ocultos nesse caso, pois nós alteramos os valores de “Question” e “Answer”, porém essa operação não é explicitada. Além disso, o método “SaveQuestion” oculta as entradas, ao salvar uma “Question”.

E como podemos mitigar esses pontos de falha? Explicitando todas as entradas e saídas:

  • Vamos eliminar os campos privados, pois não vamos precisar mais deles. Até o momento, poderíamos assimilá-las à variáveis globais, e isso, desde os tempos de universidade, nós aprendemos que não é uma boa prática na maioria dos casos.
  • Vamos deixar explícitos as entradas e saídas de cada método.

Explicitando as entradas e saídas, forçamos com que os métodos dependa um do outro na ordem esperada, de acordo com suas assinaturas. Se trocarmos a ordem, dará erro de compilação. Assim, fazemos com que nossas funções/classes sejam imutáveis e transparentes, se comprometendo com o que é proposto.

Contudo, ainda ficamos reféns dos “side effects”, pois veja o método “SaveQuestion”, o mesmo irá realizar uma alteração no banco de dados e isso não está totalmente claro neste método. Então, como lidar com estes efeitos colaterais que, muitas vezes, não conseguimos eliminar 100%? Bom, existe algumas guidelines para isso:

  • CQS (Command-Query Separation Principle): A ideia é separar os comandos, que causam os efeitos colaterais, das consultas, que estão “livres” desses efeitos. Os comandos, que alteram algo, devem ser do tipo void, ao contrário das queries, que recebem uma entrada e retornam algo, exaltando que a ideia das queries é não alterar nada e isso deve estar claro.

Entretanto, também, não há como seguir fielmente este princípio. Um exemplo disso é o trabalho com Listas/Filas:

Na última linha, você altera o estado da fila (comando) e, ao mesmo tempo, você recebe um retorno do primeiro item da lista. Isto viola os princípios CQS, porém, não faz sentido separar este código.

Além disso, na explicação que apresentei sobre CQS eu deixei entre aspas a parte que mencionam que as Queries estão livres dos Side Effects. Bom, ainda assim ela sofre com estes efeitos pois viola um dos princípios da programação funcional, que é termos funções puras. Uma função pura, dado uma entrada, sempre retorna o mesmo valor. Contudo, como estamos realizando uma consulta no banco de dados, os registro retornado pode ter sido alterado. A ideia é mitigar os efeitos e deixar tudo o mais transparente possível.

Vamos ver um outro caso arquitetural de como podemos tratar os Side Effects:

Imagem retirada do curso Applying Functional Principles in C#

Neste modelo acima, não focamos em nenhuma arquitetura do mercado, mas sim na ideia de separar as regras de negócio, que são imutáveis e geram artefatos para que a outra parte da sua aplicação use-os causando as mutações necessárias, como uma transação. Pontos positivos de desenvolver um sistema assim:

  • Simplificação das regras de negócio
  • Fácil de testar
  • CQS implantando “arquiteturalmente”, onde conseguimos separa os Efeitos
  • Operações ficam transparentes, nas regras de negócio.

De modo geral, o que estamos fazendo com programação funcional é basicamente como mostrado na imagem abaixo:

Imagem retirada do curso Applying Functional Principles in C#

A camada imutável recebe uma entrada e, como funções matemáticas, faz toda a lógica necessária e devolve algum resultado, como um artefato, Sendo assim, a camada mais externa recebe este artefato e faz alguma operação de alteração no sistema, como salvar no banco de dados.

A camada mais externa deve ser a mais burra possível.

Esse tipo de componente pode ser composto por outros N componentes como este, onde há uma entrada, a qual é processada, assim devolve-se um artefato o qual causa algum efeito e, pode chamar outro componente para processar algo a mais. Um exemplo disso, talvez seria a compra de algum produto no supermercado, seguido o fluxo:

  • Primeiro é cadastrado o cliente, recebendo uma entrada, processa/realiza algumas validações, devolve para a camada externa e salva o cliente
  • Em seguida, faz o mesmo com os produtos, para realização da compra

Com isso, terminamos o capítulo de como analisamos e resolvemos os efeitos causados na nossa aplicação. No próximo capítulo, vamos ver como lidar com exceções!

Ir para a Parte 03

--

--

Alex Alves

Bachelor in Computer Science, MBA in Software Architecture and .NET Developer.