Arquivo

Arquivo da Categoria ‘Arquitetura de Software’

Usando AspectJ para injetar comportamento em um Enum

29, abril, 2010

Nota: Isso pode não ser viável na maioria dos casos, mas acho interessante pelo fator curiosidade.

Já me deparei algumas vezes em uma situação onde eu tinha uma série de enums que implementavam uma interface. Enums em Java (ainda) não podem herdar métodos, o que faz com que os métodos devam ser implementados em todos os enums, mesmo que o que a gente queira seja a mesma implementação pra todos os casos.

Um exemplo disso é o seguinte: para internacionalizar uma aplicação, por motivos que não vem ao caso, decidimos encapsular as mensagens em enums. Ou seja, teríamos uma interface Message, que todos os enums vão implementar, e cada contexto da aplicação teria seu próprio enum. Declaramos dois métodos em uma mensagem: um get(), que retorna a mensagem diretamente; um get(Object… args), que usa um MessageFormat para colocar os valores dos argumentos dentro da mensagem.

public interface Message {
    String get();
    String get(Object... args);
}

O método para recuperar as strings é o mesmo para todos os enums. Um ResourceBundle (que contém as mensagens carregadas de um arquivo properties) é acessado, e o valor correspondente ao valor do enum é retornado. Um enum típico seria assim:

public enum PersistenceMessage implements Message {
    OPEN("persistence.open"),
    SAVE("persistence.save");

    private String key;

    private PersistenceMessage(String key) {
        this.key = key;
    }

    @Override
    public String get() {
        return Messages.get(key);
    }

    @Override
    public String get(Object... args) {
        return Messages.get(key, args);
    }
}

O problema aqui é que os métodos em todos os enums vão estar duplicados.

Como seria bom se existisse um método de injetar comportamento nestes enums… talvez como os mixins do Ruby

Acontece que em Java existe um jeito de fazer algo parecido com os mixins de Ruby. Com técnicas de AOP (aspect oriented programming), mas especificamente com ajuda do framework AspectJ, é possível injentar comportamento em classes com as chamados inter-type declarations.

Eu não vou explicar aqui o que é AOP ou como funciona o AspectJ, mas abaixo vai o exemplo de como fazer isso.

O primeiro passo é criar um aspecto para a interface Message, fornecendo os métodos desejados.

public aspect MessageGetter {

    public String Message.get() {
        return Messages.getString(this.getKey());
    }

    public String Message.get(Object... args) {
        return Messages.getString(this.getKey(), args);
    }
}

Como vocês devem ter percebido, eu adicionei na interface de Message o método getKey(), para ter um ponto de acesso à chave. Eu nunca usei AspectJ na prática, então talvez exista uma forma mais elegante de fazer isso. Essa foi a única alteração que fiz na interface, então não vou mostrá-la novamente.

Cada enum agora segue o seguinte padrão:

public enum PersistenceMessage implements Message  {
    OPEN("persistence.open"),
    SAVE("persistence.save");

    private String key;

    private PersistenceMessage(String key) {
        this.key = key;
    }

    @Override
    public String getKey() {
        return key;
    }
}

O método getKey() passou a ser duplicado, mas acredito que seja muito menos grave do que a situação anterior.

Este foi um exemplo extremamente simples, e com certeza não é uma opção para todos depender do AspectJ só para esse comportamento em um projeto, mas acredito que só o fato de ter o conceito de mixins na cabeça seja útil para abrir alguns horizontes na hora de bolar a arquitetura de uma aplicação.

Renato Besen Arquitetura de Software

Heurísticas de Usabilidade aplicadas ao Código

24, março, 2010

Nos dias 12 e 13 de Março a OnCast promoveu um curso de teste de usabilidade, com o Ezequiel Blasco. O que mais me chamou a atenção no curso foram algumas heurísticas para avaliar a usabilidade de um sistema.

