Anotações Capítulo 8: Boundaries
Tags: books, codequality
Published: 2021-07-12 17:03:45

- Sempre precisamos utilizar módulos, códigos fornecidos por terceiros.
- E devemos integrar de forma limpa esse código externo ao nosso.


## O uso de códigos de terceiros

- Há uma tensão natural entre o fornecedor de uma interface e seu usuário.

- Os fornecedores de pacotes (aqui no sentido de módulos) e frameworks de outros fabricantes visam a uma maior aplicabilidade de modo que possam trabalhar com diversos ambientes e atender a um público maior.

- Já os usuários desejam uma interface voltada para suas próprias necessidades.

- **Essa tensão pode causar problemas nos limites de nossos sistemas**.

- Exemplo: `java.util.Map`, é uma interface bastante ampla com diversas capacidades. O que nos dá flexibilidade, isso é útil, mas também pode ser uma desvantagem:

 - Nosso aplicativo pode construir um `Map` e passá-lo adiante.
 
 - E nosso objetivo talvez seja que nenhum dos recipientes de nosso `Map` não exclua nada do `Map`. Mas logo no início da lista dos métodos do `Map` está o método `clear()`. E assim qualquer usuário do `Map` pode apagá-lo.

 - Ou talvez, segundo a convenção que adotamos, o `Map` pode armazenar apenas certos tipos de objetos, mas ele não restringe os tipos que podemos armazenar, assim qualquer usuário pode adicionar itens de qualquer tipo.

 - Exemplo `sensors`:
Se temos um `Map` de `sensors` da seguinte forma:
```
Map sensors = new HashMap();
```
E quando alguma parte do código precisar acessar o Sensor, terá que fazer:
```
Sensor s = (Sensor) sensors.get(sensorId);
```
E isso aparece várias vezes ao longo do código. E com isso o cliente fica com a responsabilidade de obter um `Object` do `Map` e atribuí-lo ao tipo certo. **Apesar de funcionar, não é um código limpo**. E esse código não explica muito bem o que ele faz. 
E poderíamos melhorar a legibilidade com o uso de tipos genéricos, como:
```
Map<Sensor> sensors = new HashMap<Sensor>();
…
Sensor s = sensors.get(sensorId );
```
Observação: O `Map` deveria ter a chave e o tipo do valor.

Porém, isso ainda não resolve o problema de que o `Map<Sensor>` oferece mais capacidade do que precisamos ou queremos.
**Passar adiante pelo sistema uma instância de `Map<Sensor>` significa que haverá vários lugares para mexer se a interface `Map` mudar.** 

Uma forma limpa de usar o `Map`:
```
public class Sensors {
    private Map sensors = new HashMap();

    public Sensor getById(String id) {
        return (Sensor) sensors.get(id);
    }
    
    //snip
}
```
Dessa forma a interface no limite (Map) está oculta, e é possível alterá-la causando muito pouco impacto no resto do aplicativo. Essa classe também pode forçar regras de modelo e de negócios.

- Não estamos sugerindo para sempre usar dessa forma.

- Mas é bom **não passar os `Maps` ou qualquer outra interface num limite** por todo o sistema.

- Se for usar, mantenha numa classe ou próxima a uma família de classes em que ela possa ser usada. **Evite retorná-la ou aceitá-la como parâmetro de APIs públicas**.

## Explorando e aprendendo sobre limites
- Códigos de terceiros nos ajudam a obter mais funcionalidade em menos tempo.

- Não é tarefa nossa testá-los, mas pode ser melhor para nós criar testes para os códigos externos que formos usar.

- Isso quando não é claro como usar uma biblioteca de terceiros. Podemos gastar um tempo lendo a documentação e decidindo como usá-la. E então escreveremos nosso código para usar o código externo e vemos que não é o que achávamos. E poderíamos passar muito tempo depurando o código e tentando saber se o bug é no nosso código ou no deles.

- **Entender códigos de terceiros é difícil. Integrá-lo ao seu também é**. Fazer ambos ao mesmo tempo dobra a dificuldade, e se adotássemos outra abordagem?

- Se criarmos testes para explorar nosso conhecimento sobre ele. O que o Jim Newkirk chama isso de *testes de aprendizagem*.
- Nesses testes, chamamos a API do código externo como faríamos ao usá-la em nosso aplicativo. Assim estaríamos controlando os experimentos que verificam nosso conhecimento daquela API. 

- O teste foca no que desejamos saber sobre a API.


## Aprendendo sobre log4j

Para usar o pacote `log4j` do Apache, baixaremos o pacote e abriremos a documentação inicial.

- Sem ler muito, criamos nosso primeiro teste:
```
@Test
public void testLogCreate() {
    Logger logger = Logger.getLogger("MyLogger");
    logger.info("hello");
}
```

