O erro do criador do Ant

No livro Pragmatic Project Automation, publicado em 2004, há um trecho em que James Duncan Davidson (criador do Ant e do Tomcat) admite que foi um erro ter usado XML para descrever os passos do build do Ant.

Segue uma tradução livre:

O criador do Ant exorciza um de seus demônios
por James Duncan Davidson

A primeira versão do Ant não tinha esse monte de tags que você vê espalhadas pelos arquivos de build. Ao invés disso, era usado um arquivo de properties e a classe java.util.Properties para definir quais tasks deveriam ser executadas para um determinado target. Funcionou bem para pequenos projetos mas começou a entrar em colapso à medida que os projetos cresciam.

O motivo do colapso era a maneira que o Ant enxerga o mundo: um projeto é uma coleção de targets (alvos). Um target é uma coleção de tasks (tarefas). Cada task tem um conjunto de propriedades. É obviamente uma árvore hierárquica. Porém, arquivos .properties tem apenas mapeamentos chave=valor, nos quais essa estrututura de árvore não encaixa.

Eu queria um formato de arquivos hierárquico que capturasse a visão de mundo do Ant. Mas eu não queria criar meu próprio formato. Eu queria usar um formato padrão e, mais importante, eu não queria criar um parser. Eu queria reusar o trabalho dos outros. Eu queria o caminho mais fácil.

Na época, XML estava despontando no radar. A spec tinha sido finalizada, ainda que bem recentemente. SAX tinha se tornado um padrão de-facto, mas não tínhamos JAXP ainda. Eu estava convencido que XML era a next big thing depois do Java. Código portável e dados portáveis. Duas frases de efeito que ficam bem juntas.

E já que os dados no XML tem uma estrutura de árvore, parecia perfeito para o tipo de coisa que precisava ser expressado em um arquivo de build. Adicione o fato de que XML era um formato de texto editável manualmente, e parecia um casamento feito nos céus. E eu não precisava criar um parser. Trato feito.

Em retrospecto, XML provavelmente não foi a escolha correta como parecia. Eu tenho visto arquivos de build com centenas, e até milhares, de linhas e, nesses tamanhos, editar XML não é tão amigável como eu esperava. Além disso, quando você mistura XML e os mecanismos baseados em reflection do Ant que permitem estender e customizar tasks, você acaba com um ambiente que oferece o poder e a flexibilidade de uma linguagem de script — mas com a dor de cabeça de tentar expressar essa flexibilidade com tags no XML.

Minha intenção nunca foi que o formato de arquivo se transformasse em um linguagem de script. Afinal de contas, minha visão original do Ant era que teríamos a declaração de algumas propriedades que descreveriam o projeto e que as tasks escritas em Java fariam toda a lógica. Os atuais desenvolvedores do Ant em geral pensam o mesmo.

Mas quando eu fundi XML e reflection no Ant, fiz algo que é 70–80% de um ambiente de script. Só que não percebi na época. Negar que as pessoas iriam usar o XML do Ant como uma linguagem de script é equivalente a pedir para que finjam que açúcar não é doce.

Se eu soubesse o que sei agora, eu teria tentado usar uma linguagem de script de verdade, como JavaScript via o componente Rhino ou Python via Jython, com bindings para objetos Java que implementariam a funcionalidade expressadas nas tasks. Então, haveria uma forma natural de expressar lógica, e não estaríamos presos ao XML como formato que é muito ruim para o maneira como as pessoas querem usar a ferramenta.

Um processo de build é algo que comumente será customizado. Usar XML para definir os passos de um build foi um erro no Ant. E que foi herdado pelo Maven.

O poder de uma linguagem de script casa muito bem com a definição de um processo de build. E isso foi levado em conta em ferramentas mais novas como Gradle, que usa a linguagem Groovy.

Dependências do Java EE 7 no Maven

Um projeto que usa o Maven ganha um monte de facilidades, da compilação a geração dos entregáveis em apenas um comando.

Vamos supor que temos o seguinte pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>br.com.alexandreaquiles</groupId>
  <artifactId>exemplo</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </properties>

  <dependencies>
    <!-- aqui ficam as dependências -->
  </dependencies>
</project>

Considere que nosso projeto tem a seguinte classe:

@Stateless
@Path("/produtos")
public class ProdutosResource {
  @Inject
  private EntityManager em;
  @GET
  public List<Produto> lista(){
    return em.createQuery("select p from Produto p", Produto.class).getResultList();
  }
}

Usamos algumas funcionalidades do Java EE 7 como EJBs (@Stateless), JPA (EntityManager) e JAX-RS (@Path e @GET).

A questão é: como devemos declarar as dependências dessas API do Java EE 7? Será que precisamos declarar cada API utilizada, uma a uma?

Dependência do Java EE 7

Não! Há uma dependência publicada no repositório central do Maven que disponibiliza as APIs do Java EE 7:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-api</artifactId>
  <version>7.0</version>
  <scope>provided</scope>
</dependency>

Perceba o escopo provided acima. Estamos indicando que essas APIs devem ser utilizadas apenas para compilação e não precisam ser incluídas no entregável (WAR, no nosso caso).

É importante notar que passamos a depender apenas das APIs e não de nenhuma implementação. Isso é muito interessante se desejarmos mudar de servidor de aplicação. Podemos gerar o WAR e implantá-lo no Wildfly, no Glassfish e em qualquer servidor que implemente o Java EE 7.

Dependência do Java EE 7 Web Profile

Na verdade, a dependência acima é da versão Full do Java EE, fazendo com que possamos usar JMS, JCA, entre outros. No nosso caso, é suficiente depender do Web Profile, que possui menos funcionalidades mas tem o que precisamos. Então, poderíamos usar:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-web-api</artifactId>
  <version>7.0</version>
  <scope>provided</scope>
