Refatoração de código com Capitão Nascimento versão completa

Post on 27-May-2015

4.061 views 13 download

description

Palestra apresentada no FISL 12 Edição.

Transcript of Refatoração de código com Capitão Nascimento versão completa

Eduardo Bregaida

Refatoração com Capitão Nascimento

Eduardo Bregaida

•http://javawora.blogspot.com

•@bregaida

•eduardo.bregaida@gmail.com

O Conceito de Refatoração

Por Martin Fowler...

É o processo de reescrever um programa

de computador para melhorar sua estrutura ou legibilidade, preservando

assim seu comportamento.

Teve sua origem nos anos 80/90 com Smalltalk...

Desenvolveu-se formalmente na

Universidade de Illinois em Urbana-Champaign.

Grupo do Prof. Ralph Johnson...

Capitão?

Pois não?

Seu 05 está dormindo

Seu 05

Senhor?

Tenha a bondade de segurar isto.

Seu 05 se o senhor dormir o senhor vai se explodir, vai me explodir...

E vai explodir a todos seus companheiros do FISL.

O senhor vai dormir seu 05?

Não Senhor!

Estamos todos confiando no senhor.

Vamos analisar a motivação de refatorar

Por que refatorar?

Mudará algo na geração dos bytecodes?

Mudará algo para o computador?

Mudará algo para o cliente?

Não!

Facilitará para o senhor no caso de uma

manutenção lembrar facilmente do código

Facilitará para outros desenvolvedores entender o

que o código faz

Manutenibilidade

Um exemplo de código senhores

Dado um array de N elementos, calcular a seguinte operações:

Dado um array de N elementos, calcular a seguinte operações:

* Primeiro item menos o último

Dado um array de N elementos, calcular a seguinte operações:

* Primeiro item menos o último* O resultado dessa subtração elevado ao quadrado somado ao segundo item menos o penúltimo

Dado um array de N elementos, calcular a seguinte operações:

* Primeiro item menos o último* O resultado dessa subtração elevado ao quadrado somado ao segundo item menos o penúltimo

* O resultado dessa subtração elevado ao quadrado.

Dado um array de N elementos, calcular a seguinte operações:

* Primeiro item menos o último* O resultado dessa subtração elevado ao quadrado somado ao segundo item menos o penúltimo

* O resultado dessa subtração elevado ao quadrado.* uma seqüência com N elementos

Dado um array de N elementos, calcular a seguinte operações:

* Primeiro item menos o último* O resultado dessa subtração elevado ao quadrado somado ao segundo item menos o penúltimo

* O resultado dessa subtração elevado ao quadrado.* uma seqüência com N elementos

* (1o - N) ^2 + (2o - (N-1)) ^2 + ... + (N/2 - N/2)^2

Dado um array de N elementos, calcular a seguinte operações:

* Primeiro item menos o último* O resultado dessa subtração elevado ao quadrado somado ao segundo item menos o penúltimo

* O resultado dessa subtração elevado ao quadrado.* uma seqüência com N elementos

* (1o - N) ^2 + (2o - (N-1)) ^2 + ... + (N/2 - N/2)^2* Retornar essa soma ou "-1" caso a entrada seja inválida

public class Calcula{ // contador do array static int count=-1;

// método que faz o calculo solicitado public int somaSerie (int array []) { if(null == array) return -1; else if(++count>=array.length/2) // se chegou na metade, retorna zero return 0; // aplica a fórmula com chamada recursiva return ((array[count]-array[array.length-(count+1)])* (array[count]-array[array.length-(count+1)])) + somaSerie(array); }

// Teste aqui? public static void main(String args[]) { int ar[] = {1,2,3,4,5,6,7,8,9,10,11}; System.out.println("Resultado=" + new Calcula().somaSerie(ar)); }}

Fácil de entender senhores?

Não...

Vamos ver como a refatoração o melhora

public class CalculaRef { //Constante do valor Metade private static final int VALOR_METADE = 2; // contador do array static int count = -1; private int[] array; private int tamanho = 0;

// retorno para array nulo final int ENTRADA_INVALIDA = -1;

public CalculaRef(int[] array) { this.array = array; tamanho = array.length; }

// mesmo cálculo efetuado na classe Calcula // mas as operações difíceis de ler // foram transformadas em métodos public int somaSerie() { if (array.length == 0) return ENTRADA_INVALIDA; else if (isMaiorQueMetade(++count)) return 0;

int subtracaoValores = getPrimeiroItemArrayMenosUltimo(); return (getPotenciaQuadrado(subtracaoValores) + somaSerie()); }

public int getPotenciaQuadrado(int numeroOperacao) { return numeroOperacao * numeroOperacao; }

public int getPrimeiroItemArrayMenosUltimo() { return array[count] - array[tamanho - (count + 1)]; }

public boolean isMaiorQueMetade(int indice) { return indice >= tamanho / VALOR_METADE; }

}

