A mágica por trás das interceptações em Java

Tinha escrito esse no LinkedIn há algum tempo. Só estou postando aqui para manter tudo junto.

Qualquer programador Java deve estar mais habituado a injetar dependências, definir métodos de callback ou, entre outras, fazer lazy load de suas entidades. Mas você já parou para pensar como isso acontece por trás dos panos? Como, por exemplo, o Hibernate permite que você defina métodos para executar antes e depois da sua entidade ser sincronizada com o banco de dados subjascente, como um container EJB/CDI consegue interceptar as chamadas de métodos para iniciar transações, executar rotinas de segurança e encapsular exceções?

Como acontece em qualquer profissão, o profissional trabalha melhor quando conhece bem a ferramenta que utiliza. E não seria diferente com os profissionais de TI. Tomar conhecimento dos mecanismos que permitem com que a mágica por trás das tecnologias aconteçam não só te tornará um profissional melhor, mas te permitirá antecipar situações inconvenientes e utilizar seguramente um componente qualquer.

Neste caso, vamos falar dos proxies.

Proxy é o nome do design pattern designado a permitir fornecer um substituto para um objeto qualquer. Esse substituto deve ser capaz de interceptar as chamadas ao objeto substituído permitindo que operações extras sejam executadas em complemento à função do objeto real.

Suponha que você tenha a classe a seguir:

public class Greetings {
   
    public String sayHello(String to) {         
        return "Olá " + to + ".";     
    } 
} 

E você tenha uma factory para os objetos dela:

public class GreetingsFactory {

    public static Greetings createGreetings() {  
       return new Greetings();
    }
} 

Quando você precisar gerar uma saudação, você pode facilmente invocar:

GreetingsFactory.createGreetings().sayHello("Jaumzera");

Que vai te retornar um belo:

Olá Jaumzera.

Agora suponha que em dado momento seu cliente decida que a saudação deva ser retornada entre tags <h1> e </h1>. É claro que no mundo real simplesmente alteraríamos a classe Greetings para fazer o desejado. Entretanto, aqui estamos para exemplificar o padrão proxy.

public class GreetingsHtmlProxy extends Greetings {
   
    @Override
    public String sayHello(String to) {         
        return "<h1> " + new Greetings().sayHello(to) + ".</h1>";     
    } 
} 

A nova classe GreetingsHtmlProxy implementa a mesma interface do proxy (que neste caso é a própria classe), delega a chamada para um objeto da classe Greetings e altera seu resultado para se adequar ao novo requisito. E então a fábrica é alterada para retornar para retornar o novo objeto saudador:

public class GreetingsFactory {

    public static Greetings createGreetings() {  
       return new GreetingsHtmlProxy();
    }
} 

O fato da classe proxy estender a classe original, fará com que não tenhamos problemas de compatibilidade em quaisquer que sejam os pontos onde a classe Greetings original estava sendo usada.

E onde antes exibia uma suadação simples, passará a exibir:

<h1>Olá Jaumzera.</h1>

Observe que utilizando a classe proxy poderíamos, por exemplo, executar ações antes ou depois do método orignal ser executado, como executar rotinas de log, iniciar e parar transações, criar callbacks para pré e pós execução, entre outras.

Concordamos no fato de que é um exemplo medíocre, mas serve bem para mostrar o papel de um objeto substituto, ou seja, um proxy.

Com a importância que esse tipo de abordagem foi ganhando, foi inserida na plataforma Java, a partir da versão 1.3, a possibilidade de criar-se versões dinâmicas do pattern proxy. Essas versões nada mais são do que implementações de runtime de interfaces Java. Pelo fato de se basearem em interfaces, vamos extrair uma interface do exemplo anterior.

public interface IGreetings {
    
    String sayHello(String to);
    
}

E alterar o objeto Greetings para implementá-la:

public class Greetings implements IGreetings {
    
    public String sayHello(String to) {
        return "Olá " + to;
    }        
}

Para criar um proxy dinâmico, neste caso, vamos implementar a interface InvocationHandler, do pacote java.lang.reflect:

public class GreetingsInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        return "<h1>" + new Greetings().sayHello((String) args[0]) + "</h1>";
    }
}

