Resgatando o blog e trocando um pouco o tema para falar sobre programação.
Existem muitos tutoriais sobre orientação a objetos e desenvolvimento orientado por testes (TDD) pela internet. Grande parte se utiliza de um exemplo simples: uma calculadora.
Para desenvolver a calculadora, demonstram o poder do polimorfismo construindo abstrações sobre operações binárias e a utilidade do TDD através de uma programação incremental.
Neste post tentarei extender este exemplo de calculadora, porém focando em outro ponto do problema: a interação com o usuário.
Os exemplos estão na linguagem orientada a objetos mais utilizada do planeta: Java.
- Programa: Calculadora para terminal.
- Requisito: Ler cálculos no formato "2 + 2" e informar o usuário sobre o resultado do cálculo: "4".
- Problema: Como testar a interação com o usuário?
Por ser um programa feito para rodar em terminal, utilizaremos System.in e System.out para interagir com o usuário. Porém, se amarrarmos o algoritmo de cálculo diretamente à estes recursos, ficará muito difícil e não-produtivo criar e manter testes para este sistema.
Algo importante que aprendi com a linguagem Haskell é a de separar o código em áreas puras e impuras. Uma área de código pura é um conjunto de funções que dependem apenas de seus argumentos e só produzem um resultado, i.e. funções que não alteram nenhuma informação e dependem apenas de seus argumentos. A área de código impura é aquela composta pelas demais funções que dependem de I/O, alterações na memória, banco de dados, etc.
A linguagem Haskell faz um trabalho incrível para assegurar que esta distinção seja mantida pelos programadores através do seu sistema de tipos que diferenciam funções puras e funções impuras. Nas linguagens tradicionais, como Java, não existe esta distinção: todo pedaço de código tem a liberdade de ser impuro. Desta forma, os programadores devem sempre prestar atenção para o que cada parte do código está executando.
Para separar a parte "pura" e "impura" da nossa calculadora, precisamos primeiro identificar quais funções pertencem a cada parte.
Toda a parte de código que realiza o cálculo pode se encaixar perfeitamente na área de código pura, afinal são apenas representações para funções matemáticas, que são puras por definição.
Por outro lado, a parte que faz a interação com o usuário é impura, pois altera informações que vão além de seu alcance em termos de código: imprime caracteres no terminal, e recebe informações que vão além de seus argumentos: teclas pressionadas pelo usuário.
A parte impura do código é muito difícil de ser testada e por isto deve ser mantida reduzida e de forma controlada, que faça apenas o necessário para o funcionamento. No caso da calculadora para terminal, a parte impura é apenas interação com o usuário no seguinte formato:
- Usuário digita um texto.
- Programa responde com outro texto.
- Volte para 1.
Esta interação é abstrata o suficiente para ser totalmente separada da parte que realiza o processamento. Por exemplo, o seguinte código encapsularia toda a parte impura da nossa calculadora:
public interface Programa { String processar(String entrada); } public class InteracaoTerminal { public void interagir(Programa programa) { Scanner s = new Scanner(System.in); while (true) { String entrada = s.nextLine(); String saida = programa.processar(entrada); System.out.println(saida); } } }
Desta forma a interface Programa é 100% testavel, por exemplo:
assertEquals("4", programa.processar("2 + 2")); assertEquals("6", programa.processar("2 + 2 * 2"));
Além de testavel, a implementação da nossa calculadora apenas manipulará Strings, sem dependência direta com o System. Isto significa que poderemos aproveitar o código em outros lugares, quem sabe utilizar o mesmo código para duas interfaces diferentes: uma interage com o usuário através de um terminal e outra através de um aplicativo gráfico.
A abstração colocada como exemplo tem uma limitação: cada interação com o usuário é feita através de uma linha de entrada e uma linha de saída. Deixo como exercício aprimorar esta abstração para que seja possível ter mais de uma linha de entrada e mais de uma linha como saída de uma única interação. Se você conhece Haskell, poderá se basear na função interact, e se você não conhece: Aprender Haskell será um grande bem para você!
Nenhum comentário:
Postar um comentário