/** * Classe de Teste */public class CalculaRefTeste { public static void main(String args[]) { int ar[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; CalculaRef refTeste = new CalculaRef(ar); System.out.println("Resultado=" + refTeste.somaSerie()); }}

Podemos melhorar mais...

public class CalculaRefDois {

// contador deixou de ser estático e passou a ser um atributo private int count = -1;

// array possui agora uma classe para efetuar suas operações específicas private ArrayUtil arrayUtil; final int ENTRADA_INVALIDA = -1;

public CalculaRefDois(int[] array) { // inicializa classe do array this.arrayUtil = new ArrayUtil(array); }

// ficou quase igual ao método da classe CalculaRefHum a diferença // é que o IF e o ELSE IF chamam métodos da classe nova a ArrayUtils public int somaSerie() { if (arrayUtil.getTamanho() == 0) return ENTRADA_INVALIDA; else if (arrayUtil.isMaiorQueMetade(++count)) return 0;

int subtracaoValores = getPrimeiroItemArrayMenosUltimo(); return (getPotenciaQuadrado(subtracaoValores) + somaSerie()); }

public int getPotenciaQuadrado(int valorSubtracao) { // Utilizando a lib Math do Java na qual já cálcula um valor passado e seu expoente //Cast de int pois pow retorna double return (int) Math.pow(valorSubtracao,EXPOENTE_QUADRADO); }

public int getPrimeiroItemArrayMenosUltimo() { return arrayUtil.getItem(count) - arrayUtil.getItemIndiceInverso(count); }}

public class ArrayUtil {

private int array[]; private int tamanho = 0;

private static final int VALOR_HUM = 1; //Constante do valor Metade private static final int VALOR_METADE = 2;

public ArrayUtil(int array[]) { this.array = array; if (null == array) this.array = new int[] {}; tamanho = this.array.length; }

public int getIndiceIntermediario() { return tamanho / VALOR_METADE; }

public int getTamanho() { return tamanho; }

public int getItem(int indice) { return array[indice]; }

public int getItemIndiceInverso(int indice) { return array[tamanho - (indice + VALOR_HUM)]; }

public boolean isMaiorQueMetade(int indice) { return indice >= getIndiceIntermediario(); }}

Senhor, o que é preciso para refatorar?

Xerife, o senhor precisa testar o código

Xerife, o senhor precisa sentir o “mau cheiro” do

código!

O que seria sentir o “mau cheiro” do código senhor?

Classes com muitas responsabilidades

Um código sem padrão algum

Sistemas sem alta coesão e baixo acoplamento

Ah, mas isso dá muito trabalho senhor

Xerife

O senhor é um fanfarrão seu Xerife

Os senhores têm que refatorar sempre

public class PontoHandler { private Ponto ponto = new Ponto(); private Session session; private DAO<Ponto> dao; public void salvar() { session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); if (ponto.getId() == 0) { dao.salveOrUpdate(this.ponto); } else { session.merge(this.ponto); } this.ponto = new Ponto();

}

public void carregar(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink .findComponent("editId"); Long id = (Long) parameter.getValue(); session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); this.ponto = dao.load(id);

}

public void excluir(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink .findComponent("editId"); Long id = (Long) parameter.getValue(); session = HibernateUtil.getCurrentSession(); DAO<Ponto> dao = new DAO<Ponto>(session, Ponto.class); this.ponto = dao.load(id); dao.deleta(ponto); this.ponto = new Ponto();

}

//Outros métodos

}