Como não trabalho diretamente com design de interfaces, resolvi refletir e aplicar as heurísticas no que interessa para um desenvolvedor de software.

  1. Visibilidade do estado do sistema - quando lemos um código fonte, é importante que saibamos o que está acontecendo. Alguns exemplos de coisas que podem ferir esta heurística: métodos onde o nível da abstração é muito variado (e.g. manipulação de uma string e acesso ao servidor no mesmo método); magia negra, ou seja, pequenos truques que parecem funcionar mas ninguém sabe o porquê;  muitas suposições, o programador pode considerar certas coisas implicitamente, mas isso pode não estar claro para todos; variáveis/classes/pacotes com nomes não auto-explicativos, e etc.
    Em resumo, eu considero sempre o bom conselho da PEP 20 que diz “Explicit is better than implicit“. Se alguma coisa prejudica a visibilidade do sistema, ela deve ser bem documentada e justificada. Se muitas dessas coisas acontecem em um mesmo software, está na hora de considerar uma nova arquitetura para ele.
  2. Simetria entre sistema e mundo real - este ponto se refere as metáforas e modelagem utilizadas em um software. Se espera que um software reflita pelo menos superficialmente o domínio, o que dá origem a técnicas como o DDD. Considere por exemplo um software de gerenciamento de projetos Scrum onde não se tem um conceito de Sprint. Alguma coisa deve estar errada, não?
  3. Controle e liberdade do usuário - Se uma classe é muito fechada, a reutilização dela é prejudicada. Obviamente é importante ter um balanço entre nenhuma configuração e muita configuração. É interessante fazer com que o sistema tenha um fallback para uma configuração padrão, mas devemos tomar cuidado para não limitar demais as coisas, pois isso gera duplicação de código.
  4. Consistência e padrões - Se uns métodos tem nomes em cammelCase e outros separados com underscore, nunca se sabe o que esperar do sistema. Não ter um padrão de código e arquitetura induz o programador a erros, já que o nosso cérebro é basicamente uma máquina de identificar padrões.
  5. Prevenção de erros - existem aqui duas possibilidades. Primeiro, se o erro pode tratado, faça, mas não esqueça da heurística 1: o erro deve ser comunicado, bem como a sua solução. Caso não seja possível tratar, quebre cedo. Não existe nada mais frustrante do que entrar no fluxo de trabalho, e descobrir que ele não vai poder ser concluído por que ocorreu um erro aconteceu lá no início.
  6. Reconhecimento ao invés de lembrança - esta heurística se refere a intuitividade do código. Um bom código parece natural de ser utilizado, nada é forçado, e você é capaz de utilizar mesmo se ficar 3 meses sem encostar nele. Se toda vez que você precisa usar algo (uma biblioteca, um framework, etc.) é necessário ler o tutorial/ajuda, bem… é um mau sinal.
  7. Flexibilidade e eficiência de uso - para exemplificar esta heurística, considere o framework Ruby on Rails. Ele é extremamente eficiente de se usar, pois otimiza a codificação para os casos mais comuns. Digamos que 80% das pessoas vai utilizar o sistema de template X, com o banco de dados Y e o framework de teste Z. Nesse cenário, é muito mais prático ter estes três componentes pré-configurados, e o 20% que precisar utilizar algo diferente mergulha mais fundo no sistema, e faz as alterações necessárias. Assim, além de eficiente, o framework é flexível.
  8. Estética e design minimalista - esta heurística vai ao encontro da filosofia Unix de desenvolvimento, que diz: “faça apenas uma coisa, e faça bem”. Outro conceito que descreve a heurística é o “Single Responsibility Principle“.
  9. Recuperação de erros - o programador deve ser capaz de identificar qual o erro e ter informações para poder tratar o mesmo. Sair do programa com uma mensagem “exit with code -1” não é muito útil para ninguém.
  10. Ajuda  e documentação - este talvez seja um ponto polêmico, mas depende muito de situação para situação. Muitos consideram que documentação de código é igual a código duplicado. Bob Martin no livro Clean Code sugere que devemos evitar o mínimo de mistura de linguagens em um arquivo (e.g. um arquivo com Java, Html, JavaScript e Inglês é terrível de manter, pois temos que mudar a sintaxe em que o cérebro está trabalhando em um curto espaço de tempo). Por outro lado, se estamos criando uma biblioteca que vai ser usada por terceiros, é importante ter a documentação dos métodos com exemplos de uso, edge cases, etc. Só não podemos esquecer que a documentação possui um custo para ser mantida, assim como o código.

Lembrando que essas são apenas as minhas reflexões sobre o assunto, e assim como no design de interfaces são apenas heurísticas, ou seja, não necessariamente um código precisa atender a todas elas para ser bom. A medida dessas heurísticas é fuzzy, e temos que encontrar um balanço para entregar o software funcionando, que afinal é o objetivo primordial de todos os desenvolvedores.

Eu gostaria muito de conhecer a ótica de outras pessoas sobre os pontos aqui tratados, portanto sintam-se livras para colocar suas opiniões nos comentários.

Renato Besen Arquitetura de Software

Sobre DTOs e programação estruturada

7, janeiro, 2010

Com a minha própria autorização, replico aqui um post do meu blog:

Os DTOs (Data Transfer Object) tem como principal uso a transferência de dados entre diferentes camadas de uma aplicação. A principal característica desse tipo de objeto é falta de comportamento.

O problema: DTOs as vezes são usados além do seu propósito inicial, o que leva o desenvolvedor à programação estruturada. Considere por exemplo os seguintes exemplos, primeiro em Java:

public class User {
  private String login;
  private String passwortHash;

  [getters e setters]
}

public class UserUtil {
  public static boolean checkLogin(User user) {
    ...
  }
} 

Agora em C:

struct User {
  char* login;
  char* passwordHash;
};

int checkLogin(User user) {
...
} 

Nota: não compilei os códigos, mas são suficientes para objetivos didáticos.

Perceberam a semelhança? Apesar da keyword “class” estar presente na versão Java, isso NÃO é orientação a objetos.

DTOs têm usos justificados, porém muitas vezes acabam se espalhando pela aplicação. A consequência dessa difusão é que o comportamento que utiliza os dados de um DTO tem dois “destinos” principais:

  • Uma “classe” com vários métodos estáticos;
  • no pior caso, o comportamento é duplicado em cada lugar que é usado.

