Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Arquitetura evolutiva e design emergente: Aproveitando códigos reutilizáveis, Parte 2

Capturando padrões idiomáticos

Neal Ford, Application Architect, ThoughtWorks Inc.
Photo of Neal Ford
Neal Ford é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. Projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD, e é autor ou editor de livros que abordam uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

Resumo:  Depois de usar as técnicas descritas na parte anterior do artigo Arquitetura evolutiva e design emergente para descobrir designs emergentes no código, é necessário encontrar uma maneira de coletar e aproveitar esses elementos de design. Este artigo abrange duas técnicas para coletar padrões idiomáticos: capturando padrões como APIs e usando técnicas de metaprogramação.

Visualizar mais conteúdo nesta série

Data:  17/Mai/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  820 visualizações
Comentários:  


Diversas partes anteriores desta série focaram na primeira etapa óbvia em design emergente: descoberta de padrões idiomáticos. Depois de encontrar um padrão, o que fazer com ele? A resposta dessa pergunta é o foco dessa parte, a segunda de um artigo com diversas partes. A Parte 1 — , uma discussão sobre o relacionamento entre o código e o design, — abrange a base teórica da perspectiva segundo a qual o design no software realmente se refere ao código de origem completo da solução. Depois de mudar sua perspectiva para considerar todo o código como o design real, é possível começar a pensar sobre a consolidação dos elementos de design no nível da linguagem, ao invés de considerá-los somente no âmbito dos diagramas e de outros artefatos de auxílio ao design. Neste artigo, vou falar sobre o que fazer após descobrir o design reutilizável no código, abrangendo as técnicas de coleta desses padrões. Começarei coletando-os como APIs simples e, em seguida, ilustrarei a técnica de coleta que permite diferenciar esses elementos de outros códigos.

Sobre esta série

Esta série visa fornecer uma perspectiva atual sobre os conceitos sempre discutidos, mas difíceis de compreender, de arquitetura e design de software. Por meio de exemplos concretos, Neal Ford fornece uma base sólida nas práticas ágeis da arquitetura evolutiva e do design emergente. Ao adiar importantes decisões de arquitetura e design até o último instante, é possível evitar uma complexidade desnecessária com base em questionamentos de seus projetos de software.

Coletando padrões como APIs

A forma mais fácil de coletar padrões idiomáticos é extraí-los como suas próprias APIs ou estruturas. A maioria das estruturas de software livre usadas são conjuntos de padrões idiomáticos relacionados para resolução de um problema particular. Por exemplo: as estruturas da Web contêm todos os elementos de API necessários para desenvolver aplicativos da Web, coletados anteriormente de outros aplicativos da Web em execução. O Spring, por exemplo, é uma coleção de padrões idiomáticos técnicos para tratar injeção e desenvolvimento de dependências, e o Hibernate encapsula padrões para mapeamentos objeto-relacional (consulte a seção Recursos).

Certamente, é possível fazer a mesma coisa em seu código. Esta é certamente a técnica mais simples, pois somente a estrutura do código é alterada (normalmente isso ocorre através do suporte para refatoração no IDE escolhido). Diversos exemplos dessa técnica são exibidos na Parte 1, assim como no artigo "Linguagem, expressividade e design, Parte 2", que discute padrões de design.

Evitando duplicações estruturais

As APIs tendem, acidentalmente, a criar duplicações estruturais. O uso de APIs pode ser tumultuado, porque normalmente é necessário usar objetos de host para chamar a API. Considere o exemplo na Listagem 1 (que chama uma API relacionada a vagões ferroviários):


Listagem 1. Acessando uma APICar .

Car2 car = new CarImpl();
MarketingDescription desc = new MarketingDescriptionImpl();
desc.setType("Box");
desc.setSubType("Insulated");
desc.setAttribute("length", "50.5");
desc.setAttribute("ladder", "yes");
desc.setAttribute("lining type", "cork");
car.setDescription(desc);

A obrigatoriedade de digitar o objeto de host (desc) pelo usuário inclui ruídos desnecessários ao código. A maioria das APIs inclui objetos de host como o ponto de entrada da API, e é necessário utilizá-los outras vezes para acessar a API.

Existem algumas técnicas para diminuir esse problema nas APIs. Uma delas usa uma sintaxe Java pouco conhecida que permite o "transporte" do objeto de host através da definição de escopo de uma classe interna anônima, como mostrado na Listagem 2:


Listagem 2. Usando uma classe interna anônima para transportar o objeto de host

MarketingDescription desc = new MarketingDescriptionImpl() {{
    setType("Box");
    setSubType("Insulated");
    setAttribute("length", "50.5");
    setAttribute("ladder", "yes");
    setAttribute("lining type", "cork");

}};  