Eliminem a duplicidade

Dêem nomes decentes aos métodos e os

parâmetros

public class PontoHandler { private Ponto ponto = new Ponto(); private PontoBusiness negocio = new PontoBusiness(); private Long id ; public void salvar() { if (ponto.getId() == 0) { negocio.salvar(this.ponto); } else { negocio.merge(this.ponto); } this.ponto = new Ponto();

}

public void carregar(ActionEvent event) { id = populaDados(event); this.ponto = negocio.carregar(id); }

public void excluir(ActionEvent event) { id = populaDados(event); this.ponto = negocio.carregar(id); negocio.excluir(ponto); this.ponto = new Ponto(); } public List<Ponto> getAllPonto() { return negocio.getAllPontos(); }

/** * Popula os dados dos componentes JSF * @param event * @return id */ private Long populaDados(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink.findComponent("editId"); Long id = (Long) parameter.getValue(); return id; }

//Outros métodos}

Os nomes dos métodos e parâmetros

tem que ser condizentes com o que eles fazem

public class PontoHandler {

private Ponto ponto = new Ponto();

private Session session;

private DAO<Ponto> dao; public void salvar() { //Implementação }

public void carregar(ActionEvent event) { //Implementação }

public void excluir(ActionEvent event) { //Implementação }

}

Os métodos e classes devem ser pequenos

public class PontoHandler { private Ponto ponto = new Ponto(); private Session session; private DAO<Ponto> dao; public void salvar() { session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); if (ponto.getId() == 0) { dao.salveOrUpdate(this.ponto); } else { session.merge(this.ponto); } this.ponto = new Ponto();

}

public void carregar(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink .findComponent("editId"); Long id = (Long) parameter.getValue(); session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); this.ponto = dao.load(id);

}

public void excluir(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink .findComponent("editId"); Long id = (Long) parameter.getValue(); session = HibernateUtil.getCurrentSession(); DAO<Ponto> dao = new DAO<Ponto>(session, Ponto.class); this.ponto = dao.load(id); dao.deleta(ponto); this.ponto = new Ponto();

}

//Outros métodos

}

public class PontoHandler { private Ponto ponto = new Ponto(); private PontoBusiness negocio = new PontoBusiness(); private Long id ; public void salvar() { if (ponto.getId() == 0) { negocio.salvar(this.ponto); } else { negocio.merge(this.ponto); } this.ponto = new Ponto();

}

public void carregar(ActionEvent event) { id = populaDados(event); this.ponto = negocio.carregar(id); }

public void excluir(ActionEvent event) { id = populaDados(event); this.ponto = negocio.carregar(id); negocio.excluir(ponto); this.ponto = new Ponto(); } /** * Popula os dados dos componentes JSF * @param event * @return id */ private Long populaDados(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink.findComponent("editId"); Long id = (Long) parameter.getValue(); return id; }

//Outros métodos}

Crie comentários no

código

Utilize o JavaDoc

/** * @author eduardobregaida * PontoHandler * ManagedBean para fazer a ponte entre a View e o Controller */public class PontoHandler { // Classe de Ponto do ônibus private Ponto ponto = new Ponto();

// Classe da Linha do ônibus private Linha linha = new Linha(); // Método para Salvar um Ponto public void salvar() { if (ponto.getId() == 0) { negocio.salvar(this.ponto); } else { negocio.merge(this.ponto); } this.ponto = new Ponto();

}

// Método para excluir um ponto recebe um evento do componente public void excluir(ActionEvent event) { id = populaDados(event); this.ponto = negocio.carregar(id); negocio.excluir(ponto); this.ponto = new Ponto(); }

}

Tire as responsabilidades a mais nas classes

public class PontoHandler { private Ponto ponto = new Ponto(); private Session session; private DAO<Ponto> dao; public void salvar() { session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); if (ponto.getId() == 0) { dao.salveOrUpdate(this.ponto); } else { session.merge(this.ponto); } this.ponto = new Ponto();

}

public void carregar(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink .findComponent("editId"); Long id = (Long) parameter.getValue(); session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); this.ponto = dao.load(id);

}

public void excluir(ActionEvent event) { UIComponent comandLink = event.getComponent(); UIParameter parameter = (UIParameter) comandLink .findComponent("editId"); Long id = (Long) parameter.getValue(); session = HibernateUtil.getCurrentSession(); DAO<Ponto> dao = new DAO<Ponto>(session, Ponto.class); this.ponto = dao.load(id); dao.deleta(ponto); this.ponto = new Ponto();

}

//Outros métodos

}

