Desenvolver código claro e simples de manter não é algo fácil, requer prática, experiência e muita boa vontade para não ativar o modo "XGH" (eXtreme Go Horse) nem o "FDD" (Desenvolvimento Dirigido pela Fé). Para auxiliar o desenvolvimento de um código limpo e claro, compilei algumas dicas baseadas no Object Calisthenics de Jeff Bay, e de outros artigos de boas práticas. Obviamente, isso não é o suficiente pra se escrever um bom código, mas serve como um lembrete inicial, até mesmo, um ponto de partida.

Deixo claro aqui que as dicas são referentes a código Java, não por não ser possível aplicá-las em outras linguagens, mas sim, porque algumas delas podem gerar perda de performance em linguagens de script como javascript e PHP. A comunidade PHP inclusive, possui diversos artigos e discussões sobre calistenia, e já possuem de forma clara alguns itens que não devem ser praticados na linguagem.

Bom, seguindo em frente, as recomendações são:

1 - Usar o menor número de indentações possível

Métodos com grande número de indentações dificultam a leitura e tornam o código complexo desnecessariamente. Caso caia em uma situação parecida, utilize a ferramenta de "Extração de Método" existente em sua IDE. No livro "Refatoração para Padrões", o método "Compor Método" é proposto de forma a dividir um método cheio de atribuições em métodos mais concisos, como no exemplo abaixo:

Classe Original

public class List{

    private Object[] elements;
    private boolean readOnly;
    private int size;

    public void add(Object element) {
        if (!readOnly) {
            int newSize = size + 1;
            if (newSize > elements.length) {
                Object[] newElements = new Object[elements.length + 10];
                for (int i = 0; i < size; i++) {
                    newElements[i] = elements[i];
                }
                elements = newElements;
            }
            elements[size++] = element;
        }
    }
}

Classe Refatorada

public class NewList{

    private Object[] elements;
    private static final int GROWTH_INCREMENT = 5;
    private boolean readOnly;
    private int size;

    public void add(Object element) {
        if (readOnly) return;
        if (isTheListAtCapacity()){
            growTheList();
        }
        addElement(element);
    }

    private boolean isTheListAtCapacity(){
        return (size + 1) > elements.length;
    }

    private void growTheList(){
        Object[] newElements = new Object[elements.length + GROWTH_INCREMENT];
        for (int i = 0; i < size; i++){
            newElements[i] = elements[i];
        }
        elements = newElements;
    }

    private void addElement(Object element) {
        elements[size++] = element;
    }
}

2 - Cada método deve ser responsável por uma única ação

Assim como no exemplo de refatoração acima, cada método deve ser responsável por apenas uma atividade, isso evita complexidade demasiada. Importante frisar que "uma atividade" não é a mesma coisa que "uma operação": um método pode fazer diversas operações para atingir um objetivo, e esse é o ponto: o método deve ter apenas um objetivo!

3 - Faça classes pequenas

Se suas classes estão grandes, isso quer dizer que as mesmas possuem muitas atribuições. Se esforce para contextualizar melhor seus métodos e divida esse excesso de responsabilidade em classes menores, além de melhorar a coesão de suas classes, isso irá melhorar o reaproveitamento de código.

4 - Injete dependências por construtor

Não é porque é usado um framework de gestão de dependências que você pode sempre contar com ele. Não dependa de injeções por reflection em atributos, já que isso dificulta o uso de testes e aumenta o acoplamento entre a aplicação e o container de injeção. Além disso, a injeção por construtor funciona como um detector de “mal cheiro” em seu design, e mostra claramente o excesso de dependências usadas em sua classe.

5 - Mantenha o número de dependências baixo

Se sua classe depende de muitas classes, isso quer dizer que o design das mesmas não está coeso, e que existem classes com excesso ou falta de responsabilidade. Procure manter o número de dependências abaixo de três (Porém, você não precisa se enforcar se em um caso específico você der uma escapulida xD).

6 - Não abrevie!!

Abreviaturas não melhoram o desempenho da aplicação e dificultam o entendimento de quem dá manutenção ao código. Use nomes descritivos e claros para variáveis e funções.

7 - Encapsule tipos primitivos e Strings

