Category Archives: Java

Novo guia da Itexto: SDKMan

Infelizmente o Jenv (http://jenv.io) ficou fora do ar por um bom tempo (pelo menos os últimos cinco dias). Então, junto com Daniel Altarmiro, escrevemos um novo guia, desta vez sobre o SDKMan, tendo como base o nosso antigo guia do Jenv.

Entrei em contato com a equipe responsável pelo desenvolvimento e manutenção do projeto pelo Gist, e por lá pude ver que a situação já dura um bom tempo infelizmente.

Vocês podem baixar o guia clicando aqui.

Experimentando o Apache Tapestry

Recentemente começamos um projeto de evolução de uma aplicação web escrita em Apache Tapestry. Já fazia um tempo que não ouvia falar a seu respeito (uns seis anos pelo menos), então esta foi a oportunidade perfeita (e necessária) para que finalmente eu estudasse um pouco a seu respeito, e o que estou aprendendo tenho achado muito interessante.

Nota importante: este é um post escrito por um iniciante no assunto (eu). Sendo assim toda correção é bem vinda!

O que é o Apache Tapestry e minhas primeiras impressões

Uma definição bem vagabunda: “é framework para desenvolvimento de aplicações web, baseado em componentes, voltado para a plataforma Java”. Isto não incita ninguém a correr atrás da ferramenta, especialmente se levarmos em consideração que não vemos muitos artigos escritos a seu respeito nos últimos anos. Sendo assim neste post vou escrever uma aplicação de exemplo que ilustra seu funcionamento.

Meu primeiro contato com Tapestry foi através de artigos na Internet e revistas em 2004/2005: anos mais tarde, lá por 2011, trabalhei em uma empresa que tinha alguns projetos baseados neste framework, mas eu mesmo jamais tive qualquer contato com estes projetos. Como nunca fui fã de frameworks que fossem baseados em componentes, torci o nariz para o projeto, até este mês (agosto/2017), quando precisei estudá-lo.

O que gostei no Tapestry

  • Altíssima produtividade – o projeto implementa um mecanismo de carregamento automático de classes que nos possibilita modificar o código fonte (Java) e ver as mudanças imediatamente na aplicação em execução, sem qualquer necessidade de reiniciar a aplicação.
  • O modelo de desenvolvimento – uma aplicação escrita em Tapestry essencialmente é um conjunto de páginas (este não é um framework voltado para a escrita de APIs REST). Uma página é basicamente uma classe Java e um arquivo de Template que reflete o estado dos objetos instanciados desta classe.
  • POJO puro (ou quase) – você não precisa implementar interfaces neste framework. Classes Java simples (e portanto fáceis de serem testadas) fazem todo o trabalho, e ainda há um container de IoC embutido que cuida de todo o ciclo de vida destes objetos.
  • É muito rápido – o desempenho é inacreditável. O carregamento dinâmico de classes que mencionei acima, por exemplo, é praticamente instantâneo.
  • Gestão interessante de sessões – o modo como Tapestry lida com sessões do usuário é muito bacana. Você não tem um objeto “session” ou algo similar: ao invés disto, os atributos das páginas que mencionei acima são tratados como tal de uma forma completamente transparente para o desenvolvedor.
  • É extensível – pelo pouco que pude ver na documentação oficial do projeto, é possível integrá-lo com o Spring, o que abre um universo de possibilidades. Este link mostra algumas extensões publicadas além da documentação instruindo como aplicar o Spring e Hibernate no projeto.
  • Baseado em Maven (e Gradle) – isto é ótimo, pois me possibilita abrir o código fonte em praticamente todas as IDEs Java do mercado hoje.
  • A documentação oficial – é boa, apesar dos problemas que vou citar a seguir (talvez este ponto seja uma contradição). Ela quebra o galho, mas não é ideal.
  • Bom suporte no Eclipse – há bons plugins para o projeto (estou usando o Tapestry Tools). Neste link há algumas opções, mas você as encontra fácil no Eclipse Marketplace.
  • Convenções sobre configuração – você quase não precisa editar arquivos de configuração no Tapestry, basta seguir as convenções que o framework oferece, mas sem te prender a uma forma pré-fixada: é possível mudar, até onde pude ver, qualquer comportamento do framework.
  • Desenvolvimento do projeto continua ativo – apesar de não ouvir falar do Tapestry já faz um bom tempo, fiquei muito feliz ao ver na página de release notes que sai pelo menos um (no máximo 2) release importante por ano do framework.
    (talvez seja um release por ano pelo fato do projeto já estar maduro, o que é um ponto bastante positivo)

O que não gostei

  • Alguns problemas na documentação – No momento da escrita deste post a documentação do framework (especialmente a sessão “Tutorial“) estava quebrada, e os trechos que envolvem o código fonte não estava muito legível, o que dificulta a vida de quem está dando os primeiros passos.
    O “Getting Started”, por exemplo, está desatualizado (durante a escrita deste post), e você não conseguirá criar o projeto se seguir o passo a passo.
  • Ausência de uma comunidade brasileira – no site oficial estão listadas algumas mailing lists, mas apenas em inglês. Infelizmente não encontrei muitos artigos escritos em português, o que, por mais que muitos não creiam, dificulta a adoção da ferramenta em território nacional.
    (sobre a falta de artigos em português, comecei a fazer minha parte. Conforme vou aprendendo mais sobre o Tapestry posto por aqui).
  • Comunidade mundial pouco ativa – ao menos esta foi minha impressão. No Facebook, por exemplo, só encontrei um grupo contendo seis membros, e na mailing list de usuários há poucos posts novos por mês também.
  • Poucas issues no Jira do projeto e ausência de um roadmap para as próximas versões – este é um ponto de atenção: não vi muitas issues abertas no Jira da comunidade que digam respeito a novas funcionalidades, especialmente no que diz respeito à próxima versão, que será a 5.5.0.
    Também não encontrei um roadmap para as próximas versões: o máximo que encontrei foi um antigo documento (marcado inclusive como desatualizado) para a versão 5.0 do projeto.
    Estes pontos levantam dúvidas a respeito da evolução do projeto e se ele estará acompanhando as próximas tendências do mercado, o que conta como ponto negativo na sua adoção para novas aplicações.
  • Não é bom para se escrever uma API REST – o objetivo do Tapestry claramente é a criação de aplicações no sentido “páginas interativas”, não para a escrita de APIs. Na documentação oficial, por exemplo, você não vê qualquer menção ao desenvolvimento deste tipo de projeto (ao menos esta foi a impressão, enquanto iniciante, que tive do projeto).

Notas sobre a popularidade do framework e se você deve adotá-lo ou não com base nisto.

Tapestry, assim como Grails, Wicket, Play e outros não é o que podemos chamar de uma opção “mainstream”, tal como o Spring (aliás, de todos os frameworks fora do Java EE, talvez o único mainstream seja o Spring) ou Java EE (puro,o que inclui o JSF). Sendo assim, quaisquer comparações em relação a estas opções é descabida de fundamentação.

No caso do Java EE, ainda mais, especialmente se levar em consideração o fato de que todos os demais frameworks dependem deste para funcionar (o que consequentemente o tornará sempre o mais popular).

O que levo em consideração na adoção destes frameworks (que são verdadeiras armas secretas) é a atividade da comunidade que possuem e a quantidade de releases anuais. Normalmente quando não ocorrem releases por mais de um ano levanto o sinal amarelo.

A propósito, já escrevi sobre este tópico neste link alguns anos atrás, mantenho minha opinião e já está na hora de escrever outro post a respeito.

(e ei, se você curte uma tecnologia e não escreve sobre ela por não ser tão popular, só tenho uma coisa a te dizer: SHAME ON YOU!)

“Olá Mundo” com Tapestry

O código fonte pode ser baixado neste repositório do GitHub.

O que você vai precisar

  • Maven 3 – http://maven.apache.org 
  • Java (JDK) 1.8 (para a versão 5.3.8 pra frente do framework. As anteriores tem de usar o Java 7 ainda)
  • Qualquer IDE que ofereça suporte a Maven (o projeto vai abrir em qualquer uma, no meu caso estou usando o Eclipse junto com o plug-in Tapestry Tools.

É uma aplicação bem simples, só pra mostrar os conceitos de páginas, componentes e o modus operandi do framework, sendo assim não esperem muita coisa daqui.

Criando o projeto com Maven

No link “Getting Started” do projeto você lerá que deve executar o comando a seguir:

mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org

Não funcionará com o Maven 3: você topará com a seguinte mensagem de erro:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-archetype-plugin:3.0.1:generate (default-cli) on project standalone-pom: archetypeCatalog 'http://tapestry.apache.org' is not supported anymore. Please read the plugin documentation for details. -> [Help 1]

Isto é fácil de resolver: basta usar uma versão anterior do plug-in archetype do Maven, tal como a 2.4 no exemplo a seguir:

mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeCatalog=https://repository.apache.org/content/repositories/staging -Dfilter=org.apache.tapestry:quickstart

Após metade da Internet ser baixada para seu computador, serão aprsentadas 10 opções de arquétipos a serem escolhidos. Para o nosso exemplo adotei a opção “20” que equivale à última versão disponível do Tapestry (5.4.3).

Na sequência você precisará responder algumas perguntas:

  • O nome do grupo (groupId) – no exemplo: br.com.itexto
  • O nome do artefato (artifactId) – no exemplo: inicio-tapestry
  • Sua versão
  • O pacote no qual o código fonte se encontra (isto é importante e veremos mais a respeito mais à frente) – no exemplo: br.com.itexto

Respondidas as perguntas, será criado um diretório cujo nome equivale ao artifactId do projeto. Para testar o projeto, mude seu diretório corrente para o criado pelo projeto e execute o comando abaixo:

mvn jetty:run

Será iniciado o servidor Jetty, embarcado no projeto. Sim, seu ambiente de desenvolvimento está pronto. Acessando a URL http://localhost:8080, exposta na saída do prompt, você se deparará com a seguinte página:

clique sobre o link: ele aponta para uma URL similar ao artifactId que você forneceu ao criar o projeto. Agora você verá a página de boas vindas do Tapestry.

Dica: dá pra criar o projeto de uma forma muito mais simples usando o Eclipse, tal como descrito neste link.

A estrutura do projeto

A estrutura de diretórios criada pelo arquétipo do Maven

Importe o projeto Maven para a IDE de sua preferência, pois iremos alterar levemente este código fonte para expor o funcionamento do Tapestry. O arquétipo do Maven cria alguns sub-pacotes que são importantes: neste post falarei apenas de dois:

  • br.com.itexto.pages – contém as páginas do projeto
  • br.com.itexto.components – contém os componentes

E na pasta “src/main/resources” também são criados alguns diretórios interessantes:

  • src/main/resources/br/com/itexto/pages – contém os templates das páginas (falaremos mais sobre isto mais a frente)
  • src/main/resources/br/com/itexto/pages/components

Os mesmos nomes dos pacotes se repetem nas pastas criadas em “src/main”. Como disse no início deste post, uma aplicação Tapestry é composta essencialmente por páginas. Uma página, por sua vez, tem duas faces:

  • O código fonte Java que representa o “controlador” do projeto.
  • Um arquivo de template, que deve ter exatamente o mesmo nome da classe que a representa, na pasta “src/main/resources”. Estes templates tem a terminação “.tml”

Com base no exemplo acima, alterei o código fonte do arquivo “Index.java” para que fique tal como o exposto a seguir:


package br.com.itexto.pages;package br.com.itexto.pages;

public class Index{

public String getRandomico() {
return java.util.UUID.randomUUID().toString();
}
}

Note que é uma classe Java simples, sem a necessidade de realizar qualquer implementação de interfaces ou uso de anotações. Alterei também levemente o arquivo de template relacionado (src/main/resources/br/com/itexto/pages/Index.tml) para que fique tal como no exemplo a seguir:


<html t:type="layout" title="inicio-tapestry Index"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
>
<p>Randomico como ${randomico}</p>
</html>

(Quando acessamos a URL do projeto, é a página Index que será renderizada para o visitante. Se você quiser experimentar, crie páginas com o nome “Teste” (contendo o código Java e o de template) e acesse as URLs diretamente para brincar.)

O template é a outra face da página: é o conteúdo HTML que será renderizado. No caso do Tapestry, é XHTML que usamos na realidade (HTML formatado como XML). Em seu interior, incluímos uma expressão: ${randomico}, que irá renderizar o valor retornado pela função getRandomico(), definido na classe Index.java. Tapestry usa a especificação JavaBeans do Java para definir as expressões que serão usadas na renderização das páginas.

Coloque a aplicação para executar em sua IDE usando o comando “mvn jetty:run” para ver o resultado, que será similar ao exposto na imagem a seguir:

Recarregamento dinâmico de classes

Agora, com a aplicação ainda em execução, faça o seguinte experimento: modifique a função getRandomico() da classe Index para que retorne diferentes valores, sem parar a aplicação. Carregue novamente a página: a mudança é instantânea.

Vá além: crie outros “getters” para novas propriedades e vá incrementando o arquivo de template. Note como mesmo novos métodos são detectados instantaneamente na aplicação sem precisar realizar qualquer tipo de reinicialização. Isto dá muita produtividade no desenvolvimento. A propósito, observou a velocidade com que a aplicação é iniciada?

E os componentes?

Bom, vendo a última imagem você deve ter notado que é renderizada uma barra de navegação, mas não a digitamos no código fonte do template. Notou? Então vou repetir o código aqui:


<html t:type="layout" title="inicio-tapestry Index"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
>

<p>Randomico como ${randomico}</p>
</html>

O template define que ele é de um dado tipo: “layout”. Você notou que existe uma pasta chamada “components” em “src/main/resources” e também um pacote chamado “components”? Se você abrir o código fonte do arquivo “src/main/resources/br/com/itexto/components/Layout.tml” verá que a barra de navegação, assim como o rodapé e toda a identidade visual está aí. E também vai achar um trecho similar ao exposto a seguir:

<div class="container">
<t:body />
<hr />
<footer>
<p>&copy; Your Company 2015</p>
</footer>
</div> <!-- /container -->

Sabe esta tag <t:body/>? Ela inclui o conteúdo da página Index no interior do Template. É um mecanismo muito similar ao Sitemesh do Grails! Na realidade é um pouco mais que isto, mas para este post inicial, creio que esta definição será o suficiente.

Código fonte do exemplo

O código fonte deste projeto pode ser encontrado neste link. Talvez eu o evolua no futuro conforme novos posts surjam no blog.

O que o Tapestry não tem e não é

É importante salientar que este não é um framework full stack tal como o Grails ou Spring Boot (dependendo da configuração). Quando criamos um novo projeto ele não vem com a camada de persistência pré-configurada. É necessário realizar esta configuração manualmente, tal como exposto na própria documentação.

O projeto já vêm com algumas configurações prontas, tal como o mecanismo de logging e algum CSS, baseado no bootstrap, mas não muito mais do que isto.

Também é importante observar o seu foco: são aplicações web que contenham páginas interativas, não APIs REST. É possível configurar no mesmo projeto um segundo framework para prover estas funcionalidades, tal como o JAX-RS ou mesmo o Spring, mas não recomendo isto.

Sei que não mencionei neste post nada relativo à gestão de sessões, mas este é outro aspecto interessantíssimo do Tapestry: não é um destes frameworks no qual você tem a sessão explícita. Lidamos com ela de uma forma indireta através de anotações que incluímos em nossas classes (@Persistent). Sendo assim, para projetos que precisem lidar diretamente com sessões (cada vez menos o caso), Tapestry também não se aplica.

Conclusões finais sobre este contato inicial

Neste post mostrei apenas o básico do básico do básico do Tapestry (quase nada pra ser sincero). Muita coisa ficou de fora, tal como a forma como é realizada a navegação entre as páginas, gestão de sessão, formulários, inversão de controle, etc.

Meu objetivo foi apenas gerar algum interesse pelo framework e, talvez, criar o post inicial que me permita ir detalhando aos poucos como o Tapestry funciona conforme vou avançando nossos estudos a seu respeito.

Estou achando uma solução muito interessante: mais interessante que o JSF na minha opinião, pois é mais fácil de aprender e oferece uma produtividade muito superior. Isto sem mencionar no desempenho, que foi um dos fatores que mais me surpreendeu.

(tal como mencionado no início deste POST, para implementar APIs REST não é uma solução interessante. Aliás, nem sei se faria sentido, dado ser focado em componentes, e não em ações, tal como ocorre no Grails e Spring MVC)

Dado o estado atual da comunidade (ao menos minha percepção inicial), não sei se o recomendaria para iniciar novos projetos, mas em relação a este aspecto apenas o tempo me dará uma resposta satisfatória. Entretanto, se o tempo mostrar que ainda há gás nesta comunidade, sem sombra de dúvidas que o incluirei em nosso radar.

Visto que tão pouco se escreve a respeito do Tapestry ultimamente, creio que no mínimo estes meus posts servirão para atrair interessados a seu respeito. Mesmo que você jamais o use, as ideias que ele trás já valem à pena conhecer.

Mais fontes sobre Tapestry

Talvez você tenha se interessado pelo framework. A documentação oficial apesar de boa não é suficiente. Sendo assim, seguem alguns links que estão me ajudando no aprendizado:

Tapestry Jumpstart – quase um cookbook de Tapestry

Wiki do projeto – tem algumas informações que não constam na documentação oficial

Este tutorial da Tutorials Point – no formato PDF, é um complemento bem interessante para o tutorial oficial

Tutorial oficial – apesar de estar quebrado o HTML, me ajudou bastante

Novo guia da itexto: usando Jenv!

Se existe uma ferramenta que me ajuda HORRORES no dia a dia é o Jenv: trata-se de um gerenciador de ferramentas baseadas em Java e que ofereçam interfaces de linha de comando, tais como Grails, Groovy, Maven, JDK, Ant, Spring Boot e muitas outras.

No meu caso, como lido com diversos projetos baseados em Grails, com versões que variam da 1.1 a 3.1.4, é uma mão (ou melhor, duas!) na roda. Se a coisa parece chata, imagine que também lido com diferentes versões do JDK (do 1.3 a 1.8!). :)

Este é um guia rápido para que você possa começar a trabalhar com a ferramenta imediatamente. Exponho apenas o essencial, mas também alguns hacks que você pode aplicar na ferramenta e, claro, sua comparação com o SDKMan, que é outra opção bastante interessante.

Trata-se de uma ferramenta que sempre indico para nossos clientes de consultoria e alunos. Acabo de publicar então o terceiro guia da itexto, que você pode baixar neste link.

Espero que goste e que o Jenv lhe seja tão útil quanto é para nós!

Nosso novo projeto: Formação itexto!

Pessoal, é com muita alegria que anuncio o nosso novo projeto: “Formação itexto“. Trata-se dos cursos online da itexto sobre tecnologias que sei agregarem real valor a empresas e desenvolvedores independentes.

Após muita pesquisa finalmente cheguei a um formato que, acredito, trás muito mais para nossos alunos e pessoas interessadas em obter excelente produtividade e qualidade no desenvolvimento de sistemas.

Nosso primeiro treinamento será sobre Groovy e Grails (previsível :) ). As turmas são limitadas, e maiores detalhes podem ser vistos no site da “Formação itexto”: http://formacao.itexto.com.br

