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:

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.

 

Tópicos relevantes do livro Código Limpo: Clean Code A Handbook of Agile Software Craftsmanship

O livro Código Limpo (Clean Code – A Handbook of Agile Software Craftsmanship) é basicamente um atalho pra você aprender as boas práticas dos grandes programadores poupando dores de cabeça e, principalmente, tempo e dinheiro. Eu mesmo, durante minha carreira relativamente curta de desenvolvimento de software já colecionava uma série de mandingas, cacoetes e práticas que poderiam ser (algumas) consideradas boas e outras eufêmicamente “teimosas”.

Basicamente, quando leio um livro, gosto de ter à mão um lápis e uma caneta de grifar para poder destacar e anotar o que julgo importante. Também tenho um Kindle™ que me permite fazer isso de forma “ecologicamente correta”. Entretanto, desta vez o livro não era meu, então, tive que anotar minhas considerações em blocos de papel e, posteriormente, no documento que aqui compartilho convosco. Todavia, antes de começar, recomendo a todos que leiam o livro na íntegra, pois realmente vale cada um dos 60 reais que o meu brother William Lopes (conhecido pelas alcunhas Maica, Capi, Non, entre outros) pagou (acabei de consultar a Amazon.com e vi que está custando 170 hoje: http://www.amazon.com.br/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882).

A organização desse post segue-se mais ou menos assim: o título é o capítulo de onde retirei a informação e os tópicos são as práticas que considerei relevantes seguidas de algum comentário (in-) oportuno meu. Também vale a pena frisar que há muito mais no livro do que eu aqui vos transcrevo, no entanto, anotei os pontos que mais chamaram a atenção pra minha insignificante vida de programador.

Nomes

  • Nomes significativos, independente do tamanho, o nome deve expressar o que a variável/método/classe se propõe a fazer. É melhor um nome do tipo somarOTotalPagoAFornecedores do que um método chamado somar e um comentário gigantesco, perecível e ignorável dizendo o que o dissilábico método faz. E é isso que a anotação seguinte expressa:
  • Melhor um nome extenso e significativo do que um nome curto e confuso. Isso quer dizer que você não deve se preocupar em economizar caracteres para descrever o comportamento do seu método enquanto o nomeia. No entanto, cautela e bom-senso devem sempre predominar nas escolhas.
  • Nem sempre é (ou não é) possível começar pelo ótimo, o ideal é fazer um brainstorming inicial e refatorar usando-se as ferramentas (IDE). Em outras palavras, você “vomita” o código funcional e depois faz o refactoring. Tentar começar pelo “código limpo” em muitas ocasiões me custou muito tempo, simplesmente pela minha vaidade de não colocar uns ifs dentro dos outros.
  • O código deve contar uma história. Quem vai lê-lo deve entendê-lo na integra, sem auxílio de comentários e documentações. Aqui a dica vai além da nomenclatura dos artefatos. “Contar uma história facilmente compreensível” é basicamente a dica do livro.
  • Os nomes devem ser refatorados com o tempo. Uma classe/função/método/variável pode perder o significado original, portanto, é sempre importante manter a nomenclatura em acordo com a intenção real e atual do método. Aqui cabe ressaltar o trabalho extra que os comentários geram. Eu, particularmente, até ler o livro, era um cara que gostava de enfeitar as classes com belos textos, às vezes até rebuscados, detalhando o funcionamento das minhas rotinas. Percebi (leia-se: fui convencido) mais tarde que esses comentários só serviam para ficar desatualizados.
  • Nomes pronunciáveis são sempre melhores que nomes acrônomicos. AgentConsoleLoginRequest é melhor que ACLRequest. Obvio, não?.
  • Classes → substantivos, métodos → verbos. Básico de qualquer curso de programação; classe define coisas e, os métodos, como essas coisas se comportam.
  • Nomes devem ser significativos dentro do contexto (domínio) do software. A palavra “nota” pode ter significados diferentes em um sistema de vendas, um acadêmico e um sistema de anotações pessoais, por isso é sempre bom estar de acordo com o vocabulário do seu cliente. Vai por mim, já passei muita raiva porque o cliente mencionava uma funcionalidade ou artefato usando uma palavra e no software estar representada por outra. Usar um vocabulário comum é essencial.

Funções

  • Devem ser pequenas! 5 ou 10 linhas é mais que o necessário. É isso! O Joshua Kerievsky fala no seu livro “Refatoração para Padrões” que a média de linhas dos métodos do seu sistema não devem ser maior do que 5 (“Your methods can be no longer than five lines of code”).
  • Devem fazer somente o que se propõem a fazer.
  • Devem fazer somente 1 tarefa. A 2 e a 3 se complementam. Um método denominado somar(a, b) deve somar a e b (ponto). Não tem que incrementar outras variáveis, não tem que validar se o usuário pode somar a com b, não tem redirecionar para outra página, não tem que fazer mais nada. Tem que somar a com b! Outra dica interessante é que se você usar “e” ou “ou” na nomenclatura do seu método tem grandes chances dele estar acumulando responsabilidades; na dúvida: refatore.
  • Quanto menos parâmetros, melhor: zero parâmetros: o ideal; 1 parâmetro: bom; 2 parâmetros: aceitável; 3 parâmetros: ruim; 4 parâmetros: melhor repensar;
  • Exceções são melhores do que códigos de erro. Concordamos que não devemos exibir um UnauthorizedException, na tela do usuário, mas ainda assim é melhor do que exibir um “error 403”.
  • Uma função que trata códigos de erro não deve fazer mais nada além de tratar os códigos de erro. Basicamente as anotações 1, 2 e 3 juntas.

Comentários

Essa foi pra mim a parte mais dolorosa: parar de comentar. Quando eu aprendi a fazer meu primeiro if no saudoso Clipper 5, mais ou menos no verão de 1996, comentários eram o que diferenciavam o bom programador do ruim; o cara organizado do porco relaxado. Acabei carregando esse hábito comigo para as outras linguagens: C, C++, PHP e mais tarde pro Java. Até pouco tempo atrás eu ficava bravo se alguém me falasse que comentar era ruim.

  • Comentários são ruins. Isso é um fato, aceite.
  • Javadoc é aceitável, mas também é ruim. Geralmente são gerados pelas IDEs aqueles cabeçalhos de métodos entre “/**” e “*/” com as meta-variáveis @param, @see, @author, etc. Isso é de certa forma, segundo o autor, aceitável, porém desencorajado. Você acaba refatorando o método e se esquece de alterar a quantidade de parâmetros, tipos, etc; é justamente isso que diz a a notação seguinte:
  • O programador acaba alterando a função mas não a documentação.
  • A regra básica é: sempre que precisar comentar, refatore. Resumidamente: “se você se sentir tentado a colocar um comentário para explicar seu código, é porque ele não está compreensível” (coloquei entre aspas porque não me lembro quem disse isso).
  • Um tipo de comentário válido é aquele que expressa algo que não pode ficar claro no código: uma decisão de negócio, por exemplo:
// desculpe, me obrigaram a fazer isso

Formatação

  • Melhora a legibilidade.
  • Deve ser empregada por toda a equipe de maneira consensual. “Ao ler um código, não ser possível identificar quem o escreveu”. Todos devem praticar de forma igual. Eu costumo usar o padrão da IDE para evitar entrar numa luta recursiva toda vez que alguém usar o recurso de auto-formação antes de commitar uma classe.
  • Espaçamentos devem ser padronizados. Entre métodos, entre declarações de variáveis, entre parênteses, entre chaves, enfim, reúna a equipe e entre em um acordo sobre o que deverá ser empregado como padrão de espaçamento, redija um documento e disponibilize à todos.
  • Separar contextos dentro da classe. Declaração de variáveis estáticas privadas de públicas; declaração de variáveis dos métodos e por aí vai. No meu caso, uso somente uma quebra de linha simples para separar os contextos, sem traços, pontos ou aquelas horrorosas série de sinais de igualdade (ex: “=======”).
  • Variáveis devem ser declaradas próximo de onde serão usadas (dentro do método). Eu aprendi a declarar as variáveis nos inícios dos métodos, quando comecei a programar em C. Não tenho certeza se isso era um requisito da linguagem, mas sei que me acostumei a fazer isso e acabou sendo outra mania que migrei para as demais linguagens. Não o faça! Declare suas variáveis próximas aos pontos onde você irá utilizá-las.
  • Métodos devem ser posicionados imediatamente abaixo dos métodos que os chamam. Se um método A chama um método B, posicione o método B imediatamente abaixo do método A. Isso facilita a leitura de quem pegar seu código no futuro. Geralmente as ferramentas de refactoring já fazem isso automaticamente quando você vai extrair um bloco de código para um método.
  • Linhas devem ser quebradas em qualquer posição entre 100 e 120 caracteres (eu uso 128… porque sim). Tanto no Netbeans quanto no Eclipse, eu coloco a margem da direita na posição 128 e NUNCA, repito, NUNCA deixo meu código ultrapassá-la. Acho que isso é uma prática que veio das telas 80×24 (tipo Clipper) que a galera usava para programar antigamente; 80 colunas por 24 linhas. Assim, para uma melhor visualização do código, os programadores recomendavam não ultrapassar a coluna 80 para não gerar rolagem horizontal. Hoje, com as telas alta resolução isso não se faz mais necessário, mas o autor acha importante quebrar a linha em algum ponto entre o começo e o fim do seu “range” visual.
  • Aninhamento horizontal são ruins porque geralmente as IDEs não os respeitam. Aquelas declarações de variáveis, enums e qualquer outra coisa que você tenta alinhar em colunas, colocando os sinais de igual exatamente um abaixo do outro, não funcionam. O primeiro aloprado que usar a formatação automática vai destruir em milésimos de segundos os minutos preciosos que você gastou tentando alinhá-las. Eu adotei uma regra simples: não brigue com a IDE.

Estrutura de dados

  • Um objeto não deve expor seus atributos internos. A primeira ideia que vem a mente ao ler isso é gerar métodos get e set para todos os atributos, o que é de certa forma válido, mas ainda devemos considerar a observação seguinte:
  • Getters e setters acabam quebrando o encapsulamento quando indiscriminadamente usados. A lição aqui é ser cauteloso no que sua classe vai expor. Talvez não haja a necessidade da sua classe externar atributos como “precoCusto” e “margemDeLucro” quando você pode simplesmente expor um método que retorna o preço final (eu sei que foi um exemplo medíocre, desculpa). Lei de Demeter: o módulo não deve enxergar o interior do objeto que manipula. Uma função f de uma classe c só deve chamar: métodos de c; objetos usados por f; objetos passados por parâmetro para f; variáveis de instância de c. Esse assunto por si só, rende um post inteiro, portanto, se quiser se aprofundar mais, dá uma olhada nesse post aqui.
  • Train Wrecks (acidentes ferroviários) ocorrem quando há encadeamento de chamada de métodos. Quando há encadeamento, melhor separá-los em variáveis locais conhecidas:
a.getB().getC();

deve ficar:

B b = a.getB();
C c = b.getC();

No entanto, o ideal é que a classe A retorne o objeto esperado a partir de um método adequadamente nomeado e especificado (comportamento estaria em a):

a.getQualquerCoisaQueBFazComC();
  • Objetos DTO não devem possuir comportamentos. São burros, ou como o pessoal gosta de dizer: anêmicos.

Exceções e erros

  • Sempre preferir exceções ao invés de retornar códigos de erro. Como foi mencionado em uma nota anterior, é melhor lançar uma UnauthorizedException do que um “Error 403”.
  • Sempre optar por exceções não verificadas. Exceções verificadas propagam alterações para níveis que não deveriam, por exemplo: um método que passa a lançar uma nova exceção verificada deverá alterar todas as estruturas de outros níveis que o usa.
  • Contextualize as exceções. Geralmente um tipo de exceção por módulo é mais do que o suficiente.
  • Nunca retorne null. Retornar null implica em verificação if-null por todo o código, sem contar as atormentadoras NullPointerExceptions que fazem qualquer programador se sentir um newbie.
  • Null deve ser encarado como uma condição anormal. Você deve considerar que um null em qualquer ponto do código é algo anormal e deve ser verificado.
  • Nunca passar ou receber null como parâmetro.

Classes

  • Devem ser pequenas. Pode ser difícil definir “pequena” aqui, mas considere a dica do Joshua Kerievsky no “Refatoração para Padrões” sobre o tamanho médio dos métodos para mensurar suas classes.
  • Possuir um único propósito e ter a responsabilidade bem definida (coesão). Uma dica: geralmente quando a descrição (Javadoc, por exemplo) de uma classe usa “e” para descrevê-la, significa que ela possui mais de uma responsabilidade.
  • Escolher um bom nome pode ser a forma exata de separar adequadamente as responsabilidades.
  • Recomenda-se começar declarando variáveis públicas e estáticas (constantes) seguidas pelas privada e estáticas, seguidas pelas, seguidas pelas variáveis de instância privadas.

E é isso. As práticas acima, como eu disse, são só um amontoado de anotações que eu considerei importantes para usar no meu dia a dia enquanto eu lia; nem sequer abrange o conteúdo todo do livro. Me arrependi muito de ter negligenciado tal material por tanto tempo e se há um livro de TI que eu recomendaria para alguém que está iniciando sua carreira, não tenho dúvidas que seria esse.