A duplicação do comportamento pode ser bem problemática. Muitas vezes um mesmo comportamento está replicado com pequenas variações (e.g. uma comparação de string case-sensitive e outra case-insensitive). Esse acoplamento com o DTO dentro da aplicação também dificulta a mudança do mesmo, pois dependendo da escala da aplicação os pontos de mudança são muitos, e como não há uma centralização dos comportamentos, refactorings automatizados estão fora de questão.

Outro problema que ocorre nos dois casos é que esse estilo de programação dificulta a criação de testes da aplicação. Ao invés de criar um mock do objeto (estendendo o objeto/interface ou com algum framework), é necessário criar um objeto real com os dados necessários para o comportamento desejado. Com isso, o teste que deveria ter um ponto da aplicação sendo testado possui uma dependência com o comportamento associado ao DTO (ou seja, os testes falham se o “comportamento do DTO” for alterado).

Muitas vezes os DTOs são uma dependência externa do projeto (e.g. equipes distribuídas, cada uma trabalhando em um módulo da aplicação). Neste caso, se não for possível colocar o comportamento junto com os dados, é interessante ter uma classe que encapsule o DTO, fornecendo os comportamentos do mesmo. Desta forma evita-se que toda a aplicação conheça o funcionamento do DTO (flags ou outros atributos internos), fornece um ponto centralizado com o comportamento e melhora muito a legibilidade do código, pois os dados estão juntos com o comportamento associado.

Renato Besen Arquitetura de Software

Testes parametrizados com JUnit

9, dezembro, 2009

Muitas vezes temos a necessidade de executar um mesmo teste repetidas vezes mudando apenas alguns parâmetros. A solução mais simples caso sejam poucos testes é replicar o método de testes e mudar as variáveis, ou então criar um método com as asserções desejadas e chamá-lo com os diferentes parâmetros.

Ambas as possibilidades apresentadas pecam, pois não respeitam o princípio DRY. Uma solução muito mais elegante é usar os testes parametrizados, uma possibilidade que para quem usa JUnit existe a partir da versão 4.

O funcionamento é simples: um método no teste, anotado com Parameters, deve retornar uma coleção de arrays, que serão os parâmetros de execução:

@Parameters
public static Collection<Object[]> parameters() {
    List<Object[]> list = new ArrayList<Object[]>();
    list.add(new Object[] {"asshole!", "jerk!"});
    list.add(new Object[] {"what the hell?", "what the heck?"});
    return list;
}

Cada array desta coleção fornecerá os argumentos para o construtor do teste, possibilitando assim que estes valores sejam salvos em campos da classe, e utilizados por quem precisar dos mesmos:

public ProfanityFilterTest(String input, String output) {
    this.input = input;
    this.output = output;
    filter = new ProfanityFilter();
}

A classe de testes deve ser configurada para rodar com um runner específico, o Parameterized:

@RunWith(Parameterized.class)
public class ProfanityFilterTest {
[...]

Com estes passos básicos, ao rodar a classe testes, cada teste será chamado uma vez com os parâmetros fornecidos.

Resultado dos Testes Parametrizados

Para conferir o código completo baixe ProfanityFilter.java e ProfanityFilterTest.java.

Renato Besen Arquitetura de Software, Auto-gestão

Refactoring: Substituindo Condicional por Polimorfismo usando enum!

23, novembro, 2009

Esse refactoring é muito interessante quando feito sobre condicionais que lidam com parâmetros  que podem mudar com freqüência. Esses parâmetros podem ser tipos de relatórios, comandos utilizados para invocar métodos remotamente, estados de uma máquina entre outros.

O objetivo da utilização do polimorfismo é justamente dar flexibilidade e permitir que o sistema evolua (mude) sem precisar adicionar novos blocos de else if no código toda vez que um novo parâmetro for adicionado, alterado ou removido! Sendo assim, não precisa sair alterando todos os blocos if else do sistema, apenas os pontos mais suscetíveis a mudança e que a flexibilidade fará com que as operações de manutenção possam ser mais agradáveis.

Nosso foco são estruturas que dependem de parâmetros para executar ações como por exemplo gerar um extrato. Os extratos podem ser de vários tipos: Semanais, quinzenais, mensais, completos e tantos outros quanto os clientes necessitarem ao longo da vida útil do sistema.

Abaixo, a classe responsável por gerar um extrato utilizando um tipo passado como parâmetro:

12

Estruturas como essa além de serem procedurais, diminuem a performance do sistema e com o tempo aumentam os custos de manutenção do projeto, contribuindo para um código ilegível (imagine com 50 tipos de extrato) e desmotivante.

Uma forma interessante de resolver o problema é mapear os estados em uma enum e utilizá-la para mapear os estados polimorficamente:

3

E finalmente podemos refatorar o método condicional da classe que gera extratos para delegar a geração de extratos para as subclasses:

4

Lembrando que essa é uma forma simplificada da utilização do padrão Strategy, no entanto evitamos a criação excessiva de artefatos (cada estratégia numa subclassse) e utilizamos a enum para fazer esse trabalho.

Rodrigo Branas Arquitetura de Software