Para entender a Listagem 2, explicarei um pouco sobre como a linguagem Java trata a inicialização. Considere o código na Listagem 3:


Listagem 3. Inicializadores na linguagem Java

public class InitializerDemo {
    public InitializerDemo() {
        out.println("in constructor");
    }

    static {
        out.println("in static initializer");
    }

    {
        out.println("in instance initializer");
    }

    public static void main(String[] args) {
        out.println("in main() method");
        new InitializerDemo();
    }
}  

O exemplo na Listagem 3 ilustra quatro técnicas de inicialização diferentes na linguagem Java:

  • No método main() .
  • No construtor
  • Em um bloco inicializador static , que é executado quando a classe é carregada
  • Em um bloco inicializador, que é executado logo após o construtor

Essa ordem de execução é ilustrada na Figura 1:


Figura 1. Ordem de inicialização na linguagem Java

O inicializador static é executado primeiro, quando a classe é carregada, seguido pelo método main (que também é estático). Em seguida, a plataforma Java reúne todos os blocos do inicializador da instância e os executa antes do construtor, e por fim o construtor é executado. Os inicializadores da instância permitem a execução do código de desenvolvimento para uma classe interna anônima. Na verdade, esse é o único mecanismo de inicialização real, porque não é possível escrever um construtor para uma classe interna anônima — o construtor deve ter o mesmo nome da classe, mas a classe da classe interna anônima não possui um nome.

Ao usar o que é basicamente um simples truque de Java, evita-se utilizar novamente o nome do host da série de métodos que deseja executar. No entanto, isso gera uma sintaxe estranha que pode confundir seus colegas.

A desvantagem

A extração de padrões idiomáticos como APIs é uma técnica perfeitamente válida e, provavelmente, a forma mais comum de aproveitar gems reutilizáveis que você descobriu. A desvantagem dessa abordagem reside na sua normalidade: é difícil distinguir os elementos de design extraídos pois eles se parecem com seus outros códigos. Os seus sucessores no projeto terão dificuldade para entender que a API criada representa algo um pouco diferente do código ao seu redor, então o seu trabalho de detetive revelando o padrão pode não ser aproveitado. No entanto, se for possível destacar o padrão idiomático dos outros códigos, será fácil perceber que ele é, na verdade, uma coisa diferente.


Usando metaprogramação

A metaprogramação oferece uma excelente forma de diferenciar o código do padrão daquele de implementação, pois o padrão é expresso usando um código que é próximo . Uma boa técnica oferecida pela linguagem Java é o atributo. É possível definir atributos para criar tags de metaprogramação declarativas. Os atributos oferecem uma forma concisa para expressar conceitos. É possível reunir diversas funcionalidades em um espaço pequeno definindo-o como um atributo e decorando a parte pertinente da sua classe.

Aqui está um bom exemplo. A validação é um padrão idiomático técnico presente em muitos projetos, que serve muito bem para o código declarativo. Ao coletar padrões de validação como atributos, é possível sinalizar o código com restrições de validação claras que não interferem no objetivo principal do código. Considere o código na Listagem 4:


Listagem 4. Atributo MaxLength

public class Country {
	private List<Region> regions = new ArrayList<Region>();
	private String name;
	
	public Country(String name){
		this.name = name;
	}
	
	@MaxLength(length = 10)
	public String getName(){
		return name;
	}
	
	public void addRegion(Region region){
		regions.add(region);
	}
	
	public List<Region> getRegions(){
		return regions;
	}
}  

A capacidade de marcar elementos de código com atributos declara sua intenção de que algo externo opera no código seguinte. Isso, por sua vez, facilita a distinção entre a parte do padrão e a parte da implementação. O seu código de validação é destacado, pois ele não se parece com os códigos que estão ao redor. Esse particionamento de código por funcionalidade facilita a identificação, refatoração e manutenção de determinadas responsabilidades.

O validador MaxLength especifica que um nome de País não deve exceder 10 caracteres. A declaração do atributo é exibida na Listagem 5:


Listagem 5. Declaração de atributo MaxLength

@Retention(RetentionPolicy.RUNTIME)
public @interface MaxLength {
	int length() default 0;
}

A funcionalidade real do validador MaxLength reside em duas classes: uma classe abstrata chamada Validator e uma implementação concreta da classe chamada MaxLengthValidator. A classe Validator é exibida na Listagem 6:


Listagem 6. Classe abstrata Validator baseada em atributo

public abstract class Validator {

    public void validate(Object obj) throws ValidationException {
        Class clss = obj.getClass();
        for(Method method : clss.getMethods())
            if (method.isAnnotationPresent(getAnnotationType()))
                validateMethod(obj, method, method.getAnnotation(getAnnotationType()));
    }

