Diretriz: Refatoração
Relacionamentos
Elementos Relacionados
Descrição Principal

Tópicos

O que é refatoração?

A refatoração é o ato de melhorar a estrutura de um programa sem alterar o seu comportamento. A refatoração é feita em poucos pequenos passos, cada um valendo o que faz. Entre cada passo, executamos os testes de unidade relevantes para certificar que as mudanças que fizemos não prejudicaram nada. O ciclo de edição, compilação e testes dura normalmente de 30 segundos a cinco minutos.

Por que devo refatorar?

A finalidade da refatoração é melhorar o design e a legibilidade do código. Existem várias metas específicas:

  • O código deve passar em todos os seus testes.
  • Ele deve ser o mais expressivo possível que você possa fazer.
  • Ele deve ser o mais simples possível que você possa fazer.
  • Ele não deve ter nenhuma redundância.

Quando devo refatorar?

A refatoração não é algo que possamos agendar. Não existe nenhuma entrada no cronograma para ela. Não existe nenhum momento especial para fazê-la. A refatoração é feita o tempo todo. Quando você e seu parceiro estiverem trabalhando em uma tarefa, tal como escrever testes e código, vocês notarão que o código e os testes não estão tão limpos e simples quanto poderiam estar. Esta é a hora de parar e refatorar o código.

A regra é: Não deixe que o sol se ponha com código ruim.

Um exemplo de refatoração

Considere os dois testes de unidade e a classe Formatter abaixo. A classe Formatter funciona, mas não é tão expressiva como eu gostaria que fosse. Então eu vou refatorá-la em etapas.


    public void testCenterLine() {
        Formatter f = new Formatter();
        f.setLineWidth(10);
        assertEquals(" word ", f.center("word"));
    }

    public void testOddCenterLine() throws Exception {
        Formatter f = new Formatter();
        f.setLineWidth(10);
        assertEquals(" hello ", f.center("hello"));
    }




    import java.util.Arrays;
    public class Formatter {
        private int width;
        private char spaces[];

        public void setLineWidth(int width) {
            this.width = width;
            spaces = new char[width];
            Arrays.fill(spaces, ' ');
        }

        public String center(String line) {
            int remainder = 0;
            StringBuffer b = new StringBuffer();
            int padding = (width - line.length()) / 2;
            remainder = line.length() % 2;
            b.append(spaces, 0, padding);
            b.append(line);
            b.append(spaces, 0, padding + remainder);
            return b.toString();
        }

    }

A função setLineWidth está um pouco misteriosa. O que é este array spaces e porque é preenchido com espaços em branco? Olhando adiante, a função center, vemos que o array spaces é apenas uma conveniência para nos permitir mover uma série de espaços em branco para um StringBuffer. Questiono se precisamos realmente deste array de conveniência.

Por enquanto, eu irei inicializar o array com sua própria função denominada buildArrayOfSpaces. Dessa forma, tudo está em um só lugar, e posso pensar sobre isso com um pouco mais de clareza.


        public void setLineWidth(int width) {
            this.width = width;
            buildArrayOfSpaces(width);
        }

        private void buildArrayOfSpaces(int width) {
            spaces = new char[width];
            Arrays.fill(spaces, ' ');
        }

    Executo os testes: os testes passam

Eu não gosto da forma como a função center foi construída. Existe matemática espalhada por toda parte. Creio que podemos reorganizar a matemática para tornar as coisas mais claras.


        public String center(String line) {
            int remainder = line.length() % 2;
            int numberOfBlanksInFront = (width - line.length()) / 2;
            int numberOfBlanksAtEnd = (width - line.length()) / 2 + remainder;
            StringBuffer b = new StringBuffer();
            b.append(spaces, 0, numberOfBlanksInFront);
            b.append(line);
            b.append(spaces, 0, numberOfBlanksAtEnd);
            return b.toString();
        }

    Executo os testes: os testes passam

Agora parece melhor, mas podemos reduzir a desordem, transformando algumas variáveis em funções.


        public String center(String line) {
            StringBuffer b = new StringBuffer();
            b.append(spaces, 0, numberOfBlanksInFront(line));
            b.append(line);
            b.append(spaces, 0, numberOfBlanksBehind(line));
            return b.toString();
        }

        private int numberOfBlanksBehind(String line) {
            int extraBlankIfOdd = line.length() % 2;
            return (width - line.length()) / 2 + extraBlankIfOdd;
            }

        private int numberOfBlanksInFront(String line) {
            return (width - line.length()) / 2;
        }

    Executo os testes: os testes passam

Isto torna a leitura da função center um pouco melhor. Entretanto, o uso da função StringBuffer.append está um pouco confuso. Podemos melhorá-la um pouco, criando uma função mais explícita.


        public String center(String line) {
            StringBuffer b = new StringBuffer();
            appendBlanks(b, numberOfBlanksInFront(line));
            b.append(line);
            appendBlanks(b, numberOfBlanksBehind(line));
            return b.toString();
        }

        private void appendBlanks(StringBuffer b, int numberOfBlanks) {
            b.append(spaces, 0, numberOfBlanks);
        }

    Executo os testes: os testes passam

Agora, podemos rescrever appendBlanks para evitar usar o array spaces.


    import java.util.Arrays;
    public class Formatter {
        private int width;

        public void setLineWidth(int width) {
            this.width = width;
        }

        public String center(String line) {
            StringBuffer b = new StringBuffer();
            appendBlanks(b, numberOfBlanksInFront(line));
            b.append(line);
            appendBlanks(b, numberOfBlanksBehind(line));
            return b.toString();
        }

        private void appendBlanks(StringBuffer b, int numberOfBlanks) {
            while(numberOfBlanks-- > 0)
            b.append(' ');
        }

        private int numberOfBlanksBehind(String line) {
            int extraBlankIfOdd = line.length() % 2;
            return (width - line.length()) / 2 + extraBlankIfOdd;
        }

        private int numberOfBlanksInFront(String line) {
            return (width - line.length()) / 2; }
        }

    }

    Executo os testes: os testes passam

Os nomes de funções tais como numberOfBlanksBehind implicam que o leitor saiba que elas serão chamadas a partir da função center. Devemos eliminar essa implicação renomeando essas funções.


    import java.util.Arrays;
    public class Formatter {
        private int width;

        public void setLineWidth(int width) {
            this.width = width;
        }

        public String center(String line) {
            StringBuffer b = new StringBuffer();
            appendBlanks(b, numberOfBlanksToLeftOfCenter(line));
            b.append(line);
            appendBlanks(b, numberOfBlanksToRightOfCenter(line));
            return b.toString();
        }

        private void appendBlanks(StringBuffer b, int numberOfBlanks) {
            while(numberOfBlanks-- > 0)
            b.append(' ');
        }

        private int numberOfBlanksToRightOfCenter(String line) {
            int extraBlankIfOdd = line.length() % 2;
            return (width - line.length()) / 2 + extraBlankIfOdd;
        }

        private int numberOfBlanksToLeftOfCenter(String line) {
            return (width - line.length()) / 2;
        }

    }

    Executo os testes: os testes passam

E acho que acabamos. Você poderá fazer outros tipos de refatorações, ou talvez não concorde com todas as refatorações que eu fiz. Isso é esperado. A questão é, contudo, que eu fiz um grande esforço para melhorar a legibilidade e a simplicidade desta classe. Este esforço irá ajudar as outras pessoas a compreender esta classe e facilitará a mudança quando chegar a hora.