public class PontoBusiness{ private Session session; private DAO<Ponto> dao; private Ponto ponto; public void salvar(Ponto ponto) { session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); dao.salveOrUpdate(ponto); } public void merge(Ponto ponto) { session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); session.merge(ponto); }

public Ponto carregar(Long id) { ponto = new Ponto(); session = HibernateUtil.getCurrentSession(); dao = new DAO<Ponto>(session, Ponto.class); ponto = dao.load(id); return ponto; }

public void excluir(Ponto ponto) { session = HibernateUtil.getCurrentSession(); DAO<Ponto> dao = new DAO<Ponto>(session, Ponto.class); dao.deleta(ponto); } //Outros métodos}

Não exponha o interior dos seus

objetos, encapsulem seus métodos

Esta classe apenas deve saber que o método soma recebe dois valores

public class CalculadoraTeste {

int valorA = 1; int valorB = 2;//Exemplo de DRY (Don't repeat yourself) public static void main(String[] args) { Calculadora calculadora = new Calculadora(); int resultado = calculadora.soma(valorA, valorB); System.out.println("Resultado: " + resultado); }

}

O Método está em outra classe deixando invisível para classe acima

public class Calculadora {

public int soma(int valorA, int valorB) { return valorA + valorB; }}

Quando usar números crie constantes

public class Calcula { // retorno para array nulo final int ENTRADA_INVALIDA = -1; final int VALOR_ZERO = 0;

public int somaSerie() { if (array.length == VALOR_ZERO) return ENTRADA_INVALIDA; else if (isMaiorQueMetade(++count)) return VALOR_ZERO; }

// Outros métodos da classe

}

Utiliza a Herança quando necessário

public class Funcionario {

private String nome; private double salario; private int idade; private int tempoRegistro; protected String cpf;

// Getters e Setters

}

public class Gerente extends Funcionario { private int senha; private int numeroDeFuncionariosGerenciados;

// Getters e Setters

}

public class GerenteTeste {

public static void main(String[] args) { Funcionario funcionario = new Funcionario(); funcionario.setNome("Carlos Bergamasco"); funcionario.setSalario(5000.0); System.out.println(funcionario.getNome()+" "+funcionario.getBonificacao()); Gerente gerente = new Gerente(); // podemos chamar metodos do Funcionario: gerente.setNome("Eduardo Bregaida"); // e tambem metodos do Gerente! gerente.setSenha(4231); gerente.autentica(gerente.getSenha()); gerente.setSalario(5000.0); System.out.println(gerente.getNome()+" "+gerente.getBonificacao());

}}

Utilize e prefira Polimorfismo

public abstract class Funcionario {

private String nome; private double salario; private int idade; private int tempoRegistro; protected String cpf; private int senha;

// Getters e Setters

}

public class Gerente extends Funcionario { private int numeroDeFuncionariosGerenciados;

// Getters e Setters

}