Aguardo vocês lá!

Projete boas APIs com Java exceptions

No meu último post falei um pouco sobre um dos meus recursos favoritos do Java que são as exceptions. Lá falei um pouco sobre como as exceções nos ajudam a pensar contratos, mas agora gostaria de ir um pouco além. Agora vou mostrar como tornar suas APIs muito mais ricas usando exceptions.

Mas o que é uma API mesmo?

Não vou dourar a pílula, sendo assim vou definir de uma forma bem direta:

é a parte executável do seu programa que você irá expor a outros programadores para que eles a executem

Repare que não usei a palavra “sistema” aqui, mas sim “programa”. Quando você projeta uma classe que possuirá métodos públicos, pensados para que outros programadores a usem, você está criando uma API (mesmo que você seja este outro programador).

Existe algum programa que não crie uma API Kico? Yeap: pense nos scripts que você escreve para automatizar suas tarefas. É aquele tipo de programa que você apenas quer que seja executado. Softwares feitos apenas para uso direto normalmente não expõem uma API.

Ah, mas aí, se outro programa o chamar, eu posso dizer que aquilo ali é uma API? Aí você relativizou a coisa e este post não sai. :)

Falaremos aqui não de APIs REST, mas sim aquela que se manifesta sob a forma de código executável, ou seja, os métodos públicos que você declara em suas classes ou os abstratos em suas interfaces.

