Tag Archives: refatoração

Como começar a refatorar seu legado em PHP/ASP/JSP?

TL;DR: O primeiro passo na refatoração de arquivos de script é mover todo o código de acesso a banco de dados, regras de negócio existentes e fluxo para o topo do arquivo. Para isso, crie as variáveis que serão usados na parte de baixo. Em seguida, o HTML que contém código PHP apenas para regras de visualização.

Não é difícil encontrarmos aqueles arquivos de script imensos, em PHP, ASP ou JSP, misturando um pouco de tudo: HTML, lógica, acesso a dados, mais HTML, mais lógica, etc:

A pergunta é: Como começar a refatorar esse código? Muitos anos atrás, tive uma experiência bastante interessante com um sistema enorme escrito só em JSP. O primeiro passo que costumava fazer é fazer com que todo código JSP ficasse no começo do arquivo. Assim, toda lógica de negócio, acesso a banco de dados ficaria agrupado. O resto da página com HTML conteria scripts apenas para alguma lógica de visualização.

Veja o código anterior refatorado. Tanto rs quanto rs2 foram para cima. Criei a variável total também para deixar o código um pouco melhor.

Porque fiz isso? Para começar a separar as responsabilidades desse código. Colocar todo “controle de fluxo” em um único lugar, e regras de visualização em outro. Mesmo que estando no mesmo arquivo, a separação já é visível e facilita a manutenção. Sim, no fim, estou tentando separar o M, do V, do C.

Aqui, começamos a separar o C do V. O M (modelo) não existe nesse código! O próximo passo seria começar a criar classes de domínio e lidar com elas, ao invés de manipular diretamente o array que as funções de acesso a banco geralmente devolvem. Mas isso é assunto para um próximo post!

PS: rs e rs2 são péssimos nomes. Você, claro, escolha nomes que tenham a ver com o seu domínio.

Variáveis de explicação melhoram o código?

É impressionante nossa capacidade de escrever “ifs” complicados ou com condições malucas. Veja, por exemplo, o if que escrevi no fim de semana. Consegue me dizer o que ele faz em 5 segundos?


if(!m.wasDeleted() && m.getFileName().toLowerCase().endsWith(".java") && m.hasDiff()) {
// ...
}

Uma simples maneira de refatorá-lo é extraindo partes da condição para variáveis com nomes significativos, que explicam melhor o que aquela condição significa. Por exemplo:


boolean naoDeletado = !m.wasDeleted();
boolean ehJava = m.getFileName().toLowerCase().endsWith(".java");
boolean temDiff = m.hasDiff();

if(naoDeletado && ehJava && temDiff) {
// ...
}

Perceba como é muito fácil agora ler a condição. Afinal, “as variáveis explicam”. Essa refatoração é conhecida por “Introducing Explaining Variable”, e você pode vê-la no famoso livro de Refatoração do Martin Fowler, ou mesmo nos atalhos da sua IDE favorita de desenvolvimento.

Li um artigo sobre esse tipo de refatoração há pouco tempo [1]. Nele, os autores mostram que desenvolvedores costumam fazer essa refatoração justamente em classes que já apresentaram muitos defeitos no passado. Para tal, ele observou refatorações feitas em 5 diferentes releases do projeto Eclipse.

O artigo tem lá seus viéses. O autor separa classes que receberam essa refatoração, e classes que não receberam essa refatoração, e mostra a média e a mediana da propensão das classes terem defeitos. Apesar da média ser realmente diferente, a mediana é igual em alguns casos. E, claro, em distribuições como essas, a mediana faz muito mais sentido. Ou seja, os resultados dele parecem ser mais fracos do que o que eles argumentam. Um ponto positivo é que o autor preocupou-se em mostrar que as classes tinham os mesmos tamanhos (afinal, classes maiores são mais propensas a terem bugs).

A pergunta que o artigo nos levanta é: Por que os desenvolvedores resolveram aplicar essa refatoração, justamente em classes problemáticas? Será que é por que essa refatoração realmente deixa o código mais claro de ler e, por consequência, menos suscetível a defeitos? Meu coração diz que sim.

Eu, particularmente, costumo sempre aplicar esse tipo de refatoração em ifs complicados como esse. Aliás, sempre que extraio variáveis de explicação, penso se esse código não deveria estar dentro da classe de origem.

A condição da variável ehJava, por exemplo, poderia estar dentro da classe Modification (que é o tipo da variável m). Dessa forma, fica fácil reusar a condição, e fácil de ler:


boolean ehJava = m.isJava();

