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.
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 API
Car .
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 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.
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.
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.
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
- Participe da comunidade My developerWorks.

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.