public class Vendedor extends Funcionario { private long quantidadesVenda;

// Getters e Setters}

public class PolimorfismoTeste {

public static void main(String[] args) {

Gerente gerente = new Gerente(); gerente.setNome("Eduardo Bregaida"); gerente.setSenha(4231); gerente.autentica(gerente.getSenha()); gerente.setSalario(5000.0); System.out.println("Gerente "+gerente.getNome()+" "+gerente.getBonificacao()); Vendedor vendedor = new Vendedor(); vendedor.setNome("Consani"); vendedor.setSalario(52); System.out.println("Vendedor "+vendedor.getNome()+" "+vendedor.getSalario());

Funcionario funcionario = new Gerente(); funcionario.setNome("Carlos Bergamasco"); funcionario.getBonificacao(); funcionario.setSalario(44.0); System.out.println("Funcionario Gerente "+funcionario.getNome()+" "+funcionario.getSalario());

funcionario = new Vendedor(); funcionario.setNome("Carlos Bergamasco"); funcionario.getBonificacao(); funcionario.setSalario(44.0); System.out.println("Funcionario Vendedor "+funcionario.getNome()+" "+funcionario.getSalario());

}}

Utilize a Interface

public abstract class Funcionario {

private String nome; private double salario; private int idade; private int tempoRegistro; protected String cpf;

// Getters e Setters

}

public interface Autenticavel { public boolean autentica(int senha);}

public class Gerente extends Funcionario implements Autenticavel {

private int senha;

// assinatura do contrato pela interface public boolean autentica(int senha) { if (this.senha != senha) { System.out.println("Acesso Permitido!"); return true; } else { System.out.println("Acesso Negado!"); return false; }

}}

Façam testes unitários