[1] S. Counsell, X. Liu, S. Swift, J. Buckley, M. English, S. Herold, S. Eldh, and A. Ermedahl. 2015. An exploration of the ‘introduce explaining variable’ refactoring. In Scientific Workshop Proceedings of the XP2015 (XP ’15 workshops). ACM, New York, NY, USA, , Article 9 , 5 pages. DOI=10.1145/2764979.2764988 http://doi.acm.org/10.1145/2764979.2764988

Cuidado com seus baby steps!

TDD sugere que o programador ande sempre em passos de bebê (os famosos baby steps): ele deve escrever testes sempre para a menor funcionalidade possível, escrever o código mais simples que faça o teste passar e fazer sempre apenas uma refatoração por vez.

Às vezes vejo pessoas interpretando errado o “escrever o código mais simples que faça o teste passar”. Na opinião delas, um if a mais é o código mais simples que ela pode escrever. E de repente, você tem um código cheio de if, praticamente ilegível, e só a partir daí é que começam as refatorações.

A ideia de escrever o código mais simples é justamente fazer com que o programador evite criar complexidade desnecessária. E na minha opinião, evitar na verdade complexidade de design (sabe aquela história de você criar uma façade que chama uma factory que cria um strategy que invoca um command, e por aí vai? Ou criar uma classe acoplada com outras 10 porque você acha que ela vai precisar?).
Se você for implementar uma soma (péssimo exemplo?), e escrever:

public class CalculadoraTest {
  @Test
  public void DoisMaisDoisEhQuatro() {
    assertEquals(4, new Calculadora().soma(2,2));
  }
}

public class Calculadora {
  public int soma(int a, int b) {
    return 2;
  }
}

E logo em seguida…

public class CalculadoraTest {
  ...

  @Test
  public void TresMaisDoisEhCinco() {
    assertEquals(5, new Calculadora().soma(3,2));
  }
}

public class Calculadora {
  public int soma(int a, int b) {
    if(a==3) return 5;
    return 2;
  }
}

E depois…

public class CalculadoraTest {
  ...

  @Test
  public void QuatroMaisDoisEhSeis() {
    assertEquals(6, new Calculadora().soma(4,2));
  }
}

public class Calculadora {
  public int soma(int a, int b) {
    if(a==3) return 5;
    if(a==4) return 6;
    return 2;
  }
}

para só então chegar no código:

public class Calculadora {
  public int soma(int a, int b) {
    return a + b;
  }
}

… você não entendeu muito bem o objetivo de escrever o código mais simples que faça o teste passar!

No livro do Kent Beck [1], ele mostra o exemplo da classe Money, e ele faz os passos mais simples que fazem o teste passar. Passos realmente simplórios, parecidos com os do exemplo acima, que qualquer programador poderia considerar desnecessários. Mas logo em seguida, ele faz a seguinte afirmação: “Se eu faço assim o tempo todo? Claro que não. Mas eu fico feliz em saber que poderia fazer!”.

Ou seja, TDD não é fazer baby steps o tempo todo, e sim poder fazer baby steps quando necessário! Se você está escrevendo algum trecho de código complicado, você pode andar mais devagar; agora, se está escrevendo algum trecho de código simples, e você está confiante sobre isso, você pode dar um passo maior!

Um exemplo que me agrada muito em um outro artigo do Beck [2] é o exemplo de uma classe que lida com tabelas de mortalidade, ou seja, ela deve calcular valores de acordo com a idade de uma pessoa. Uma classe que poderia ser escrita naturalmente da seguinte maneira:

public class MortalityTable {
  public MortalityTable(Person person) {
    // ...
  }

  public Table calculate() {
    // usa Person aqui para calcular a tabela
  }
}

E ele mostra que, ao usar TDD para gerar essa classe, ele percebeu que (i) gerar uma entidade Person era complicado e (ii) a classe MortalityTable só precisava realmente da idade da pessoa para fazer o cálculo. Então, ele refatorou:

public class MortalityTable {
  public MortalityTable(int age) {
    // ...
  }

  public Table calculate() {
    // usa apenas a idade para calcular a tabela
  }
}

Ou seja, passar a idade para a classe MortalityTable era o jeito mais simples que ele tinha para resolver o problema! Isso sim é escrever o código mais simples que faz o teste passar! Perceberam a diferença?

Para terminar o post, gostaria de parafrasear o Jason Gorman: If “doing the simplest thing” tends to lead to lots of IF’s or Switch statements, it’s possible you’ve misunderstood TDD.

Referências

[1] Beck, K. Test-Driven Development: By Example. Addison-Wesley Professional, 2002.
[2] Beck, K. Aim, Fire. IEEE Software Volume 18 Issue 5, September 2001.