Código simples é diferente de código simplista: Elm vs JavaScript
Tags: javascript, html, elm, webdev
Published: 2022-01-06 11:51:27

Existem linguagens, _frameworks_ e bibliotecas que se esforçam para permitir que seja possível realizar tarefas relativamente complexas escrevendo poucas linhas de código. _JavaScript_ é um bom exemplo. Para fazer uma chamada _http_ para uma página do meu site usando esta linguagem, basta escrever uma única linha:
```JavaScript
await fetch("https://segunda.tech/sobre")
```
A maioria das pessoas provavelmente não considera este código é difícil ou complexo, mas podem existir cenários de erros escondidos que não são triviais de tratar. Para analisar isso, vou mostrar uma implementação de uma pequena página utilizando _JavaScript_ puro e discutir os potencias problemas. Em seguida vou mostrar como implementar a mesma solução utilizando a [linguagem de programação _Elm_](https://elm-lang.org) e analisar os mesmos pontos. 

## Exercício: recuperando os nomes dos Pokémons

Para exemplificar o problema que quero discutir neste artigo, implementei em _html_ e _JavaScript_ puro (utilizando _Ajax_) o mínimo necessário para exibir uma lista com nomes de Pokémons. Para isso utilizei a _API_ do site [PokéAPI](https://pokeapi.co). O _endpoint_ para recuperar a lista dos 5 primeiros Pokémons é bem simples: basta acionar a _URL_ `https://pokeapi.co/api/v2/pokemon?limit=5` e o retorno será um _json_ contendo o resultado abaixo.
```json
{
  "count": 1118,
  "next": "https://pokeapi.co/api/v2/pokemon?offset=5&limit=5",
  "previous": null,
  "results": [
    {
      "name": "bulbasaur",
      "url": "https://pokeapi.co/api/v2/pokemon/1/"
    },
    {
      "name": "ivysaur",
      "url": "https://pokeapi.co/api/v2/pokemon/2/"
    },
    {
      "name": "venusaur",
      "url": "https://pokeapi.co/api/v2/pokemon/3/"
    },
    {
      "name": "charmander",
      "url": "https://pokeapi.co/api/v2/pokemon/4/"
    },
    {
      "name": "charmeleon",
      "url": "https://pokeapi.co/api/v2/pokemon/5/"
    }
  ]
}
```
Neste exercício o objetivo é recuperar estes dados de forma assíncrona e listar na página _html_ apenas o conteúdo do campo _name_ (que está dentro de _result_). 

## Implementando uma solução utilizando _html_ e _JavaScript_ puro

Existem várias formas de se resolver este problema utilizando estas tecnologias. Abaixo apresento a minha implementação. 

```html
<!doctype html>

<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>Lista de Pokémons em HTML e JavaScript</title>
  <meta name="author" content="Marcio Frayze David">
</head>

<body>
  <p id="loading-message">
    Carregando lista de nomes dos Pokémons, aguarde...
  </p>

  <ul id="pokemon-names-list">
  </ul>

  <script>
    
    (async function() {

      await fetch("https://pokeapi.co/api/v2/pokemon?limit=5")
        .then(data => data.json())
        .then(dataJson => dataJson.results)
        .then(results => results.map(pokemon => pokemon.name))
        .then(names => addNamesToDOM(names))

      hideLoadingMessage()
    
    })();

    function addNamesToDOM(names) {
      let pokemonNamesListElement = document.getElementById('pokemon-names-list')
      names.forEach(name => addNameToDOM(pokemonNamesListElement, name))
    }

    function addNameToDOM(pokemonNamesListElement, name) {
      let newListElement = document.createElement('li')
      newListElement.innerHTML = name
      pokemonNamesListElement.append(newListElement)
    }

    function hideLoadingMessage() {
      document.getElementById('loading-message').style.visibility = 'hidden'
    }

  </script>

</body>
</html>
```
A ideia é que ao finalizar a chamada _Ajax_, a mensagem de carregamento deixe de aparecer e a lista contendo os nomes dos Pokémons seja carregada dentro da _tag_ com o _id_ _pokemons-names-list_. Publiquei [esta página no editor _on-line_ _JSFiddle_](https://jsfiddle.net/027p8sa9) para que veja o comportamento esperado.

Sei que dificilmente alguém escreveria um código desta forma. Não usei nenhum _framework_ ou biblioteca externa e fiz algumas coisas que muitos considerariam más práticas (como por exemplo colocar o código _JavaScript_ direto no _html_). Mas mesmo que tivesse implementado esta solução com tecnologias populares como _[React](https://pt-br.reactjs.org)_, _[JSX](https://en.wikipedia.org/wiki/JSX\_(JavaScript))_ e _[Axios](https://axios-http.com)_, os potencias problemas que quero discutir aqui provavelmente ainda existiriam.

Analisando o código acima, as perguntas que gostaria que você tentasse responder são:
- O que vai acontecer caso ocorra um _timeout_ na chamada _Ajax_? 
- Se o servidor voltar um _status http_ de falha, o que vai acontecer?
- Se o servidor retornar um _status http_ de sucesso mas o formato do conteúdo retornado for diferente do esperado, o que vai acontecer?

O código acima não responde nenhuma destas perguntas de forma clara. É fácil visualizar o "caminho feliz", mas qualquer situação inesperada não esta sendo tratada de forma explícita. E embora nunca devêssemos colocar um código em produção que não trate estes cenários, a linguagem _JavaScript_ não nos **obriga** a lidar com eles. Caso alguém do seu time esqueça de fazer o tratamento adequado para um desses potenciais problemas, o resultado será um **erro em tempo de execução**. 

Se o seu time tiver azar, talvez estes cenários só apareçam quando o código já estiver em produção. E quando isso inevitavelmente acontecer, é bem provável que coloquem a culpa na pessoa desenvolvedora que implementou aquela parte do sistema.

Mas se sabemos que este tipo de situação precisa obrigatoriamente ser tratada, **por que as linguagens, _frameworks_ e bibliotecas permitem que este tipo de código seja escrito?**

## O que é uma solução **simples**?

Existe uma diferença grande entre uma solução ser **simples** e ser **simplista**. Esta solução que escrevi em _JavaScript_ não é simples, mas simplista, já que ela ignora aspectos fundamentais do problema em questão.

Já linguagens como _[Elm](https://elm-lang.org)_, por sua vez, tendem a nos **obrigar** a pensar e implementar a solução para todos os potenciais cenários de problemas. O código final provavelmente será maior, mas trará consigo a garantia de que **não vamos ter erros em tempo de execução**, já que o compilador verifica e impõe que a pessoa desenvolvedora trate todos os caminhos possíveis, não deixando espaço para falhas previsíveis.

Claro que isso não significa que _webapps_ criadas nesta linguagem estão isentas de todo e qualquer tipo de erro. Podem ocorrer problemas na lógica de negócio e a aplicação ter um comportamento inesperado, ou aspectos visuais do sistema podem não estar como gostaríamos. Mas aqueles erros previsíveis, que podem ser encontrados por um compilador, vão deixar de existir. Um bom exemplo é o famoso _**Undefined is not a function**_ do _JavaScript_. Já em _Elm_, **é impossível escrever um código que resulte em qualquer erro de _runtime_**.

Outra vantagem desta abordagem é que temos um código realmente **auto-documentado**. Deve ficar muito claro, por exemplo, qual o formato do retorno esperado, quais campos são obrigatórios e quais são opcionais, etc.

## Implementando a mesma solução em _Elm_

Agora vamos analisar uma solução escrita em _[Elm](https://elm-lang.org)_ para este mesmo problema. Se você não conhece essa linguagem (ou alguma outra similar, como _[Haskell](https://haskell.org)_ ou _[PureScript](https://purescript.org)_), provavelmente vai achar a sua sintaxe um pouco estranha. Mas não se preocupe, você não precisa entender totalmente este código para compreender a proposta deste artigo.

Primeiro precisamos de um arquivo _html_ simples, que irá hospedar nossa página. Esta abordagem é bastante similar ao que é feito quando utilizamos ferramentas como _[React](https://pt-br.reactjs.org)_ ou _[Vue](https://br.vuejs.org)_.
```html
<!doctype html>

<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>Lista de Pokémons em HTML e Elm</title>
  <meta name="author" content="Marcio Frayze David">
</head>

<body>
  <main></main>
  <script>
    Elm.Main.init({ node: document.querySelector('main') })
  </script>
</body>
</html>
```
Desta vez nosso _html_ quase não tem lógica. Ele apenas irá carregar a aplicação escrita em _Elm_ (previamente compilada) e colocar seu conteúdo dentro da _tag_ _main_.

Agora sim a parte interessante: o código escrito em _Elm_. Vou primeiro listar o código por completo e depois destacar e comentar algumas partes mais relevantes para o tema deste artigo.
```elm
module Main exposing (..)

import Browser
import Html exposing (..)
import Http
import Json.Decode exposing (Decoder)


-- MAIN


main =
  Browser.element
    { init = init
    , update = update
    , subscriptions = subscriptions
    , view = view
    }


-- MODEL


type alias PokemonInfo = { name : String }

type Model
  = Failure
  | Loading
  | Success (List PokemonInfo)


init : () -> (Model, Cmd Msg)
init _ =
  (Loading, fetchPokemonNames)


-- UPDATE


type Msg
  = FetchedPokemonNames (Result Http.Error (List PokemonInfo))


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of

    FetchedPokemonNames result ->
      case result of
        Ok pokemonsInfo ->
          (Success pokemonsInfo, Cmd.none)

        Err _ ->
          (Failure, Cmd.none)


-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none


-- VIEW


view : Model -> Html Msg
view model =
  case model of
    Failure ->
        text "Por alguma razão, não foi possível carregar a lista com nome dos Pokémons. 😧"

    Loading ->
      text "Carregando lista de nomes dos Pokémons, aguarde..."

    Success pokemonsInfo ->
      ul []
        (List.map viewPokemonInfo pokemonsInfo) 


viewPokemonInfo : PokemonInfo -> Html Msg
viewPokemonInfo pokemonInfo =
  li [] [ text pokemonInfo.name ]


-- HTTP


fetchPokemonNames : Cmd Msg
fetchPokemonNames =
  Http.get
    { url = "https://pokeapi.co/api/v2/pokemon?limit=5"
    , expect = Http.expectJson FetchedPokemonNames decoder
    }


pokemonInfoDecoder : Decoder PokemonInfo
pokemonInfoDecoder =
  Json.Decode.map PokemonInfo
    (Json.Decode.field "name" Json.Decode.string)

decoder : Decoder (List PokemonInfo)    
decoder =
  Json.Decode.field "results" (Json.Decode.list pokemonInfoDecoder)
```
Publiquei [esta página no editor online _Ellie_](https://ellie-app.com/gkQ6dYyxrJGa1) para que possa ver este _webapp_ em funcionamento. Recomendo que tente alterar o código e veja o que acontece. É uma ótima forma de começar a experimentar a linguagem _Elm_.

## Analisando a implementação em _Elm_

Não irei neste artigo explicar todo este código e a arquitetura por trás da linguagem _Elm_. Mas queria destacar algumas partes importantes para o contexto da discussão deste artigo, começando pela definição dos nossos _types_.

### Definição de tipos

```elm
type alias PokemonInfo = { name : String }

type Model
  = Loading
  | Failure
  | Success (List PokemonInfo)
```

No código acima primeiro é definido um _type alias_, tornando mais claro para pessoa que está lendo o código o que é um _PokemonInfo_ (neste caso, uma estrutura com um campo chamado _name_ do tipo _String_). Isso também facilitará a vida do nosso compilador, permitindo que faça o tratamento de erro adequado quando necessário e, durante a fase de compilação, consiga emitir mensagens de erros mais informativas.

Em seguida, definimos um _type_ chamado _Model_ que será utilizado para representar o estado atual da nossa aplicação. Neste exemplo, nossa _webapp_ pode estar em um (e apenas um) dos 3 possíveis estados:
- _Loading_: estado inicial da aplicação, indicando que a requisição _http_ ainda está sendo processada.
- _Failure_: representa um estado de falha, indicando que ocorreu algum problema ao realizar a chamada _http_ ao servidor (podendo ser _timeout_, falha no _parsing_ da mensagem de retorno, etc).
- _Success_: indica que a requisição foi realizada e seu retorno convertido com sucesso.

Dos três estados definidos, apenas o _Success_ possui uma informação extra associada a ele: uma lista contendo elementos do tipo _PokemonInfo_. Note que isso não deixa espaço para ambiguidades. Se tivermos um estado de sucesso, **obrigatoriamente** temos uma lista de _PokemonInfo_ definida e com uma estrutura válida. E o contrário também: em caso de falha, a lista com os nomes dos Pokémons não estará definida.

### A construção da página

_Elm_ foi uma das pioneiras em utilizar o conceito de _DOM_ virtual e programação declarativa no desenvolvimento de _webapp_.

Na [arquitetura do _Elm_](https://guide.elm-lang.org/architecture), existe uma separação bastante clara entre o **estado** da nossa aplicação e o que deve ser exibido na tela. É responsabilidade da função _view_ montar, a partir do estado atual da nossa aplicação, uma representação da nossa _DOM_ virtual. E toda vez que o estado for alterado (quando, por exemplo, terminar de carregar os dados com nomes dos Pokémons) esta função será reavaliada e uma nova _DOM_ virtual criada. 

Em nosso exemplo, isso ocorre no seguinte trecho de código:
```elm
view : Model -> Html Msg
view model =
  case model of
    Failure ->
        text "Por alguma razão, não foi possível carregar a lista com nome dos Pokémons. 😧"

    Loading ->
      text "Carregando lista de nomes dos Pokémons, aguarde..."

    Success pokemonsInfo ->
      ul []
        (List.map viewPokemonInfo pokemonsInfo) 


viewPokemonInfo : PokemonInfo -> Html Msg
viewPokemonInfo pokemonInfo =
  li [] [ text pokemonInfo.name ]
```
Temos aqui a declaração de 2 funções: a _view_ e uma função auxiliar chamada _viewPokemonInfo_. 

Uma vantagem de utilizar _types_ para representação do estado da nossa aplicação é que **sempre** que um trecho de código for utilizar este _type_, o compilador irá **obrigar** a pessoa desenvolvedora a tratar todos os possíveis estados. Neste caso: _Loading_, _Failure_ e _Success_. Se você remover o tratamento do _Loading_ da função _view_ do nosso exemplo, receberá uma mensagem de erro similar a esta ao tentar compilar a aplicação:
```
Line 70, Column 3
This `case` does not have branches for all possibilities:

70|>  case model of
71|>    Failure ->
72|>        text "Por alguma razão, não foi possível carregar a lista com nome dos Pokémons. 😧"
73|>
74|>    Success pokemonsInfo ->
75|>      ul []
76|>        (List.map viewPokemonInfo pokemonsInfo) 

Missing possibilities include:

    Loading

I would have to crash if I saw one of those. Add branches for them!

Hint: If you want to write the code for each branch later, use `Debug.todo` as a
placeholder. Read <https://elm-lang.org/0.19.1/missing-patterns> for more
guidance on this workflow.
```

Isso traz mais segurança para a pessoa desenvolvedora refatorar o código e incluir ou remover estados da aplicação, tendo a certeza que não vai deixar de tratar algum caso obscuro.

### Fazendo uma chamada _http_

O trecho de código abaixo é responsável por fazer a chamada _http_ de forma assíncrona e realizar o _parse_ do retorno, transformando-o em uma lista de _PokemonInfo_.

```elm
fetchPokemonNames : Cmd Msg
fetchPokemonNames =
  Http.get
    { url = "https://pokeapi.co/api/v2/pokemon?limit=5"
    , expect = Http.expectJson FetchedPokemonNames decoder
    }


pokemonInfoDecoder : Decoder PokemonInfo
pokemonInfoDecoder =
  Json.Decode.map PokemonInfo
    (Json.Decode.field "name" Json.Decode.string)


decoder : Decoder (List PokemonInfo)    
decoder =
  Json.Decode.field "results" (Json.Decode.list pokemonInfoDecoder)
```
Impossível negar que este código é maior do que uma chamada a uma função _fetch_. Mas note que este código, além de fazer a chamada de forma assíncrona, também valida e transforma o retorno em uma _List PokemonInfo_, eliminando a necessidade de qualquer validação por nossa parte.

No final da execução da chamada será emitida uma mensagem _FetchedPokemonNames_ junto com o resultado da operação: ou uma lista com nomes dos Pokémons já decodificados ou então um resultado representando que ocorreu um erro.

Será responsabilidade da função _update_ receber esta mensagem e criar um novo estado para a aplicação.

```elm
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of

    FetchedPokemonNames result ->
      case result of
        Ok pokemonsInfo ->
          (Success pokemonsInfo, Cmd.none)

        Err _ ->
          (Failure, Cmd.none)
```

Mais uma vez, somos obrigados a tratar todos os possíveis cenários. Neste exemplo, são dois: 
- caso o _result_ seja do tipo _Ok_, significa que nossa requisição foi processada com sucesso. É retornado então um novo estado para nossa aplicação, alterando para _Success_, junto com a lista contendo os nomes dos Pokémons.
- caso o _result_ seja do tipo _Err_, então sabemos que ocorreu algum problema durante a requisição ou ao realizar o _parsing_ do _json_. Um novo estado da aplicação é retornado, alterando-o para _Failure_.

Sempre que o retorno da função _update_ for diferente do estado anterior, automaticamente a função _view_ será acionada novamente, então uma nova _DOM_ virtual será criada e eventuais alterações serão aplicadas na tela. Para entender melhor este processo, você pode ler sobre a _The Elm Architecture_ [nesta página](https://guide.elm-lang.org/architecture).

## Conclusões

Embora tenha focado exclusivamente nas requisições _http_ e no _JavaScript_, os mesmos conceitos são aplicados em muitos outros cenários, bibliotecas, _frameworks_ e linguagens.

Minha intenção não é desmotivar o uso de _JavaScript_. _[Elm](https://elm-lang.org)_ é uma linguagem maravilhosa, mas até hoje ainda uso _JavaScript_ e _TypeScript_ em alguns _webapps_ e este não é o ponto focal do problema. O que eu gostaria é que quando você for consumir uma função de sua linguagem preferida (seja uma função nativa, seja de uma bibliotecas de terceiros), que você sempre reflita e responda para si mesma: existe algum cenário que este código está ignorando? Ou, em outras palavras, esta é uma solução **simples** ou **simplista**?

E o mais importante: ao escrever uma nova função, utilize uma interface de comunicação que incentive a pessoa que for consumi-la a seguir as boas práticas. Mesmo que ela esteja seguindo o caminho do mínimo esforço, deve ser capaz de se precaver de todos os cenários possíveis. Ou, em outras palavras, sempre siga o [Princípio de menor espanto](https://en.wikipedia.org/wiki/Principle_of_least_astonishment).

---

Gostou deste texto? Conheça meus outros artigos, podcasts e vídeos acessando: https://segunda.tech.