    protected abstract Class getAnnotationType();
    protected abstract void validateMethod(
        Object obj, Method method, Annotation annotation);
}  

Essa classe é repetida em métodos em uma classe considerando getAnnotationType() para determinar se os métodos são decorados com um determinado atributo. Quando encontrado, o método validateMethod() é executado. A implementação da classe MaxLengthValidator é exibida na Listagem 7:


Listagem 7. A classe MaxLengthValidator

public class MaxLengthValidator extends Validator {

    protected void validateMethod(Object obj, Method method, Annotation annotation) {
        try {
            if (method.getName().startsWith("get")) {
                MaxLength length = (MaxLength)annotation;
                String value = (String)method.invoke(obj, new Object[0]);
                if ((value != null) && (length.length() < value.length())) {
                    String string = method.getName() + " is too long." + 
                        "Its length is " + value.length() + 
                        " but should be no longer than " + length.length();
                    throw new ValidationException(string);
                }
            }
        } catch (Exception e) {
            throw new ValidationException(e.getMessage());

        }
    }

    @Override
    protected Class getAnnotationType() {
        return MaxLength.class;
    }
}

Essa classe verifica se o método sujeito à validação potencial inicia com get e, em seguida, coleta os metadados da anotação, verificando por fim o valor do campo length do atributo em comparação ao comprimento declarado e emitindo um erro de validação, caso haja violação da regra.

Os atributos podem executar trabalhos muito sofisticados. Considere o exemplo na Listagem 8:


Listagem 8. Classe com validação de exclusividade

public class Region {
    private String name = "";
    private Country country = null;
    
    public Region(String name, Country country) {
        this.name = name;
        this.country = country;
        this.country.addRegion(this);
    }

    public void setName(String name){
        this.name = name;
    }
    
    @Unique(scope = Country.class)
    public String getName(){
        return this.name;
    }
    
    public Country getCountry(){
        return country;
    }
}

A declaração do atributo Unique , mostrado na Listagem 9, é muito simples:


Listagem 9. O atributo Unique

@Retention(RetentionPolicy.RUNTIME)
public @interface Unique {
	Class scope() default Unique.class;
}

A classe de implementação do atributo Unique estende a classe Validator abstrata mostrada na Listagem 6. O seu código é exibido na Listagem 10:


Listagem 10. Implementação do validador Unique

public class UniqueValidator extends Validator{

  @Override
  protected void validateMethod(Object obj, Method method, Annotation annotation) {
    Unique unique = (Unique) annotation;
    try {
      Method scopeMethod = obj.getClass().getMethod("get" + 
          unique.scope().getSimpleName());
      Object scopeObj = scopeMethod.invoke(obj, new Object[0]);
      
      Method collectionMethod = scopeObj.getClass().getMethod(
          "get" + obj.getClass().getSimpleName() + "s");
      List collection = (List)collectionMethod.invoke(scopeObj, new Object[0]);
      Object returnValue = method.invoke(obj, new Object[0]);
      for(Object otherObj: collection){
        Object otherReturnValue = otherObj.getClass().
            getMethod(method.getName()).invoke(otherObj, new Object[0]);
        if (!otherObj.equals(obj) && otherReturnValue.equals(returnValue))
          throw new ValidationException(method.getName() + " on " + 
            obj.getClass().getSimpleName() + " should be unique but is not since");
      }
    } catch (Exception e) {
      System.out.println(e.getMessage());
      throw new ValidationException(e.getMessage());
    }
  }

  @Override
  protected Class getAnnotationType() {
    return Unique.class;
  }
}  

Essa classe deve executar uma quantidade considerável de trabalho para garantir que o valor do nome do país seja exclusivo, mas é um bom exemplo de como os atributos são eficientes na programação Java.

Os atributos são uma inclusão bem-vinda na linguagem Java. Eles permitem a definição concisa de comportamentos com impacto abrangente e com muito pouco impacto sintático na classe de destino. No entanto, eles ainda são limitados em comparação com os tipos de coisas que podem ser feitos em linguagens mais expressivas da JVM, como a JRuby.

Atributos complexos usando JRuby

A linguagem Ruby também possui atributos (apesar deles não serem chamados de "atributos" — eles são uma das diversas técnicas de metaprogramação oferecidas pelo Ruby). Aqui está um exemplo. Considere a classe de teste na Listagem 11:


Listagem 11. Testando um cálculo complexo

class TestCalculator < Test::Unit::TestCase
  def test_complex_calculation
    assert_equal(4, Calculator.new.complex_calculation)
  end
end                          

