[PT-BR] Evitando 'Escaping References' com JPMS
Tags: java, jvm, ptbr
Published: 2021-01-30 16:58:28

Muitas vezes, quando estamos desenvolvendo nossos softwares, não percebemos possíveis *bugs* que estão sendo inseridos no código. A questão é que, em alguns cenários, o comportamento da nossa aplicação pode ser altamente impactado. A ideia deste post é apresentar um desses problemas, chamado *escaping references*, e possíveis soluções.

#Show me the code!!

Antes de entrarmos na parte conceitual, vamos conhecer o código com que iremos trabalhar. O projeto possui

* 1 classe chamada `Livro`, que é a representação de um livro 

```java
package br.com.er.model;
public class Livro {

    public Livro(Long id, String nome, String autor, Double preco) {
        this.id = id;
        this.nome = nome;
        this.autor = autor;
        this.preco = preco;
    }

    private final Long id;
    private final String nome;
    private final String autor;
    private Double preco;

    public String getAutor() { return autor; }

    public void setPreco(Double preco) { this.preco = preco; }

    @Override
    public String toString() {
        return "Livro{" +
                "id=" + id +
                ", nome='" + nome + '\'' +
                ", autor='" + autor + '\'' +
                ", preco=" + preco +
                '}';
    }
}
```

* 1 classe chamada `ColecaoDeLivros` responsável por fornecer uma lista de livros e alguns comportamentos para manuseá-la. 

```java
package br.com.er.collection;
//imports omitidos
public class ColecaoDeLivros {

    public List<Livro> livros;

    public ColecaoDeLivros() {
        livros = List.of(
                new Livro(1L, "Clean Code", "Uncle Bob", 119.99)
        );
    }

    public void mostrarLivros() {
        livros.forEach(System.out::println);
    }

    public Livro buscarPorAutor(String autor) {
        return livros.stream().
                filter(livro -> livro.getAutor().contains(autor))
                .findAny()
                .orElseThrow(RuntimeException::new);
    }
}
``` 

* 1 classe `Principal` responsável por executar o código.

```java
package br.com.er;
import br.com.er.collection.ColecaoDeLivros;
public class Principal {

    public static void main(String... x) {

        ColecaoDeLivros cl = new ColecaoDeLivros();

        cl.mostrarLivros();
        System.out.println(cl.buscarPorAutor("Uncle Bob"));

    }
}
``` 

# Executando o projeto

O código acima está 100% funcional e podemos garantir isso executando a nossa classe `Principal`. 

*Intellij IDE usada para executar o código*

```
/Library/Java/JavaVirtualMachines/jdk-11.0.9.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=50174:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/jv.martins/Documents/workspace/avoid-escaping-reference/out/production/avoid-escaping-reference br.com.er.Principal
Livro{id=1, nome='Clean Code', autor='Uncle Bob', preco=119.99}
Livro{id=1, nome='Clean Code', autor='Uncle Bob', preco=119.99}

Process finished with exit code 0
```

Vamos fazer uma pequena modificação na classe `Principal`.   

```java
//resto do código omitido
public static void main(String... x) {

        ColecaoDeLivros cl = new ColecaoDeLivros();

        cl.mostrarLivros();
        cl.buscarPorAutor("Uncle Bob").setPreco(0.0);
        cl.mostrarLivros();
    }
```

Como podemos observar, o método `setPreco` da classe `Livro` é chamado após o método `buscarPorAutor` da classe `ColecaoDeLivros`. Isso está ocorrendo porque o retorno de `buscarPorAutor` é um `Livro`. Pelas boas práticas de desenvolvimento, existem duas opções:

1. Tornarmos impossível chamar o método `setPreco`.

2. Se for possível chamar o método `setPreco`, a chamada não deve surtir efeito, ou seja, o preço do livro retornado não deve ser alterado. 

Executando novamente o projeto o resultado será: 

```
/Library/Java/JavaVirtualMachines/jdk-11.0.9.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=50116:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/jv.martins/Documents/workspace/avoid-escaping-reference/out/production/avoid-escaping-reference br.com.er.Principal
Livro{id=1, nome='Clean Code', autor='Uncle Bob', preco=119.99}
Livro{id=1, nome='Clean Code', autor='Uncle Bob', preco=0.0}

Process finished with exit code 0
```

Como não deveria ser possível alterar o preço do objeto retornado, observamos que nossa aplicação não está se comportando como deveria. Este é um exemplo de *escaping reference*. Estamos mudando o atributo de um tipo, através do retorno do método de um outro tipo. Para estar de acordo com as boas práticas, vamos alterar nosso código para que não seja possível chamar o método `setPreco`. 

# Usando Interfaces 
 
A primeira opção para evitar o problema citado anteriormente, é criando uma interface de `Livro` que possua apenas os métodos *getters*

```java
package br.com.er.interfaces;

public interface ILivro {
    String getAutor();
} 
```

Agora trocaremos o retorno do método `buscarPorAutor` pela interface criada.