A interface faz exatamente o mesmo trabalho do proxy que implementamos manualmente, ou seja, envolve o retorno do objeto original em tags <h1> e </h1>.

Para executar o exemplo, criamos uma instância do proxy a partir da classe Proxy, também do pacote `java.lang.reflect`:

IGreetings greet = (IGreetings) Proxy.newProxyInstance(
                Greetings.class.getClassLoader(),
                new Class[]{IGreetings.class},
                new GreetingsInvocationHandler());

System.out.println(greet.sayHello("Jaumzera"));

E, ao executar, podemos observar o resultado desejado.

<h1>Olá Jaumzera.</h1>

O fato é que utilizar implementações de runtime baseadas em interfaces limita um pouco a utilização da estratégia de proxy. Portanto, frameworks como o próprio Hibernate utilizam APIs mais sofisticada que permite a criação de proxies baseadas em classes concretas, como por exemplo o CGLib (https://github.com/cglib/cglib), uma vez que seus objetos de trabalho, ou seja, as entidades, são baseadas em POJOs frequentemente criados sem interface de negócio.

Para implementar o mesmo proxy anterior utilizando a API CGLib, faríamos da seguinte forma.

 Greetings greetCGLib = (Greetings) Enhancer.create(
                Greetings.class, new MethodInterceptor() {

            @Override
            public Object intercept(Object o, Method method,
                    Object[] os, MethodProxy mp) throws Throwable {
                
                return "<h1>" + mp.invokeSuper(o, os) + "</h1>";
            }
        });
        
        System.out.println(greetCGLib.sayHello("Jaumzera"));

E, ao executar, verás que o resultado é o mesmo do exemplo anterior. Também é evidente que a CGLib simplifica muito a criação dos proxies dinâmicos, uma vez que podem ser utilizadas a partir de classes concretas, em detrimento dos proxies Java, que são baseados em interfaces.

Enfim, resta dizer que a CGLib não possui uma documentação muito legal, portanto, você tem que confiar no tato e no JavaDoc da API. Além do fato de que, a menos que você esteja projetando frameworks ou qualquer coisa do tipo, sempre haverá uma solução baseada em modelos de objetos mais elegante do que apelar para introspecção de objetos ou qualquer coisa mais “baixo nível” dessas.

Até o próximo.

Similaridade entre palavras

Há um bom tempo (quase uma década) eu tive um problema para normalizar os nomes das ruas de uma base de dados, que eram digitadas manualmente pelos usuários, com os nomes da base de dados fornecida pelos Correios. Na época, um formulário de venda era preenchido manualmente e gravado no banco de dados. Quando houve a necessidade e extrair relatórios analíticos, com base nas informações de venda, nos deparamos com o problema da grafia dos nomes das ruas que era um verdadeiro pesadelo. Só para exemplificar, a rua “Juscelino Kubitschek” era escrita das formas mais criativas possíveis:

  • Jusselino Kubicheque
  • Jucelino Kubicheck
  • Jucelino Kubicheki
  • JK

E por aí vai. O desafio era encontrar uma forma de normalizar essa base automaticamente, sem ter que verificar cada uma das alternativas criativas inseridas pelos usuários.

Depois de alguma busca, me deparei com um artigo denominado How to Strike A Match que propunha uma solução que consistia em criar uma série de pares de caracteres de cada uma das duas palavras, encontrar a interseção entre os  dois grupos de pares, multiplicá-la por 2 e dividi-la pela soma da quantidade de pares.

strike_a_match

O resultado da comparação é o percentual de aproximação entre as duas entradas. Daí bastou estabelecer um nível de corte com base na similaridade e executar as atualizações.

Esses dias, levantando um material sobre NLP para um sistema em que estou trabalhando, acabei me lembrando desse fato, justamente por causa a dificuldade de se fazer análise de linguagem natural para buscas em uma base de dados. Então resolvi postar aqui, já que faz uma era que não vinha escrevendo.

Essa versão do Gist já está atualizada para o Java 8. Já a saída fica como na imagem a seguir:

strikeamatch_output

Sendo que quanto mais próximo de 1, mais parecidas são as duas entradas.

Eu não consegui encontrar o artigo e nem o site original, porém se você quiser conhecer o processo na íntegra, ele está disponível aqui.

Valeu!

Empacotamento Java

Há tempo sem escrever aqui por causa da correria. Dia desses, estava conversando com um amigo de trabalho e entramos no mérito do empacotamento dos sitemas em que estávamos trabalhando. Na ocasião, o assunto era DDD, mas acabamos caindo no assunto packages. A maioria dos sistemas em que eu trabalhei usa aquela divisão de responsabilidades do tipo DAOs, services e controllers na qual, teoricamente, a DAO toma conta das suas entidades (entidades ORM), os serviços encapsulam a lógica de negócio e os controllers atuam como – rufem os tambores – controladores de fluxo.

Minha opinião é que esse tipo de divisão é assim: DAO: tabelas relacionais representadas em objetos Java; Services: lógica estruturada usando classes e objetos; e contollers: onde vai todo o resto. Mas isso é assunto para outro dia.

É muito comum, nesse tipo de divisão – que também é chamada de package-by-layer – utilizar um pacote com.acme.model (ou entity) para colocar as entidades, com.acme.service para colocar os serviços e com.acme.controller para colocar os controllers, sob a justificativa de que isso mantém os artefatos bem separados caso o sistema venha a escalar.

Não!

Gosto de pensar em pacotes como pequenos módulos – package-by-feature – se você tem um módulo de gerenciamento de usuários, nada mais justo do que manter os respectivos artefatos (entities, DAOs, services e controllers) dentro do pacote com.acme.usuario. Não é?

Pense bem, isso cria uma boa alternativa pra você utilizar o modificador package-private ou default (só contextualizando que estamos falando sobre Java). Dessa forma, você pode garantir, por exemplo, que objetos do seu módulo Usuarios só sejam instanciados dentro dos limites do seu próprio pacote. E isso é só um dos exemplos.

Para deixar mais claro, quero dar uma “introspectada” no empacotamento de uma das APIs do Java: o java.util, responsável por prover, dentre outras, as implementações de coleções (famigerada Collections API). Se você reparar, não há divisões internas do pacote no que tange lists ou sets nem mesmo para a própria raízes das coleções, como as interfaces Collection e Map. Está tudo imediatamente abaixo de java.util:

java.util.packagingE aí, pra reforçar o que eu disse há pouco, a prória implementação da interface Entry da classe HashMap faz uso do package-private, que eu comentei há pouco:

static class Entry implements Map.Entry { ... }

Vantagens do empacotamento da perpectiva de features

Então resta enumerar as benesses de se empacotar seu software do prisma package-by-feature. Essas são só as que eu julguei legais de elencar aqui, as demais vou deixar como links no final do post.

Coesão

Vai te fazer pensar melhor sobre os limites do seu software – isso no DDD é chamado de Bounded Contexts – e acredite, vai diminuir a quantidade de artefatos que você terá que escrever. Na divisão por camadas, é muito comum que, ao codificar suas camadas em pacotes diferentes, você automaticamente comece a escrever artefatos desnecessários como por exemplo, de uma relação Usuario @OneMany Conta, você acabe escrevendo um UsuarioDAO e um ContaDAO. É um exemplo miserável, eu sei, mas já vi muito. Conta, de uma perpectiva de empacotamento por feature, poderia ser transparentemente e  automaticamente internalizada pelo módulo de usuários.

Navegação

No que tange a navegação, empacotar por feature também te deixa mais confortável para encontrar os artefatos que você deve alterar. É comum, por exemplo, que alterações nas entidades propaguem alterações por todas as outras camadas. Manter tudo junto vai aliviar seu estresse quando você tiver que sair procurando onde mais estão os fragmentos do seu módulo.

Testes

Eu duvido que você já não tenha se deparado com uma situação em que teve que modificar a visibilidade de algum aterfato só para conseguir testá-lo – eu conheço o dilema sobre “testar ou não testar privados”, mas acontece que você vai cedo ou tarde se deparar com isso . Então, empacotar por feature também vai te beneficiar nesse sentido, uma vez que ao invés de publicar os atributos ou comportamentos da sua classe, você pode simplesmente deixá-los privados ao nível do pacote.

Nomenclatura

Aumenta significativamente o escopo dos nomes que você pode empregar em seu código. Pense assim, uma vez que o pacote compõe o nome (unique name) das suas classes, você poderia simplificá-los:

  • com.acme.usuario.Usuario.java
  • com.acme.usuario.Dao.java
  • com.acme.usuario.Service.java
  • com.acme.usuario.Controller.java
  • com.acme.usuario.Exception.java

Sei que tem gente que vai infartar ao ler isso, mas não deixa de ser uma possibilidade.

E você, como empacota?

Fontes:

Java EE Exceptions

Em complemento ao meu post anterior sobre tratamento de exceções, quero neste post focar em Java EE Exceptions.

As exceções Java EE se dividem em duas categorias básicas: System Exceptions e Applicatipon Exceptions. A grosso modo, uma System Exception (SE) é qualquer exceção que estenda a classe java.lang.RuntimeException enquanto que Application Exception (AE) é qualquer derivada de java.lang.Exception. Cada uma possui um conjunto de peculiaridades que dizem respeito à forma com que elas serão capturadas, encapsuladas e eventualmente repassadas para o cliente.

Application Exceptions

Uma AE pode ser qualquer tipo de exceção que estenda a classe java.lang.Exception como o exemplo a seguir:

public class MyAppException extends Exception {}

Para lançá-la, o método deve explicitamente declarar que o faz, utilizando a cláusula throws.

public void someMethod() throws MyAppException {
    // ...
}

A principal característica desse tipo de exceção é que ela será entregue ao cliente exatamente como ela foi lançada e, em contrapartida, fará com que o cliente explicitamente utilize um bloco try-catch para executar a chamada do método.

System Exceptions

As SE são as exceções que derivam da classe java.lang.RuntimeException (RTE).

public MySysException extends RuntimeException {}

Por se tratar de uma RTE, o método que a lança não precisa explicitamente declarar que o faz e o cliente também não necessariamente precisa executar a chamada dentro de um bloco try-catch. Quando um método lança uma SE, o container deve tomar algumas precauções importantes em relação ao EJB que a lança: se for um session bean (stateless ou stateful) ou um MDB, a instância que a lançou é destruída e a transação subjacente (se houver) será revertida (rolled-back). Essa é a diferença principal entre AEs e SEs. Enquanto que as AE são entregues para o cliente em seu estado original e sem forçar com que o container tome quaisquer medidas em relação à instâncias ou transações, as SEs sim o fazem.

Ainda quanto as SEs, o container irá encapsular a exceção original (a que foi lançada) em um javax.ejb.EJBException antes de entregá-la para o cliente.

A anotação @ApplicationException

A anotação @javax.ejb.ApplicationException serve para misturar os dois mundos. Por exemplo: ao utilizá-la em uma exceção do tipo RTE, faz com que ela seja tratada com exceção de aplicação:

@ApplicationException
public MySysException extends RuntimeException {}

Desta forma, sempre que um método lançar uma MySysException, o container irá se comportar da mesma forma com que se comporta em relação as AE convencionais  subclasses diretas de Exception  entregando a exceção original para o cliente (sem encapsulá-la em EJBException) e não tomando qualquer medida no sentido de destruir instância do EJB que a lançou ou reverter a transação subjacente.

A anotação ainda especifica um atributo boolean denominado rollback. Este atributo serve para indicar ao container se a transação deve ou não ser revertida.

@ApplicationException(rollback=true)
public MyAEException extends RuntimeException {}

A qualquer momento que uma instância da exceção MyAEException for lançada, ela fará com que a transação envolvida seja revertida.

Resumidamente é isso.
Até o próximo.

 

 

 

 

Classes anônimas em Java

Classes anônimas são exatamente como as classes locais, contudo, sem um nome. Eu usei um exemplo de classe anônima no meu post anterior, falando sobre o pattern Decorator.

Para declará-las, você precisa usar o operador new e uma interface de referência, a qual a classe anônima vai implementar. A interface, neste caso, pode ser qualquer coisa extensível, ou seja, não-final: classes abstratas, concretas e interfaces puras.

Object anom = new Object() {
    @Override
    public String toString() {
        return "My hashCode is: " + super.hashCode();
    };

Você pode ainda adicionar um bloco de inicialização não-estático, que funcionaria como uma especie de construtor (sem argumentos, evidentemente):

Object anom = new Object() {

    private String text;

    {
       text = "My hashCode is: ";
    }

    @Override
    public String toString() {
        return text + super.hashCode();
    }
};

As classes anônimas são muito utilizadas no desenvolvimento de alguns padrões, entre eles o Decorator, já mencionado, o Command (que ainda pretendo explicar) e alguns outros. Um exemplo prático seria, por exemplo, suprimir o método add da implementação de uma lista:

List<String> list = new ArrayList<String>() {
@Override
    public boolean add(String item) {
        throw new UnsupportedOperationException("Not supported");
    }
};

Que lançaria uma UnsupportedOperationException toda vez que o método add fosse invocado no objeto list.

As classes anônimas também podem acessar variáveis do escopo mais próximo, por exemplo, as declaradas dentro do corpo de um método ou definidas como parâmetros, desde que elas sejam declaradas com o modificador final.

Quer um outro exemplo muito legal de classes anônimas (que particularmente acho que serviu de referências para os Lambdas do Java 8)? Dê uma olhada no Apache Commons Collections, principalmente no que diz respeito aos predicados (Predicate).

 

 

 

Decorators

Continuando os posts sobre padrões de projeto, vamos falar sobre composição, mais especificamente decoradores; e vamos começar pelo código! Imagine uma classe de saudação chamada Greetings:

public class Greetings {

   public static Greetings create() {
       return new Greetings();
   }

   public String sayHello(String to) {
        return "Hello " + to;
   }
}

Agora imagine que os objetos da classe Greetings são usados por todo seu código:

// código ...
Greetings.create().sayHello(loggedUser);
// mais código

Agora digamos que, hipoteticamente, em dado momento, alguém mudou os requisitos para que as classe tenha que retornar a saudação entre tags h1 e /h1 (e é claro que isso é hipotético).

Você poderia simplesmente sair alterando o seu código para concatenar a saudação retornada entre as tags HTML. Mas digamos que existam milhares de pontos onde você precisa alterar e você não quer sair alterando manualmente. De repente, você tem a idéia genial de usar o polimorfismo:

public class Greetings {
    public static Greetings create() {
        return new Greetings() {

            // NOTE: eu não sei por quê Satanás o WordPress quebra
            // a formatação desse código, mas você pode ver ele
            // bonitinho e formatado aqui:
            // @see https://pastebin.com/C2nFykjw
            @Override
            public String sayHello(String to) {

                return "
<h1>" + super.sayHello(to) + "</h1>
";

            }
         };
     }

     public String sayHello(String to) {
         return "Hello " + to;
     }
}

E é isso. Você alterou o método de criação da sua classe para retornar um objeto decorado. É claro que aqui usamos o artifício das classes anônimas do Java, mas a idéia é basicamente essa:

a classe decoradora estende a decorada com objetivo de acrescentar comportamentos

Você consegue identificar um strategy por aqui? Para o cliente (ou seja, quem usa) trata-se ainda de um objeto da classe Greetings, no entanto, nos bastidores, com o comportamento modificado por uma de suas subclasses.

Vamos aos conceitos…

A idéia por trás do padrão Decorator é a utilização do contrato (uma interface). Embora não tenhamos usado explicitamente uma, pelo conceito de herança/polimorfismo, a classe Greetings permite um ponto de extensão: um método público não-final. O cliente espera por um objeto da classe Greetings (ou qualquer uma das suas subclasses), desde que especifique um método sayHello com a assinatura especificada. O padrão decorator (se você clicar no link) também é conhecido como Wrapper e possui outra variação muito conhecida: o pattern Proxy.

Pretendo falar sobre o pattern proxy em outra ocasião. Por hora, dê uma lida sobre os Dynamic Proxies que escrevi no Linkedin.

Até o próximo.

Tratamento de exceções em Java

Todo o desenvolvedor de software já passou pela situação de ter que caçar um bug no código do projeto. Na grande maioria das vezes o problema inicia-se com a queixa de um usuário sobre o insucesso ao tentar utilizar uma funcionalidade qualquer. Geralmente ele liga para o suporte e reporta uma mensagem de erro. Outras vezes você tem um pouco mais de artefatos como screenshots e códigos de erros. Nesse tipo de situação, qualquer evidência pode te poupar de perder tempo (dinheiro) e de dores de cabeça. É exatamente por isso que venho estudando boas práticas no que tange o tratamento de exceções e erros há algum tempo. Um pequeno conjunto de diretrizes neste sentido pode te poupar de horas e horas de transtornos.

Em uma das minhas pesquisas, um dos melhores “guias” – vamos chamar assim – que encontrei foi uma resposta à uma questão do StackOverflow resumindo em 10 tópicos as boas práticas do tratamento de exceções. É a partir dessa resposta que estou escrevendo este post. A partir dela vou descambar para outros materiais que venho utilizando.

Tipos de exceções em Java

A estrutura de exceções em Java está definida da seguinte forma (se você não está familiarizado com as exceções em Java, dê uma lida neste post da Caelum):

Throwable, embora esteja nomeada segundo a orientação de se nomear interfaces (sufixo “able”) é na verdade uma classe concreta e permite que, além dela, suas especializações sejam lançadas (cláusula throw*) e capturadas (bloco try-catch) no seu código. Não vamos falar de Errors aqui, contudo, cabe ressaltar que eles são irreversíveis e quase sempre vão interromper a execução da aplicação.

Exceções verificadas

A classe java.lang.Exception é a raiz das exceções em Java e, por natureza, é uma exceção verificada (checked exception). Exceções verificadas são uma das característica do Java que faz com que o desenvolvedor tome algumas medidas em relação à exceção lançada: tratá-la (bloco try-catch) ou propagá-la (assinar o métodos com a cláusula throws**) para alguma outra rotina tratar. Todas as classes que derivam imediatamente de Exception são exceções verificadas, exceto RuntimeException e suas especializações.

Exceções não-verificadas

A classe java.lang.RuntimeException é uma derivada especial de Exception que tem como característica principal não ser verificada (unchecked exception). Exceções não-verificadas podem ser lançadas a qualquer momento e não necessariamente precisam ser tratadas ou propagadas.

Estrutura das exceções

Uma exceção é uma classe Java comum; com construtores, métodos e atributos. Se você não tem o source code do Java instalado, dê uma olhada no GrepCode. A classe, despida do JavaDoc, não passa de vinte e poucas linhas de código sobrescrevendo o próprio construtor:


package java.lang;

public class Exception extends Throwable {

    static final long serialVersionUID = -3387516993124229948L;

    public Exception() {
        super();
    }

    public Exception(String message) {
        super(message);
    }

    public Exception(String message, Throwable cause) {
        super(message, cause);
    }

    public Exception(Throwable cause) {
        super(cause);
    }
}

E para estendê-la é necessário nada mais do que:


public class MyOwnException extends Exception {}

Sim! Uma linha de código. A maior parte do comportamento de Exception está encapsulado na superclasse dela – Throwable – e serve basicamente para auxiliar o rastreamento da origem da exceção na pilha de chamadas de métodos (dê uma olhada em StrackTraceElement).

A grande maioria das extensões de Exception com as quais me deparei na minha vida de programador não faziam nada a mais do que isso. Um pecado! O simples fato de você adicionar algum comportamento extra em uma especialização da classe Exception, seja ela qual for, já seria uma mão na roda na sua leitura de stacktrace em busca de erros. Mas não vamos falar disso aqui. Deixa isso para outra ocasião.

Tratando exceções

Com base na resposta do Vineet Reynolds no StackOverflow, que divide o tratamento de exceções em 10 tópicos primordiais,  pretendo aqui implementá-lo com todo o resto que encontrei por aí, seja em livros, fóruns, vídeos e podcasts. Eu tomei a liberdade de modificá-los um pouco, às vezes trocando a ordem, outras removendo ou adicionando algum tópico de forma que faça mais sentido pra mim. Você, no entanto, está livre para (e deve) dar uma lida na resposta original.

Lance imediatamente

Assim que o código encontra uma situação adversa, é interessante lançar a exceção. Isso ajuda a rastrear a linha onde o erro foi ocasionado.

Encapsule a exceção original

Em complemento ao tópico anterior, se você vai lançar uma exceção nova, ao invés de propagar a que foi gerada, encapsule a original. Isso faz com que seja possível rastrear a pilha inteira de erros até se chegar à causa:


try {
    // código que gera a exception
} catch(Exception originalException) {
    throw new MyOwnException(“Erro ao tentar fazer qualquer coisa”,
        originalException);
}

log ^ throw

Você loga ou (XOR) você lança. Simples assim. Nunca faça os dois! Logar uma exceção relançá-la vai gerar pilhas imensas de log, vai dificultar a leitura do arquivo de saída e consequentemente a busca por origens de bugs.

try {
    // código que gera a exception
} catch(Exception ex) {
    LOG.warn(“Problema ao tentar…”);
    // qualquer coisa que você for fazer para tratar a exception.
    // menos relançá-la
}

 Ou


try {
    // código que gera a exception
} catch(Exception ex) {
    throw new MyOwnException(
        “Erro ao tentar fazer qualquer coisa”, ex);
}

Só capture exceções que você puder tratar

Parece óbvio, não? Se o código tenta abrir um arquivo e o arquivo não existe a pergunta é: devo tratar o IOException lançado? Sim:


public File openConfigFile() {
    try {
        // código que abre o arquivo
    } catch(IOException ex) {
        LOG.warn(“Não foi possível abrir o arquivo: “
            + ex.getMessage());
        return createNewFile();
    }
}

Alternativamente, você pode decidir que o código que está chamando o método openConfigFile deve tomar alguma medida em relação à exceção:


public File openConfigFile() throws IOException {
    // código que abre o arquivo
}

Ou simplesmente você pode decidir que não há nada a fazer:


public File openConfigFile() {
    try {
        // código que abre o arquivo
    } catch(IOException ex) {
        throw new RuntimeException(“Erro ao abrir arquivo”, ex);
    }
}

Opte por exceções da API sempre que puder

A linguagem Java já especifica uma gama de exceções que são usadas dentro das próprias APIs. É encorajado reutilizá-las ao invés de criar uma série de outras exceções dentro da sua própria aplicação. Por exemplo, um método pode receber uma String representando uma data e, antes de gravá-la no banco de dados, ele pode tentar convertê-la em um objeto java.util.Date. Para isso, foi definido que a data tem que estar no padrão ISO 8601: yyyy-mm-dd. O código poderia, por exemplo, especificar (criar) uma InvalidDateFormatException ou qualquer outra coisa parecida. Mas porque não simplesmente lançar uma:

throw new IllegalArgumentException(“Formato de data inválido”);

do pacote java.lang? IllegalArgumentException é uma exceção não-verificada, já que certamente não há o que fazer neste caso. Nada mais justo do que propagá-la para para as camadas mais acima, nas quais ela possa ser logada e eventualmente ter a mensagem de erro ao usuário final.

Lance exceções não-verificadas para erros programação

No caso anterior, com a data em formato inválido, não há o que ser feito. Uma alternativa seria utilizar a data atual (new Date()) dentro do bloco catch, mas isso vai sempre depender do domínio do seu problema. Entre elas está a própria IllegalArgumentException, mencionada no tópico anterior, e qualquer outra que estenda RuntimeException, como IllegalStateException para objetos em estados inválidos, UnsupportedOperationException para operações não implementadas ou métodos não mais suportados (removidos de uma API, por exemplo). Vale frisar que – seja lá qual for a ocasião – nunca lance um NullPointerException e se você não consegue entender o porquê, dá uma lida nisso aqui. Hehehe. Brincadeira =). NullPointerException é uma condição adversa muito especifica: foi invocada uma operação em uma referência de objeto que estava nula. Você consegue imaginar em que situações isso aconteceria? Pois bem:


if(obj == null) {
    throw new NullPointerException(“Objeto obj null”);
} else {
    obj.doSomething();
}

Se você não conseguir entender o que está errado no código acima, sugiro que você então dê uma olhada neste link. E desta vez não é brincadeira.

Lance exceções verificadas quando a exceção deve ser tratada por quem invocou

Eu sei que você questionou o exemplo anterior da data, por isso vou reiterá-lo aqui. Se, por qualquer razão, você espera que o cliente do seu método (o método que o está invocando) trate a situação adversa da data não estar no formato esperado, aí sim: cria uma exception verificada do tipo InvalidDateFormatException, como foi mencionado naquela seção e a lance. Neste caso, o cliente estará ciente de que se a data não for informada no formato esperado, ele terá que tomar alguma providência.

Uma exceção por módulo é mais que suficiente

Não que você não possa especificar suas exceções, mas faça isso com carinho. Se você tem um módulo de controle de clientes, por exemplo, crie uma CustomerModuleException. Fica mais fácil trafegar-la entre as várias camadas ou serviços da sua aplicação. Nada te impedirá de especializá-la internamente, criando subclasses como CustomerNotFoundException, InativeCustomerException ou qualquer outra coisa que possa representar uma condição intrínseca ao seu módulo. Mas dele pra fora, a CustomerModuleException é mais do que suficiente. Dê uma olhada na estrutura de EJBException e de PersistenceException.

Converta exceções verificadas em não-verificadas somente quando necessário

Em um dos exemplos da leitura de arquivos, nas seções anteriores, foi encapsulada uma IOException em uma RuntimeException para que ela pudesse “vazar” do método. Tal coportamento não deve ser deliberado. Considere esse tipo de “conversão” (de checked para unchecked) em situações em que a thread em execução deva ser abortada.

Não use Throwable.printStackTrace()

Algumas IDE’s costumam inserir um ex.printStackTrace() quando você opta por englobar um código em um bloco try-catch. Embora seja útil quando se trata de pequenas codificações para testes, é completamente desencorajado em códigos que vão para ambientes de produção. O método printStackTrace é definido na classe Throwable e herdado por todas as subclasses de Exception. Ele faz uso da API System.err que às vezes compartilha os recursos com o System.out (se estiverem sendo redirecionadas para o mesmo dispositivo/arquivo). Só isso já é uma boa razão para evitá-la. Considere o seguinte: uma vez que elas não fornecem qualquer alternativa thread-safe de acesso, o log gerado em uma situação concorrente seria completamente ilegível.

Use enums para mensagens

Eu gosto de exibir mensagens de erros significativas para o usuário. Exibir as mensagens de exceções é melhor ainda, desde que elas sejam compreensíveis. Utilizar enums nesse contexto pode tornar sua vida mais fácil; já que você está definindo uma mensagem source-level como enum e na ponta (front) você pode usar qualquer mecanismo de internacionalização para traduzí-la para o usuário final.

Voltando ao exemplo do CustomerModuleException, poderíamos especificar algo do tipo:

throw new CustomerModuleException(
    CustomerModuleExceptionMessages.EMAIL_ALREADY_TAKEN);

Ainda neste cenário, considere configurar atributos dinâmicos à sua exceção. Lembre-se, exceptions são classes Java serializáveis comuns e você pode facilmente permitir o comportamento abaixo com o emprego de um Map<String, Serializable>:

throw new CustomerModuleException(
    CustomerModuleExceptionMessages.EMAIL_ALREADY_TAKEN)
        .put("email", customer.getEmail())
        .put("username", customer.getUsername());

 

Use o método de logging adequado

Evite usar log.error() ou seus variantes para tudo. Existem situações em que o comportamento excepcional é esperado ou não vai causar qualquer prejuízo para o usuário final. Nestes casos, você pode considerar usar log.warning() ou até mesmo log.info(). Use log.error() para situações irrecuperáveis.

Considerações

Bem, é isso. Gostaria de convidar você à compartilhar suas considerações sobre exception handling aqui nos comentários e me ajude a melhorar o post.

“No mais” [sic], muito obrigado e até o próximo.