quinta-feira, 25 de setembro de 2014

Refatoração de Software

Refatoração de Software

Refatoração nada mais é do que alterar um sistema de software de modo que essa alteração não comprometa o comportamento externo do código, sendo assim é uma maneira disciplinada de aperfeiçoar o código onde as chances de falhas são minimizadas. Então, quando você refatora um código você está melhorando o projeto deste após o mesmo ter sido escrito. O propósito da refatoração é tornar o software legível a ponto que qualquer pessoa entende-lo, apenas estas mudanças são consideradas refatorações.

Kent Beck utiliza uma metáfora, denominada de os dois chapéus, onde diz que quando você desenvolve um sistema você utiliza basicamente duas atividades distintas: adicionar funções e refatorá-las. Neste desenvolvimento você geralmente se descobre trocando de chapéu, onde você começa adicionando uma nova função e após troca de chapéu e refatora esta funcionalidade.

Porque você deve refatorar?

- Melhora o projeto do software: Um sistema não refatorado normalmente necessita de mais códigos para as mesmas funções, sendo esses replicados diversas vezes.

-Torna o software legível: Ao desenvolver um sistema geralmente após um tempo outros programadores fazem alterações nesse código, então faz muita diferença o programador usar uma semana para entender o código e fazer a modificação ao invés de utilizar horas se o código fosse legível.

-Ajuda a encontrar falhas: Ao refatorar um código, você clareia certas suposições que havia feito até chegar ao ponto de não conseguir evitar de encontrar falhas.

-Ajuda a desenvolver mais rápido: Sem um bom projeto você progride rapidamente durante um certo tempo, porém ao longo de um projeto mau estruturado, você acaba perdendo mais tempo procurando e consertando falhas do que adicionando novas funcionalidades.

Quando você deve refatorar?

-Acrescentando novas funções: Um motivo que conduz a refatoração é quando você tem dificuldade em adicionar novas funcionalidades. Quando você precisa pensar muito tempo para saber o que o código faz é hora de refatorá-lo.

-Precisa consertar falhas: Quando você é notificado de uma falha é o sinal de que o código precisa ser refatorado, já que esse não estava claro o suficiente para detectar a falha.

-Enquanto revisa o código: As revisões de código regulares facilitam o entendimento de outros desenvolvedores em aspectos de um sistema grande e também a transmissão do conhecimento dos mais para os menos experientes.

Porque a refatoração funciona?

Programas que são difíceis de ler, que tem lógica duplicada, que para incluir novas funcionalidades requer modificação no código existente e com lógicas condicionais complexas, são difíceis de modificar.

Quando você não deve refatorar?

Há vezes em que o código está tão confuso que você até poderia refatorá-lo, porém é mais fácil reescrevê-lo. Outras vezes você se encontra muito perto do prazo final da entrega do software, então acaba adiando a refatoração.

Exemplo de refatoração:

Código não refatorado.:

public class StringUtil{
 private StringUtil(){}
 public static String us(String a){
  if(a==null){
   throw new NullPointerException("O campo não pode ser nulo");
  }
  char[] c = a.toCharArray();
  for (int i=0; i < a.length; i++){
   if((a[i] >= 97 && a[i] <= 122) || a[i]==231){
     a[i] -= 32;
   }
  }
 }
}

Código refatorado.:


public class StringUtil{


  private static final String EXCEPTION_MESSAGE = "O campo não pode ser nulo";
  private static final int DIF_MIN_MAIUS = 32;

  private StringUtil(){}

 /**
  * Converte uma String Minuscula para Maiuscula
  *
  *
  * @param str - A String em minusculo
  * @return Retorna uma nova String em maiusculo
  * @throw Lanca uma excecao quando o campo eh nulo
 */