- Quando executamos, temos um erro dizendo que precisamos de algo chamado `Appender`. Após ler um pouco mais, descobrimos o `ConsoleAppender`. Então criamos o `ConsoleAppender`:
```
@Test
public void testLogAddAppender() {
    Logger logger = Logger.getLogger("MyLogger");
    ConsoleAppender appender = new ConsoleAppender();
    logger.addAppender(appender);
    logger.info("hello");
}
```

- Agora descobrimos que o `Appender` não possui fluxo de saída. Depois de pesquisar tentamos o seguinte:
```
@Test
public void testLogAddAppender() {
    Logger logger = Logger.getLogger("MyLogger");
    logger.removeAllAppenders();
    logger.addAppender(new ConsoleAppender(
    new PatternLayout("%p %t %m%n"), ConsoleAppender.SYSTEM_OUT));
    logger.info("hello");
}
```

- E funcionou. Uma mensagem “hello” apareceu no console! Porém:
Parece estranho ter que dizer ao `ConsoleAppender` o que ele precisa escrever no console, e mais interessante é quando removemos o parâmetro `ConsoleAppender.SYSTEM_OUT` e ainda é exibido “hello”. Mas quando retiramos o `PatternLayout` temos a mensagem de falta de fluxo de saída.
Lendo a documentação novamente, vimos que o construtor `ConsoleAppender` padrão vem “desconfigurado”, o que não parece óbvio ou prático. Parece um bug. Lendo mais detalhadamente sobre o pacote chegamos a seguinte série de testes de unidade:
```
public class LogTest {
    private Logger logger;

    @Before
    public void initialize() {
        logger = Logger.getLogger("logger");
        logger.removeAllAppenders();
        Logger.getRootLogger().removeAllAppenders();
    }

    @Test
    public void basicLogger() {
        BasicConfigurator.configure();
        logger.info("basicLogger");
    }

    @Test
    public void addAppenderWithStream() {
        logger.addAppender(new ConsoleAppender(new PatternLayout("%p %t %m%n"), ConsoleAppender.SYSTEM_OUT));
        logger.info("addAppenderWithStream");
    }

    @Test
    public void addAppenderWithoutStream() {
        logger.addAppender(new ConsoleAppender(new PatternLayout("%p %t %m%n")));
        logger.info("addAppenderWithoutStream");
    }
}
```
Agora sabemos como obter um console simples e inicializado de logs, com isso podemos encapsular essa lógica nas classes de logs para que o resto do código fique isolado da interface limite do `log4j`.


## Os testes de aprendizagem são melhores que de graça

- Esses testes acabam não custando nada. Porque tivemos que aprender sobre a API mesmo, e escrever eles foi uma forma fácil de aprender.

- **“Os testes de aprendizagem são experimentos precisos que ajudam a aumentar nosso entendimento”**.

- **Esses testes também geram um retorno positivo. Quando houver novas versões daquele pacote, podemos executar os testes para ver se há diferenças nas atividades**.

- Cada distribuição vem com um novo risco. Porém com os testes de aprendizagem podemos saber se o pacote ficou incompatível com os testes.

- Também pode ser mais fácil a atualização com os testes, assim podemos ir para novas versões sem grandes problemas desde que os testes continuem passando.


## O uso de código que não existe ainda

- Outro tipo de limite, que separa o conhecido do desconhecido.
- Às vezes, o que está do outro lado nos é desconhecido. Às vezes, optamos por não olhar além do limite.
- Pense num caso onde você vai ter que utilizar uma API externa mas que você não conhece ela e não sabe como ela funciona:
 - Digamos que você usará um subsistema chamado “Transmissor”.
 - Nesse caso a melhor abordagem é criar nossa própria interface “Transmitter” que será responsável por se comunicar com o subsistema "Transmissor".
 - Nela podemos criar um método “transmit” que pega a frequência e o fluxo de dados. Nesse caso, essa é a interface que gostaríamos que o subsistema "Transmissor" tivesse, que não sabemos como realmente é a sua interface.
 - Assim deixamos o código do nosso lado organizado e centralizado.
 - E podemos até utilizar testes com um “Transmissor fake” para testarmos o funcionamento do nosso lado.
 - Bem como usar “Adapter Transmitter” para fazer a comunicação no futuro com a API externa.


## Limites limpos

- Coisas interessantes ocorrem nos limites. A alteração é uma delas.
- Bons projetos de software acomodam modificações sem muito investimento ou trabalho.
- Quando usamos códigos que estão fora de controle, devemos dar uma atenção ao nosso investimento e garantir que uma mudança futura não seja muito custosa.
- O código nos limites precisa de uma divisão clara e testes que definem o que se deve esperar.
Evitar que grande parte do nosso código enxergue as particularidades de terceiros.
- Melhor depender de algo que você controle do que pegar algo que acabe controlando você.
- Lidamos com os limites de códigos externos através de alguns poucos lugares em nosso código que fazem referência a eles.
- Podemos usar uma interface, ou um adapter para converter nossa interface perfeita na que nos for fornecida.
- Nosso código se comunica melhor conosco, provê uso consistente interno e possui poucos pontos para serem mexidos quando o código externo mudar.