Trabalhar diretamente com primitivos dificulta a expansão do código, reduz clareza e aumenta os riscos de erros em passagem de parâmetros. Leve em consideração o exemplo abaixo:

public void defineUserOnDepartment(int user, int department){ ... }

Um método aparentemente inofensivo, mas um olhar mais atento aponta que o risco dos parâmetros serem invertidos é considerável. Dependendo dos valores invertidos, o código funcionaria por muito tempo até que o erro fosse identificado, provavelmente só sendo descoberto depois de algum tempo no ambiente de produção!

Agora com os dados encapsulados, você teria algo como:

public void defineUserOnDepartment(User user, Department department){ ... }

Veja que não é mais possível inverter os valores. Além disso, mesmo que as classes User e Department só contenham a própria identificação internamente, o método se torna mais claro, e permite uma futura expansão dos objetos caso seja necessário atender novas regras no método (executar alguma tarefa com o nome do usuário, por exemplo).

8 - Não envolva grandes blocos de código dentro de "IF"

Ao invés de fazer um método como:

public void doSomething(Object value){

        if(mustDoSomethingWith(value)){
                …
                Toda a lógica aqui
                …
        }
}

Faça uma ação de cancelamento de execução, como:

public void doSomething(Object value){

        if(mustNOTDoSomethingWith(value)) return;
        …
        Toda a lógica aqui
        …
}

Isso simplifica a leitura do método.

9 - "IF"s SEMPRE devem ter chaves

A não ser que seja um "if" de cancelamento como o citado no item anterior, todos os "if"s devem utilizar chaves para tornar a leitura mais clara.

"Ah, mas o desenvolvedor tem que saber usar indentação e deixar claro if's sem chaves ò.ó ...."

Bom, claro cara pálida! Mas a abertura para dor de cabeça vale a pena? Java não é uma linguagem que depende da indentação, logo deslizes podem acontecer e a leitura pode ser prejudicada. Por exemplo:

public void metodoQualquer(){

    if(deveExecutarUmaAcao)
        executeAcaoA();
        executeAcaoB();
        executeAcaoC();
}

Você como programador "profissa", sabe que apenas a "ação A" vai ser executada dentro do if, e todas as outras ações serão SEMPRE executadas. Mas pense bem, você realmente achou isso claro?

10 - Evite retornar null em métodos

Na maior parte dos casos, o retorno do null indica um erro. Nesses casos o retorno de uma exceção seria uma melhor alternativa já que evitaria erros silenciosos ou inesperados. Nos casos em que "null" não é um erro, é possível trabalhar a semântica dos métodos, deixando claro para quem usar seu código que o retorno daquele valor é opcional. Isso é possível através da classe Optional do Google Guava em versões mais antigas do Java, ou através do Optional nativo do Java 8. Outra opção é o padrão NullObject, que retorna objetos "sem efeito" no lugar de null.

11 - Passe poucos parâmetros em métodos

Se seu método recebe mais do que três parâmetros, provavelmente seu método faz ações a mais do que deveria, ou os dados que você está passando deveriam estar presentes em um único objeto. Avalie o caso e refatore conforme a necessidade. NÃO DEIXE PARA DEPOIS.

12 - Comentários sim. Código comentado não.

Utilizamos tecnologias de SCM (git, svn, etc) para mantermos histórico de código. Não realize commits com código comentado! Remova o trecho de código, e caso seja necessário, recupere o código do controle de versionamento. Comentários desse tipo atrapalham a compreensão do código, e acabam virando parte permanente do mesmo, já que o trecho comentado normalmente é esquecido.

13 - Faça code-review

A revisão de código parece algo massante e burocrático, no entanto é a melhor ferramenta para nivelar a forma como o time codifica. Pelo code-review é possível identificar dificuldades de desenvolvedores mais inexperientes, avaliando erros recorrentes e facilitando um melhor direcionamento sobre os mesmos.


Para boa parte dos desenvolvedores, nada aqui é novidade. No entanto, mesmo conhecendo essas dicas, alguns desenvolvedores têm dificuldades em segui-las. Se esse for seu caso, que tal uma nova tentativa?

Espero que gostem do artigo! Nos vemos no próximo. =]