</dependency>

Dependências mantidas pela JBoss

A empresa JBoss, que mantém os servidores de aplicação Wildfly e JBoss EAP e vários outros projetos, disponibiliza dependências alternativas para as APIs do Java EE 7.

Para ter como dependência as APIs do Java EE 7 Full:

<dependency>
  <groupId>org.jboss.spec</groupId>
  <artifactId>jboss-javaee-7.0</artifactId>
  <version>1.0.0.Final</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>

Já para as do Java EE 7 Web Profile:

<dependency>
  <groupId>org.jboss.spec</groupId>
  <artifactId>jboss-javaee-web-7.0</artifactId>
  <version>1.0.0.Final</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>

As dependências acima não são para a implementação do Wildfly ou do JBoss EAP. São dependências para as APIs do Java EE alternativas às vistas anteriormente.

Maven, a Super Nanny dos projetinhos rebeldes

A melhor coisa a fazer enquanto o maven baixa a internet é ler a biografia gloriosa dos criadores do maven #not (via @elizario)

Tem gente que odeia o Maven. Maven é autoritário, inflexível, de opinião forte.

Os caras da ThoughtWorks estão nessa turma. Dan North, thoughtWorker e criador do BDD, sempre caçoa do Maven em suas palestras. Uma das características típicas de um thoughtWorker de acordo com a página de recrutamento da TW é “enjoying complaining about Maven”.

O Bundler seguiu a parte de gerenciamento de dependências do Maven. E tá sendo zoado no Twitter.

_________

Um bom processo de build precisa:

  • compilar o código (no caso de Java)
  • integrar com bibliotecas de terceiros
  • executar os testes automatizados (unitários/funcionais)
  • gerar entregáveis
  • gerar documentação
  • analisar (estaticamente) código

Apesar de chato, Maven traz para projetos Java convenções para cada uma dessas tarefas.

As convenções podem ser um pouco esquisitas, o XML pode ser feio e as coisas podem parecer feitas por magia negra. Talvez vá longe demais definindo como organizar as pastas do seu projeto.

Mas Maven resolveu vários problemas que sempre aconteciam ao construir projetos em Java, fornecendo soluções out-of-the-box. Veio a calhar na Apache Foundation. A parte de gerenciamento de dependências levou a criação o Apache Ivy, que cuida só desse aspecto (o que é uma ótima idéia).

Maven ajuda a domar projetinhos rebeldes . E é melhor ainda para educar o projeto desde o nascimento.

Bons programadores tem o seu potencial melhor utilizado se não precisarem ficar recriando scripts de build/release. A lição do Maven é: “Automatize o que não for criativo. E use convenções”.

Build do Eclipse não vale!

Um anti-pattern que já vi muito por aí nos projetos Java em que participei é o Build do Eclipse.

Ao invés de usar um script de build automatizado para criar um pacote entregável (JAR, WAR, EAR), o sistema é colocado em produção simplesmente copiando os .class criados pelo Eclipse. Também são copiados o .project, .classpath e .settings e, às vezes, até as pastas de controle versão (.svn).

O Eclipse é ótimo para ajudar no desenvolvimento com seu code-completion, recompilação a cada arquivo salvo, sugestões, code snippets, atalhos de teclado e o montão de plugins. Também é excelente para refatorar de maneira eficiente e segura. E até para disparar um script de build.

Mas o Eclipse não deve ser usado como ferramenta de build.

Por que não?

  1. Porque builds tem que ser automatizados
  2. Usar builds do Eclipse, é um forte indicador de que você não automatiza seus builds.

    OK, você pode automatizar o build criando um sistema que abre o Eclipse, dispara os cliques necessários e copia os arquivos. Mas ninguém faz isso. E, convenhamos, essa ideia é estúpida. Usar uma ferramenta de build especializada como Ant ou Maven é muito, mas muito, mais simples.

    E por que automatizar seus builds é bom?

    Builds são receitas de bolo. São trabalhos repetitivos e tediosos. Não são processos criativos. Humanos são bons em criar e não em repetir.

    Com menos intervenção humana, seus builds terão menos erros. E você liberará tempo para trabalho criativo, que é o que os seus programadores deveriam estar fazendo.

  3. Porque você precisa usar sempre a mesma IDE
  4. Bons programadores Java tem opinião forte sobre qual IDE é melhor. Se você forçá-lo a usar uma IDE diferente, você fará um programador menos feliz e programadores bons precisam estar felizes para produzir código de qualidade.

    Pode parecer besteira, e talvez seja. Mas restrições de IDE são desnecessárias. Com um bom script de build, você se liberta de qualquer acoplamento com uma IDE específica e permite flexibilidade na hora de desenvolver.

  5. Porque você não está integrando continuamente
  6. Integração Contínua é uma das práticas mais interessantes (e usadas) do XP. Integrar continuamente é bom em qualquer contexto e em qualquer metodologia. E ajuda bastante na diminuição de vários riscos de projetos de software como descoberta tardia de problema de integração e defeitos, falta de código entregável e baixa qualidade interna.

    Mas para integrar continuamente você precisa ter, no mínimo, builds automatizados. Se o seu servidor de Integração Contínua precisa do Eclipse instalado, isso é um mal sinal.

    Para aproveitar todo o valor da Integração Contínua, além de builds automatizados, é importante que você tenha testes automatizados e análise estática de código. Assim, você testará seu código constantemente e evitará que código sem testes, duplicado e complexo entre em seu repositório.

Generalizando
Na verdade, um nome melhor para esse anti-pattern seria Build da IDE, porque o mesmo vale para Netbeans, IDEA ou qualquer outra IDE.

Mas, enfim, build do Eclipse não vale!