Como combater a complexidade usando Scala e ZIO

Como combater a complexidade usando Scala e ZIO

Ainda falando do Hackathon ZIO Berlin 2019, pude assistir a várias palestras e sessões, que abrangiam desde detalhes técnicos até conceitos mais abrangentes. Vale a pena reproduzir aqui algumas ideias que possam contribuir para que mais pessoas da comunidade adotem ZIO.

Complexidade

Vamos admitir, o inimigo número 1 do programador é a complexidade. Costumamos lidar com ela entre dois extremos: 1) ignorar (ou ocultar) situações onde ela ocorre, 2) realizar um esforço mental enorme para entender todos os detalhes e variações de um problema.

Quanto mais próximos do lado 1 dessa régua de complexidade, maior a possibilidade de acumular dívida técnica. Por exemplo, podemos ignorar que em produção temos mais conexões simultâneas do que quando realizamos testes em desenvolvimento.

Quanto nos movemos para o lado 2 da régua, precisamos de mais tempo para “carregar” o problema em nossas cabeças e analisar todas as implicações.

Onde encontramos uma boa medida?

A solução é dividir o problema em partes menores, como consequência teremos um esforço menor de análise, sem falar na possibilidade de reuso. Isso não é novidade, porém:

Como evitar que surjam efeitos inesperados quando combinamos partes menores para criar um sistema maior?

Infelizmente, essa situação é razoavelmente comum. As razões são várias.

Três passos para eliminar surpresas

Para confiar em nossos programas devemos sistematicamente eliminar suas potencias fontes de incerteza.

  1. Em primeiro lugar, nem sempre um programa (ou módulo ou função) fornece resposta para todos os tipos possíveis de entrada. Um programa ideal deveria ser total, ou seja, para cada entrada deveria existir uma saída correspondente. Basta ver que muitos programas produzem exceções quando não sabem como lidar com determinado tipo de situação, portanto, não podem ser ditos “totais”.
  2. Outro problema é que o conjunto de respostas deveria ser estável, quer dizer, dada uma mesma entrada, o programa responderia sempre com a mesma saída. Programas que produzem sempre o mesmo resultado em função de uma entrada são chamados determinísticos.
  3. Por último, se o ambiente externo tiver influência sobre o programa, fica difícil de prever o impacto desse programa em outros programas e na composição final. O problema é grave, pois um programa que não interage com o ambiente externo não tem utilidade alguma.
A solução é pensar no programa como uma sinfonia.


Adiando a interação com o ambiente externo até o último momento

A solução dada pela programação funcional requer que o programador separe seu raciocínio em duas partes pois precisará adiar a segunda parte enquanto for possível. Para ilustrar, vamos comparar o programa com uma sinfonia.

Na sinfonia, primeiro o autor escreve a partitura. Cada seção da orquestra tem a sua parte, e o maestro possui uma combinação com todas elas. Quando começa o espetáculo, o maestro ergue a batuta e todos entram em ação de acordo com suas partes individuais.

A execução da sinfonia é adiada até o momento em que o maestro a inicia. A escrita e composição são separadas da execução. Essa é a principal ideia por trás da programação funcional.

À primeira vista, a estratégia acima pode não parecer muito diferente do que fazemos como programadores no estilo imperativo todos os dias. Porém, o que a programação funcional oferece é um modelo mental para assegurar que os programas que irão compor um sistema sejam totais, determinísticos e livres de efeitos colaterais.

Como resultado, o programador passa a confiar em seus módulos, programas e subprogramas, sabendo que não encontrará surpresas, e que poderá se manter em uma posição confortável na régua da complexidade, sem ter que “carregar” todo o sistema em sua cabeça para analisar todas as possíveis variações.

Por que usar uma biblioteca para aplicar o estilo funcional

Scala possui um bom ferramental para lidar com composição de programas. A principal técnica que usamos em Scala é “for-comprehension”. Vamos aproveitar um pouco da discussão sobre paralelismo e assincronicidade (veja meu artigo anterior) para gerar um exemplo com Futures:

  def readFromUrl: Future[String] = ???
  def countWords(text: String): Future[Long] = ???
  
  for {
    text <- readFromUrl
    size <- countWords(text)
  } yield size
  


O mesmo exemplo com ZIO seria:

  def readFromUrl: Task[String] = ???
  def countWords(text: String): Task[Long] = ???
  
  for {
    text <- readFromUrl
    size <- countWords(text)
  } yield ZIO.succeed(size)
  


Os exemplos acima, além de parecidos, são simples, mas o importante é ver que a sequência da composição das etapas é clara. Intencionalmente deixei de lado as dificuldades em se empregar esse tipo de composição caso os tipos de retorno (Future[A] ou Task[A]) escapassem a certas regras. ZIO obedece às regras necessárias para compor programas dessa maneira e contribui para que o programa resultante seja total, determinístico, e que as interações com o ambiente externo sejam adiadas até que ele seja finalmente executado com auxílio do método principal.

Comparando esforços

Se por um lado o programador que abraça o estilo aqui proposto precisa se adaptar a um novo modelo mental e eventualmente trabalhar na migração de código legado, isso é compensado pelo sentimento de maior segurança ao escrever os programas e facilidade para escrever testes efetivos. Após um breve período, se espera que a produtividade da equipe aumente sucessivamente.

ZIO não é a única solução a recomendar o estilo funcional. Existem outras proposições como Cats, Scalaz, ou técnicas alternativas como Free Monads e Tagless-Final, algumas delas já bastante usadas em projetos de boa envergadura.

Contudo, a característica que mais me atraiu em ZIO é o empenho que sua equipe de desenvolvimento dedica em manter a curva de aprendizado de iniciantes livre de um pesado jargão que confunde e assusta a maioria dos que buscam se aprofundar na programação funcional.

Parabéns a John De Goes e a todos os colaboradores desse incrível projeto.









Entre para ver ou adicionar um comentário

Outros artigos de Maurício Fernandes de Castro

Outras pessoas também visualizaram

Conferir tópicos