Vamos considerar que o método complex_calculation é tão demorado que você deseja executá-lo somente ao executar testes de aceitação e não durante as execuções de testes de unidade. A Listagem 12 apresenta uma forma de limitá-lo:


Listagem 12. Limitando o escopo do teste

class TestCalculator < Test::Unit::TestCase

  if ENV['BUILD'] == 'ACCEPTANCE'
    def test_complex_calculation
       assert_equal(4, Calculator.new.complex_calculation)    
    end
  end
  
end
  

Esse é um padrão idiomático técnico relacionado ao teste cuja necessidade pode ser facilmente prevista em vários contextos. O agrupamento da declaração do método em um bloco if inclui uma complexidade indesejada ao código, pois dessa forma nem todas as declarações de método aparecem no mesmo nível de indentação. Ao invés disso, irei capturar o padrão usando um atributo, conforme mostrado na Listagem 13:


Listagem 13. Declarando um atributo em Ruby

class TestCalculator < Test::Unit::TestCase  
  extend TestDirectives 
  
  acceptance_only
  def test_complex_calculation
    assert_equal(4, Calculator.new.complex_calculation)        
  end
end  

Essa versão é muito mais simples e fácil de ler. A implementação, mostrada na Listagem 14, é trivial:


Listagem 14. Declaração de atributo

module TestDirectives
  def acceptance_only
    @acceptance_build = ENV['BUILD'] == 'ACCEPTANCE'
  end
  
  def method_added(method_name)
    remove_method(method_name) unless @acceptance_build
    @acceptance_build = false
  end
end            

É impressionante se pode fazer no Ruby com poucos códigos. A Listagem 14 declara um módulo, que é a versão de mesclagem do Ruby. A mesclagem contém uma funcionalidade que pode ser incluída em uma classe para adicionar a funcionalidade à classe. É possível pensar nisso como um tipo de interface, mas uma interface que pode incluir códigos. Este módulo define um método chamado acceptance_only, que verifica a variável de ambiente BUILD para identificar qual fase de teste está sendo executada. Depois de definir esse sinalizador, o módulo aproveita um método hook . Os métodos Hook no Ruby são executados no tempo de interpretação (e não no tempo de execução), e esse método hook em especial é executado todas as vezes que um novo método é incluído na classe. Quando esse método é executado, ele removerá o método recém-definido se o sinalizador acceptance_build estiver definido. Em seguida, ele define novamente o sinalizador como false. (De outra forma, esse atributo afetaria todas as declarações de método subsequentes, pois o sinalizador permaneceria como true.) Se quiser que ele afete um bloco de código que engloba diversos métodos, remova a reconfiguração do sinalizador, permitindo a manutenção do comportamento até sua alteração por outro atributo (como um atributo unit_test definido pelo usuário). (Esses são os chamados atributos complexos.)

Para ilustrar o poder desse mecanismo, a própria linguagem Ruby usa atributos complexos para declarar modificadores de escopo de classe private, protectede public . É isso mesmo, — as designações de escopo de classe no Ruby não são palavras-chave, são basicamente atributos complexos.


Conclusão

Nesta parte, demonstrei o uso de APIs e atributos como técnicas de coleta de padrões idiomáticos. Se for possível encontrar uma maneira de destacar os padrões coletados dos outros códigos, será mais fácil ler ambos os tipos de código, pois um não contaminará o outro.

Na próxima parte do artigo, continuarei mostrando como coletar padrões idiomáticos, com uma coleção de técnicas normalmente usadas para o desenvolvimento de linguagens específicas do domínio.


Recursos

Aprender

  • The Productive Programmer (Neal Ford, O'Reilly Media, 2008): O livro mais recente de Neal Ford expande vários tópicos desta série.

  • "Ruby off the rails" (Andrew Glover, developerWorks, dezembro de 2005): Conheça o Ruby a partir da perspectiva de um desenvolvedor Java.

  • Hibernate é uma estrutura popular de mapeamento objeto-relacional de software livre que engloba diversos padrões idiomáticos úteis.

  • Spring: A estrutura Spring é considerada uma das estruturas mais úteis de todo o domínio Java.

  • Navegue na livraria de tecnologia para obter livros sobre estes e outros tópicos técnicos.

  • Zona de tecnologia Java do developerWorks: Encontre centenas de artigos sobre cada aspecto da programação Java.

Discutir

Sobre o autor

Photo of Neal Ford

Neal Ford é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. Projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD, e é autor ou editor de livros que abordam uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=657879
ArticleTitle=Arquitetura evolutiva e design emergente: Aproveitando códigos reutilizáveis, Parte 2
publish-date=05172011
author1-email=nford@thoughtworks.com
author1-email-cc=

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).