public class StringUtil { public static String recuperaNomeAtributo(String nomeMetodo){ if(nomeMetodo==null) throw new IllegalArgumentException("Agurmento não pode ser nulo"); return nomeMetodo; } public static String recuperaNomeAtributoNaoPodeSerVazio(String nomeMetodo){ if(nomeMetodo.equals("")) throw new IllegalArgumentException("Agurmento não pode ser vazio"); return nomeMetodo; } public static String recuperaNomeAtributoNaoPodeSerMenorQue4(String nomeMetodo){ if(nomeMetodo.length()<4) throw new IllegalArgumentException("Agurmento não pode ser menor do que 4 caracteres"); return nomeMetodo; } public static String recuperaNomeAtributoNaoPodeTerLetraMaiuscula(String nomeMetodo){ String maiuscula="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for(int i=0; i<nomeMetodo.length(); i++){ if (maiuscula.indexOf(nomeMetodo.charAt(i),0)!=-1){ throw new IllegalArgumentException("Agurmento não pode ser nulo"); } } return nomeMetodo; }

}

import junit.framework.TestCase;import br.com.cb.jUnitTes2.StringUtil;

public class StringUtilTest extends TestCase{

public void testRecuperaNomeAtributoNaoPodeSerNulo() throws Exception{ assertEquals("nome", StringUtil.recuperaNomeAtributo("nome")); } public void testRecuperaNomeAtributoNaoPodeSerVazio()throws Exception{ assertEquals("XPTO", StringUtil.recuperaNomeAtributoNaoPodeSerVazio("XPTO")); } public void testRecuperaNomeAtributoNaoPodeSerMenorQue4()throws Exception{ assertEquals("Abcd", StringUtil.recuperaNomeAtributoNaoPodeSerMenorQue4("Abcd")); } public void testRecuperaNomeAtributoNaoPodeTerLetraMaiuscula()throws Exception{ assertEquals("teste funfando", StringUtil.recuperaNomeAtributoNaoPodeTerLetraMaiuscula("teste funfando"));

}}

TDD - Test Driven Development

import static org.junit.Assert.assertEquals;import org.junit.Test;import br.com.cb.tdd.junit.Calculadora;

public class CalculadoraTeste { Calculadora calculadora = new Calculadora();

@Test public void deveriaSomarDoisValoresPassados() throws Exception { assertEquals(3, calculadora.soma(1, 2)); }

}

public class Calculadora {

public int soma(int valorA, int valorB) { return valorA + valorB; }

}

import static org.junit.Assert.assertEquals;import org.junit.Test;import br.com.cb.tdd.junit.Calculadora;

public class CalculadoraTeste { Calculadora calculadora = new Calculadora();

@Test public void deveriaSomarDoisValoresPassados() throws Exception { assertEquals(3, calculadora.soma(1, 2)); }

@Test public void deveriaSubtrairDoisValoresPassados() throws Exception { assertEquals(2, calculadora.subtracao(5, 3)); }

}

public class Calculadora {

public int soma(int valorA, int valorB) { return valorA + valorB; }

public int subtracao(int valorA, int valorB) { return valorA - valorB; }

}

import static org.junit.Assert.assertEquals;import org.junit.Test;import br.com.cb.tdd.junit.Calculadora;

public class CalculadoraTeste { Calculadora calculadora = new Calculadora();

@Test public void deveriaSomarDoisValoresPassados() throws Exception { assertEquals(3, calculadora.soma(1, 2)); }

@Test public void deveriaSubtrairDoisValoresPassados() throws Exception { assertEquals(2, calculadora.subtracao(5, 3)); }

@Test public void deveriaMultiplicarDoisValoresPassados() throws Exception { assertEquals(15, calculadora.multiplicacao(5, 3)); }}

public class Calculadora {

public int soma(int valorA, int valorB) { return valorA + valorB; }

public int subtracao(int valorA, int valorB) { return valorA - valorB; }

public int multiplicacao(int valorA, int valorB) { return valorA * valorB; }

}

import static org.junit.Assert.assertEquals;import org.junit.Test;import br.com.cb.tdd.junit.Calculadora;

public class CalculadoraTeste { Calculadora calculadora = new Calculadora();

@Test public void deveriaSomarDoisValoresPassados() throws Exception { assertEquals(3, calculadora.soma(1, 2)); }

@Test public void deveriaSubtrairDoisValoresPassados() throws Exception { assertEquals(2, calculadora.subtracao(5, 3)); }

@Test public void deveriaMultiplicarDoisValoresPassados() throws Exception { assertEquals(15, calculadora.multiplicacao(5, 3)); } @Test public void deveriaDividirDoisValoresPassados() throws Exception { assertEquals(3, calculadora.divisao(9, 3)); }}

public class Calculadora {

public int soma(int valorA, int valorB) { return valorA + valorB; }

public int subtracao(int valorA, int valorB) { return valorA - valorB; }

public int multiplicacao(int valorA, int valorB) { return valorA * valorB; }

public int divisao(int valorA, int valorB) { return valorA / valorB; }}

Esse negócio aí de testar é chato

demais e demora

O senhor seu Xerife é um fraco

Seu lugar não é aqui com

profissionais

Eu desisto senhor!

Os senhores estão deixando seu

capitão muito feliz

Testes mostram qualidade

Qualidade mostra que irá funcionar

Teste é o que diferencia

programadores de crianças

Estude Mocks

Há diversos frameworks que

facilitam o uso de mocks

Estude teste de Integração

Quer subir seu código no controle de

versão?

Sem teste?

Não vai subir ninguém

Vai todo mundo ficar quietinho aí e

testar

Se não testar...

Já avisei

Isso vai dar merda

Se os senhores não fizerem isso...

Bota ele no saco

Se persistir no erro...

Pede pra sair

Pede pra sair

Tira essa roupa preta, porque você não merece usar

Se não sair e não mudar

Bota na conta do papa

Dúvidas?

• http://www.caelum.com.br/apostilas/

• http://martinfowler.com/books.html

• http://www.slideshare.net/jeveaux/testes-e-refatorao

• http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882

• http://www.slideshare.net/cassiomarques/refatorao-design-patterns-em-rubyhttp://compare.buscape.com.br/refatoracao-aperfeicoando-o-projeto-de-codigo-existente-martin-fowler-8536303956.html

• http://www.ime.usp.br/~kon/presentations/

• http://ccsl.ime.usp.br/pt-br/palestras

• http://www.slideshare.net/guestd37c23/refactory-worshop

Para Estudar

• Agradecimentos

• Carlos Daniel Bergamasco

• Braulio Consani

• Christian Reichel

• Marcelo L. Z. Ingarano

• Adriana A. Gutierre

• Rogério Ap. Bregaida Junior