O que é uma boa API?

Uma boa API é aquela que nos diz exatamente o que será feito. Tudo começa a partir do nome escolhido pelo programador para aquele método, que deverá expor de forma evidente a intenção daquele código.

Um bom nome já nos deu boa parte do que precisamos, o segundo componente são os parâmetros que a API espera. Idealmente devem ser poucos e com uma granularidade adequada. Vamos começar com um exemplo simples que iremos ir melhorando durante o post (sim, é código real):

int cadastrarPessoa(int id, String nome, String sobrenome, Date dataNascimento)

Reparou como a granularidade está errada? Por que não simplesmente passar um objeto do tipo Pessoa a ser persistido, tal como a versão melhorada que mostrarei a seguir? Ainda não é uma API perfeita, mas é inegavelmente mais simples (e o programador sofre menos no momento em que for digitar seu código):

class Pessoa {
int cadastrarPessoa(Pessoa pessoa) (...)
}

Há outro aspecto na API que deve ser levado em consideração: o tipo de retorno. Nossa versão anterior retorna um valor inteiro, que representa o identificador do registro no banco de dados. Se houver um erro, ela poderia simplesmente nos retornar um valor negativo: “-1: sem id, -2: sem nome, -3: sem sobrenome” e por aí vai. O valor de retorno terá então duplo sentido: o óbvio (retornar o identificador) e identificar um erro (quebra de contrato).