 public static String upperString(String str){

  if(str == null) throw new NullPointerException(EXCEPTION_MESSAGE);

  char[] chars = str.toCharArray();

  for(int i = 0;i < chars.length;i++) chars[i] = if((chars[i] >= 97 && chars[i] <= 122) || chars[i]==231) chars[i] -= DIF_MIN_MAIUS;   

  return new String(chars);
}

Você pode notar que agora o código contém as seguintes alterações:

- O código está comentado, o que possibilita saber qual a função do método.

- O código possui duas variáveis contantes (EXCEPTION_MESSAGE,DIF_MIN_MAIUS) para não utilizar números ou símbolos mágicos.

- O código se torna legível, pois ao colocar o nome do método de 'upperString' ao invés de 'us', o nome da variável passada como parâmetro 'str' ao invés de 'a' e a variável do array 'chars' ao invés d 'c' possibilitam saber direto o que o método faz.

- O código evita '{}' desnecessárias.

Referência: Refatoração, de Martin Fowler.

Nestas Eleições, escolha em sobrepor o toString!


Bom, garanto que você esta se questionando o porquê deste titulo bizarro. Tentarei esclarecer esta questão...

Ao contrario de muitos candidatos, o toString fará e mostrará o que você quer,(claro desde que seja programado para isto), mas se você não o sobrepor, ele só fará e mostrará o que já lhe vem programado a fazer, mostrará só um monte de coisas que você não entederá, códigos e símbolos muito estranhos, que vem da classe object.

Agora tentarei descrever de uma forma um pouco mais técnica o porquê se deve sempre sobrepor o toString.

Embora java.lang.Object forneça uma implementação do método toString, geralmente a string que ele retorna não é a que o usúario de sua classe vai querer ver. Ela é composta pelo nome da classe seguindo de uma "arroba(@)" e da representação do código de hash em hexadecimal sem sinal, por exemplo, "PhoneNumber@163b91". Ela deve ser uma representação concisa, mas não é tão facil de entender e ler se for comparado a "(53)3232-3232".

Fornecer uma boa implementação de toString tornará sua classe mais agradável de usar, o método toString é chamado automaticamente quando um objeto é passado para println, printf, para o operador de concatenação de strings ou para assert, ou exibido por um depurador. Para ser considerado prático, o método toString deve retornar todas as informações interessantes contidas no objeto, quando implementa-lo deve-se especificar o formato do valor de retorno na documentação.

A vantagem de especificar o formato é que ele serve como uma representação do objeto que é padrão, clara e legível por humanos.

A desvantagem de especificar o formato do valor de retorno, é que uma vez especificado você ficará preso a ele indefinidamente, supondo que sua classe seja muito usada. Se você alterar a representação em uma versão futura, invalidára seus códigos e dados e eles reclamarão.

Ao não especificar um formato, você preservará a flexiblidade de inclusão de informações ou aperfeiçoamento do formato em uma versão subsequente.

Decidindo ou não especificar o formato, não se esqueça em documentar claramente suas intenções.


Exemplo sem sobrepor o toString

public class Cliente {
    private String nome;
    public Cliente(String nome) {
        this.nome = nome;
    } 

     //@Override
     //public String toString() {   
     //return "O Melhor Jogador do TADS eh = " + nome;   
     //}

    public static void main(String[] args) {
        Cliente cliente = new Cliente("Falcãozinho Gaucho!");
        System.out.println(cliente);
    }
}

//Resposta com toString de Object (Cliente@1509443)


Exemplo sobrepondo o toString

public class Cliente {
    private String nome;
    public Cliente(String nome) {
        this.nome = nome;
    }     
     @Override
     public String toString() {   
     return "O Melhor Jogador do TADS eh = " + nome;   
     }
    public static void main(String[] args) {
        Cliente cliente = new Cliente("Falcãozinho Gaucho!");
        System.out.println(cliente);
    }
}


//Resposta com toString de sobrescrito (O Melhor Jogador do TADS eh = Falcãozinho Gaucho!)


Referência: Java Efetivo, de Joshua Bloch

Desenvolvimento e Otimização de um Projeto Baseados em Testes

Desenvolvimento e Otimização de um Projeto Baseados em Testes

O desenvolvimento baseado por testes deve seguir 3 regras simples:

  1. Não escrever o código de produção até que voce tenha escrito um teste de unidade que falhe.
  2. Não escrever mais um teste de unidade do que suficiente para falhar ou não compile.
  3. Não escrever mais que qualquer código de produção para passar no teste falho.

O objetivo é construir ciclos curtos, alternando as etapas de desenvolvimento. O conjunto de testes geram um impedimento para o desenvolvimento. Indica onde o programa apresenta falhas, nos permitindo criarmos funções para corrigir o programa ou para melhorarmos a sua eficiência.

Ao desenvolver um software baseado por testes ele deve ser desacoplado do seu ambiente para ser testado. Isso gera uma documentação importante para a empresa contratante e desenvolvedora.

Testes de Aceitação, além de necessários são fundamentais para que se verifique se os requisitos do cliente estão sendo cumpridos, mas são insuficientes como ferramentas de verificação do software.

Arquitetura do Projeto

Para desenvolver um projeto de software, é necessário ter uma equipe definida, com o modelo a seguir. Usando o modelo por testes pois como o programa de software a ser desenvolvido é de funções matemáticas, pode-se testa-lo desacoplado do seu ambiente e buscá-lo otimizá-lo.

A programação por pares pode ser uma excelente escolha, pois um programador desenvolve e o outro pode corrigir os erros e otimizar as funções do software.


                        +-------------+
                        | Matemática  |
                     1  | Aplicada    |
                        +-------------+             
         
     +-----------+      +-------------+    +-------------+
     | Algebra   |      | Geometria   |    | Estatística |
     +-----------+      +-------------+    +-------------+
  1A | Funções   |   1B |Trigonometria| 1C | Matemática  |
     | Calculos  |      |             |    | Financeira  |
     +-----------+      +-------------+    +-------------+
     
     
                        +-------------+
                        | Interface   |
                      2 | Usuário     |
                        +-------------+

Um exemplo de teste apresenta abaixo. Desenvolver funções matemáticas: O código mostra o código de números n primos, podendo ser implementados em linguagem Java, C ou C++.

Esse código quando o programador olha ele não sabe as funções de cada parâmetro e variável do código e tem que testa-lo, não sendo uma boa prática de programação.

 while(p>0)
 { 
  d=2;
  while((n%d)!=0)
  {
   d+=1;
  }
  if(d==n)
  { printf("n: %d ", n); 
   p--;
  }
  n++;
      
 }
 if (n==p)
 printf("n: %d ", n);

O código ao ser Testado mostra n números primos, em uma função desacoplada do projeto, mas pode ser refatorado e otimizado.

public int numeroPrimo() {
 for(primo=2; (numero>0);primo++)
 { for(quantidade=2;((p%quantidade)!=0);quantidade++)
  { if(d==p)
   { printf("primo: %quantidade ", primo); 
    numero--;
   }
  }
  if (quantidade == numero)
  printf("numero: %primo ", numero);
 }
 return numeroPrimo()
 }

O código refatorado apresenta uma maior otimização, o programador, ou equipe de desenvolvimento após testar o programa deve otimizar seu códigos usando refatoração ou outros métodos necessários conforme a plataforma utilizada ou a exigência da empresa, os nomes dos métodos e variáveis devem ser descritos na forma que qualquer outro programador, desenvolvedor veja o código fonte e entenda o programa. Sendo que implantado através de métodos permite a utilização através da chamada de métodos, sem repetir códigos e utilizá-lo em partes posteriores do projeto.

Obedecendo o novo paradigma que é a orientação orientada a objetos.

MARTIN. Micah, MARTIN Robert C.; Princípios, Padrões e Práticas Ágeis em C#. Editora Bookman,2011.