```java
//classe ColecaoDeLivros

public ILivro buscarPorAutor(String autor) {
        return livros.stream().
                filter(livro -> livro.getAutor().contains(autor))
                .findAny()
                .orElseThrow(RuntimeException::new);
    }
```  

Essa ação já será o suficiente para o código não compilar.

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

Resolvemos a situação e agora não é possível chamar o método `setPreco`. A afirmativa anterior seria totalmente verdadeira se não houvesse uma maneira de fazer o *casting* de `ILivro` para `Livro`.

```java
public static void main(String... x) {

        ColecaoDeLivros cl = new ColecaoDeLivros();

        cl.mostrarLivros();
        ILivro livro = cl.buscarPorAutor("Uncle Bob");
        Livro livroConvertido = (Livro) livro;
        livroConvertido.setPreco(0.0);
        cl.mostrarLivros();
    }
```

Podemos observar que o caminho para chamar o método `setLivro` ficou mais difícil, mas não impossível. O código acima devolve o mesmo resultado de antes

```
/Library/Java/JavaVirtualMachines/jdk-11.0.9.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=49798:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/jv.martins/Documents/workspace/avoid-escaping-reference/out/production/avoid-escaping-reference br.com.er.Principal
Livro{id=1, nome='Clean Code', autor='Uncle Bob', preco=119.99}
Livro{id=1, nome='Clean Code', autor='Uncle Bob', preco=0.0}

Process finished with exit code 0
```

Mas então não temos uma maneira de evitar definitivamente esses *escaping references*? A resposta vocês verão na próxima seção

# Usando JPMS (Java Platform Module System)

O JPMS é uma *feature* do Java que teve como objetivo modularizar a JDK. Dentre os seus vários benefícios, um deles é fornecer um melhor encapsulamento dos nossos componentes. Nessa seção iremos utilizar esta *feature* para evitar o problema com *escaping references*.  

O primeiro passo é separar a aplicação em módulos e definir quais classes ficarão em cada módulo. Para o nosso exemplo, vamos definir o primeiro módulo com as classes `br.com.er.collection.ColecaoDeLivros`, `br.com.er.interfaces.ILivro` e `br.com.er.model.Livro`. Segue a estrutura do módulo:

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

Você deve ter reparado que existe um novo arquivo chamado `module-info.java`. Este arquivo é necessário para compilar a aplicação e nele é possível fazermos algumas definições. Podemos definir quais componentes queremos que sejam expostos para fora do módulo, fortalecendo o encapsulamento da aplicação e podemos definir quais módulos queremos usar dentro do módulo em que estamos trabalhando. Para entender melhor, vamos verificar o conteúdo do arquivo.

```
module avoid.escaping.reference {

    exports br.com.er.collection;
    exports br.com.er.interfaces;
}
```

A palavra reservada `module` serve para definição do módulo, que foi nomeado de `avoid.escaping.reference`. Os exports informam quais pacotes serão expostos para fora do módulo. Qualquer outro pacote fora esses não poderá ser utilizado fora de `avoid.escaping.reference`

O segundo módulo possuirá a classe `Principal` e será responsável pela execução do projeto.

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

O módulo de execução depende do módulo `avoid.escaping.reference` e esse dependência é definida no arquivo `module-info`.

```
module avoid.escaping.reference.engine {

    requires avoid.escaping.reference;
}

```

> É importante chamar atenção para um ponto. Quando utilizamos o `export`, nós estamos fazendo a exportação dos pacotes (pacote por pacote) que podem ser utilizados fora do módulo. Quando utilizamos o `requires`, nós estamos requerendo o módulo como um todo e teremos acesso a todos os recursos que foram exportados do outro módulo.

Um último ponto para aplicação funcionar, é adicionar no build path do módulo `avoid.escaping.reference.engine`, o módulo `avoid.escaping.reference`.

Com todas as configurações realizadas, vamos tentar executar o código da classe `Principal`, com o *casting* realizado na seção anterior

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

O código apresenta erro de compilação, porque não exportamos a classe `Livro` do módulo `avoid.escaping.reference`, então para o módulo de execução (`avoid.escaping.reference.engine`) essa classe nem existe. Com essa abordagem, tornamos impossível a chamada do método `setPreco` e alcançamos o objetivo final. 

# Concluindo

Percebemos que os problemas citados no post não são sempre fáceis de identificar e o cenário piora porque nem toda solução evita 100% o problema. O uso de interfaces, em alguns cenários, funciona perfeitamente, pois o que fizemos para forçar o casting, não é realidade em certos projetos. Já para outros projetos, o JPMS é a melhor opção, porque garante o encapsulamento de recursos que não devem ser utilizados fora do módulo. A escolha vai depender da necessidade e do projeto. Qualquer dúvida, criticas e sugestões, estou à disposição. Até a próxima!!

Links para o código
- https://github.com/joao0212/avoid-escaping-references
- https://github.com/joao0212/avoid-escaping-references-engine