Um cliente da API então escreveria código similar ao exposto a seguir para lidar com erros:

switch (cadastrarPessoa(pessoa) {
case -1:
System.out.println("Opa! Sem o ID que deve ser preenchido antes!");
break;
case -2:
(...)
}

Sofrimento eterno que poderia ser um pouco aliviado incluindo algumas constantes, mas ainda seria um sofrimento eterno. Vimos alguns bons pontos na definição de uma API:

  • Um bom nome
  • Uma boa definição de parâmetros
  • Um valor de retorno que seja significativo (e possua uma única função)

Falta algo: os limites da API, ou seja, as condições para que ela funcione. É muito difícil tornar isto explícito em uma API REST, mas com código executável, especialmente em linguagens que possuam um recurso como as exceptions do Java, não. Como seria uma terceira versão da nossa API?

class PessoaNegocio {
void cadastrar(Pessoa pessoa) throws Validação (...)
}

Não preciso mais retornar um valor inteiro: se a persistência for bem sucedida, o próprio método já vai preencher o atributo “id” do objeto que passei como parâmetro. E se algo der errado? Uma exceção chamada Validação (evite caracteres especiais no seu código) será disparada, e nesta se encontrarão os detalhes a respeito do que deu errado.

A exceção é parte do contrato: ela nos diz algo como:

Ok, vou cadastrar esta pessoa no banco de dados, mas apenas se o objeto tiver valores válidos para todos os atributos.

Nossa API agora tem um limite bem definido: você lê a assinatura do método e sabe que somente objetos válidos, ou seja, aqueles cujo estado interno esteja de acordo com o que se espera, será persistido no banco de dados.

O programador agora pode escrever código ainda mais interessante:

try {
// o fluxo principal fica BEM isolado
negocio.cadastrar(pessoa);
} catch (Validação erroValidacao) {
// eu sei que meu problema é de validação
// talvez eu possa projetar algum comportamento
// de retentativa, ou mesmo informar melhor o
// usuário final a respeito da bobagem que está
// tentando fazer
}

E aqui entra mais um ponto que você deve levar em consideração quando for escrever sua API: os erros que talvez seus clientes não consigam aliviar. Uma falha no seu SGBD. Será que seria legal tentar melhorar um pouco mais nossa API tal como no exemplo abaixo?

void cadastrar(Pessoa pessoa) throws Validação, JDBCException

Antes eu sabia que objetos inválidos não seriam persistidos: agora também sei que uma falha no banco de dados pode ocorrer. Mais do que isto, sei que é um banco de dados relacional (JDBCException). Aqui entra o seu contexto.

Você quer que os usuários da sua API saibam que por trás dos panos está um SGBD relacional? Se sim, ok. Se não, trate internamente estes problemas e dispare uma exceção do tipo RuntimeException ou derivadas. Você estará aqui expondo detalhes de uma camada inferior sem necessidade alguma, e ainda tornando mais difícil a vida dos usuários da sua API.

Agora, se você quer expor este aspecto do sistema, perfeito: há aqui uma delegação de responsabilidade. O cliente da sua API terá de lidar explicitamente com erros provenientes da camada inferior do sistema.

Uma rápida menção ao Groovy

groovylogo

Groovy é uma linguagem que visa desburocratizar o trabalho do desenvolvedor. Uma das formas que faz isto é através do modo como lidamos com exceções do tipo checked.

Enquanto no Java, código que irá chamar um método que dispara uma exceção obrigatoriamente deve envolver a chamada ao método em um bloco catch ou incluir a declaração da exceção no método que o chamará, em Groovy este não é o caso. Então, código Java similar ao exposto abaixo:

try {
negocio.cadastrar(pessoa);
} catch (Validação ex) {
// trato aqui
}

ou

void executaAlgo() throws Validação {
(...)
negocio.cadastra(pessoa);
(...)
}

Em Groovy eu apenas chamo o método e o incluo em um bloco try… catch ou adiciono uma declaração do tipo throws  se eu quiser. Ok, ignoro então as exceptions? Não.

Se você vai projetar uma API, tudo o que disse em relação ao Java também se aplica ao Groovy, pois exceptions nos ajudam a explicitar os limites da mesma.

Concluindo

Meu objetivo neste post foi ir além do uso padrão das exceptions como uma ferramenta que nos possibilita escrever código mais robusto. Como puderam ver, elas também nos ajudam a implementar melhores APIs: com contrato melhor explicitado, mais fáceis de usar e que, consequentemente, acabarão por criar sistemas também mais robustos.

Exceções do Java são úteis: talvez você é que não saiba usá-las

Semana passada escrevi sobre como os programadores complicam o código Java. Volto este tema agora para falar um pouco sobre um recurso extremamente útil da linguagem que, acredito, é muito mal compreendido: as exceptions.

Uma visão histórica

Acredito que para dominar uma linguagem de programação um dos primeiros passos que devemos tomar é buscar entender o que motivou e quais princípios guiaram sua criação. Uma abordagem histórica cai muito bem neste momento, sendo assim recomendo que você leia o capítulo sobre exceções tal como redigido na especificação do Java 1.0, acessível neste link (observem a ironia).

A leitura desta especificação é muito interessante, especialmente quando vemos de forma explícita os principais objetivos dos designers na criação da linguagem naquele momento: prover portabilidade e robustez. Ao falarmos de exceções o que realmente nos interessa é o segundo princípio. Como esta robustez é obtida?

Antes um pouco de semântica: uma exception, como o próprio nome já nos diz, denota uma condição anormal que ocorre durante o fluxo de execução dos nossos programas.

 

A vida sem exceptions

Muitas linguagens de programação simplesmente finalizam a execução do software (pense em C ou Pascal) quando algo assim ocorre e não há alguma forma de tratamento do erro padronizada, outra alternativa é apenas retornar um código de erro que pode facilmente ser ignorado pelo programador (pense na função read do C retornando o valor -1, por exemplo).

Vamos a um exemplo rápido usando “pseudo C”. A linguagem possuí uma função chamada read (mencionada acima) que lê bytes em uma fonte de dados e a armazena em um buffer. Ela retorna o valor -1 caso algo de errado ocorra, 0 se chegamos ao final do arquivo e um valor positivo nos informando quantos bytes foram lidos. Veja o código abaixo:

char buffer[128];
read(arquivo, buffer, 128);
printf("Serei impresso?");
// operações importantes seriam executadas na sequência

Este é um código muito comum: o programador espera que o arquivo sempre exista, sendo assim, possuí a “certeza” de que a saída “Serei impresso?” sempre irá ser exposta em seu terminal. Mas nem sempre é assim: e se o arquivo imaginário sumir? Nosso ingênuo programador irá enfrentar problemas pois a execução do seu programa terminaria em algum ponto após esta impressão.

Talvez nosso programador pudesse escrever o código acima de uma forma diferente tal como no exemplo a seguir:

char buffer[128];
int resultadoLeitura = read(arquivo, buffer, 128);
if (resultadoLeitura < 0) {
     // tento corrigir a situação aqui
}

É uma alternativa, o código se tornou mais robusto, mas sua leitura não torna claro o que de fato ocorreu para termos um erro. O arquivo foi apagado? Seria um problema de permissão? Ainda pior: o código que motivou a escrita do programa, o fluxo principal (em condições ideais de temperatura e pressão) agora se encontra mesclado ao código de tratamento de erros.

Java veio com uma solução mais interessante. Visto que nossas classes são uma interface, por que não alertar seus clientes a respeito do que pode dar errado e, ainda melhor: força-los a tratar estas situações (o problema está neste “força-los”)?

Pensando como Gosling, Joy e Steele

java_language_spec

Clareza na escrita

Por mais incrível que possa parecer a diversos críticos atuais da linguagem, naquela época um dos objetivos era ter código menos verboso. O ideal é que o programador pudesse ver o fluxo principal do seu programa de uma forma simples, e o tratamento dos erros isoladamente, tal como no exemplo a seguir:

String conteudoArquivo(File arquivo) {
          try {
                // meu fluxo principal entra aqui
          } catch (FileNotFoundException ex) {
               // o que eu faço se o arquivo não existir?
         } catch (EOFException ex) {
              // e se o arquivo chegar ao fim antes do imaginado?
         } catch (IOException ex) {
              // e se for algum outro erro de I/O que não previ e
             // não seja como os que mostrei antes?
        }
}

É interessante como agora você sabe o quê pode ter dado errado, e consegue diferenciar de forma clara como tratar cada uma daquelas situações. Ainda melhor: o que realmente importa, o fluxo principal, está claramente isolado.

Uma exception é na realidade um desvio de fluxo. Talvez você lide com erros do tipo FileNotFound e IOException da mesma forma. Neste caso, como a primeira exception é uma subclasse da segunda, basta colocar um único bloco catch para esta.

Exceptions como contrato

 

Mais do que isto, acredito que muitos programadores simplesmente não saibam interpretar o código que encontram: imagine uma declaração de método como a abaixo:

void processeArquivos(File[] arquivos) throws FileNotFoundException

O método me diz:

“Recebo uma lista de arquivos em uma matriz como parâmetro. Conseguirei executar meu trabalho quando todos os arquivos forem acessíveis a mim. Se me passar algum deles que não seja, repasso a você, que me chamou, a responsabilidade de lidar com este problema para mim.”

O método, é um contrato, e a exception, uma validação de que o mesmo será cumprido. Se não for o caso, o fluxo deverá ser alterado para que o seja ou a responsabilidade para se resolver o problema, repassada a outro objeto (talvez o cliente do cliente).

Mais do que isto: um contrato válido é aquele bem definido. Fica fácil perceber quando quem escreveu o código não tem muita ideia a respeito do que está fazendo. Observe a declaração de método abaixo:

void processeArquivos(File[] arquivos) throws Throwable

O que este método me diz?

“Recebo uma lista de arquivos para serem processados, alguma coisa pode dar errado, mas não sei o que.”

Temos um meio contrato aqui: apenas sabemos que devemos enviar arquivos para este método. Não sei se todos devem realmente estar acessíveis, apenas os envio.

Exceptions checadas e não checadas. Pra quê?

Por que há as tais “checked exceptions” e “unchecked exceptions”? O que diferencia uma de outra? Uma interpretação rápida seria:

“Checked exception é aquela que é uma subclasse de java.lang.Exception e que, se eu disparar no corpo do meu método, tenho de incluir uma clausula throws. A outra não, eu apenas a disparo lá dentro e não aviso ninguém a respeito pois é uma subclasse de RuntimeException ou Error.”

O que não responde quase nada além de expor uma hierarquia de classes incompleta. A resposta é mais simples: há erros que são tratáveis e outros nem tanto. Erros tratáveis são aqueles que definem um contrato e os clientes conseguem ao menos tentar resolve-los quando ocorrem.

Por exemplo: um arquivo inacessível é um erro tratável. Se topei com um erro do tipo FileNotFound, talvez seja possível criar um novo arquivo para em seguida chamar aquela função ou procedimento novamente.

Por outro lado, se houver um crash do meu sistema operacional ou meu sistema de arquivos desaparecer, não há muito o que eu possa fazer. É um erro de tempo de execução (runtime). E a quantidade de problemas deste tipo que podem ocorrer é praticamente infinita: seu HD pode pegar fogo, ou seu HD pode ser removido, ou seu sistema operacional pode desaparecer, ou sua rede pode se tornar inacessível, ou alguém pode desligar o servidor, ou….

Por que as exceções do tipo Runtime não são “checked”? Vou pedir para Gosling, Joy e Steele uma força. Veja o que é dito na seção 11.2.2 da especificação:

“A informação disponível para o compilador Java, e o nível de análise que este executa, raramente são  suficientes para se descobrir que erros de tempo de execução poderão ocorrer, mesmo sendo óbvio para o programador. Obrigar o programador a declarar todas estas exceções seria apenas uma tarefa irritante para o desenvolvedor.” (tradução minha)

É interessante também ver o que os autores dizem na especificação ao nos dizerem por que a outra categoria de erros (java.lang.Error) não são checados (11.2.1):

“São problemas que podem ocorrer em inúmeros pontos de um programa e cuja solução é difícil ou impossível. Um programa escrito em Java que precisasse lidar com todos estes erros seria uma zona e sem sentido algum” (tradução minha)

Sendo assim, ao invés de obrigar o desenvolvedor a tratar cada um destes problemas, por que não força-lo a lidar apenas com o que pode ser tratado? Este é um dos principais motivadores: te forçar a escrever menos código e, quem sabe, escrever código de melhor qualidade.

Isto não quer dizer que você deva escrever código como o a seguir:

try {
    // aqui está meu fluxo principal
} catch (Throwable t) {
    // aqui lidarei com todos os problemas possíveis e impossíveis
    // dos multiversos
}

Quando escrevemos algo como “catch (Throwable)” estamos com uma das seguintes ideias na cabeça:

  • Vou ignorar qualquer tipo de erro que venha a ocorrer.
    (me faz lembrar do “on error resume next” do VB)
  • Todos os erros são iguais, sendo assim os tratarei todos da mesma forma.

Se um dos princípios norteadores da criação do Java foi a robustez, e estamos usando Java (ignore sua linguagem favorita por um momento), escrever código deste tipo é corromper a linguagem e se induzir ao erro.

Mais do que isto: checked exceptions permitem ao compilador verificar se você está lidando com as situações anômalas definidas no contrato das suas interfaces.

Como uso bem as exceções?

O principal motivador para a escrita deste post são as críticas que ouço a respeito do modo como a linguagem Java lida com exceções. É interessante como muitas pessoas se esquecem que o recurso foi incluído na linguagem para facilitar a vida do programador, e não complicá-la.

Curiosamente, a esmagadora maioria das críticas que vejo são motivadas pelo mal uso ou compreensão do recurso. Sendo assim, seguem algumas dicas:

  • Pense na declaração de exceções como a definição de um contrato bem definido: elas definem premissas, ou seja, aquilo que não deve ocorrer para que o código possa ser executado com sucesso.
  • Tire proveito da precisão: um “catch (Throwable)” não te possibilita lidar com as diferentes situações ou quebras de contrato que podem ocorrer durante a execução do sistema, você estará apenas criando um bloco catch que, no futuro, pode se tornar um verdadeiro monstrinho.
  • Entenda a diferença entre checked e unchecked exceptions.
  • Uma declaração de método que contém uma instrução throws seguida de 293847 tipos de exceção e algo como um “throws Throwable” são a mesma coisa.
  • Se checked exceptions são um problema para você, considere linguagens como Groovy que torna o tratar de exceções uma tarefa opcional

Espero com este texto ter clarificado alguns pontos a respeito de um dos aspectos mais interessantes da linguagem Java.