refatoração
DESCRIPTION
Definição de refatoração, quando utilizar, exemplos e técnicasTRANSCRIPT
Desenvolvimento Baseado em Testes
Refatoração
Agenda
• Refatoração
• Exemplos
• Técnicas de Refatoração
���2
Refatoração
O que é?
• “É o processo de realizar mudanças em código existente e funcional sem alterar seu comportamento”
!
• Alterar COMO o código
• NÃO alterar O QUE ele faz
!
• Aprimorar a estrutura interna
Qual a relação com TDD?
Refatoração e TDD
• Após implementar o código mais simples para fazer o teste passar
• refatoramos o código para remover as duplicações que adicionamos para ver o teste passar
• Como temos um conjunto seguro de testes,
• então podemos refatorar com confiança
O que nos motiva a refatorar?
Motivação
• facilitar a adição de código novo
• melhorar o projeto existente
• obter um melhor entendimento de código
• tornar a programação menos irritante
Quando refatorar?
Contextos
• quando existe duplicação de código
• quando a intenção é obscura
• percebemos que o código e/ou sua intenção não são claros
• ex: lógica condicional complicada
• quando detectamos problemas de código (“bad smells”)
• ou indícios de problemas
Duplicação
• Falência de um bom código
• Existem várias formas
• simples e óbvios
• índícios
• disfarçados
Say Everything Once and Only Once !
Don’t Repeat Yourself
Diga tudo uma vez e apenas uma vez !
Não se repita
Exemplos
Duplicação: 1.º exemplo
def save if (arquivo.nil?) return false end diretorio = Diretorio.new(arquivo) diretorio.add(arquivo) diretorio.close() return true end
def saveAs arquivo = view.file if (arquivo.nil?) return false end diretorio = Diretorio.new(arquivo) diretorio.add(arquivo) diretorio.close() return true end
def save if (arquivo.nil?) { return false } diretorio = Diretorio.new(arquivo) diretorio.add(arquivo) diretorio.close() return true end
def saveAs arquivo = view.file save end
Duplicação: 2.º exemplo
class MovieList def initialize @movies = [] @number_of_movies = 0 end def size @movies.size end def add movie_to_add @movies << movie_to_add @number_of_movies = @number_of_movies + 1 endend
class MovieList ! def initialize @movies = [] @number_of_movies = 0 end! def size @movies.size end def add movie_to_add @movies << movie_to_add end!end
Intenção obscura
O que torna um código claro?
• Escolher bons nomes
• dicionário na mão para ajudar a comunicar nossa intenção
• TDD
• Como escrevemos 1º o teste, somos forçados a pensar na interface do código antes da sua implementação
• oportunidade de pensar a partir do ponto de vista do usuário da classe
Problemas de código Code Smells
Bad Smells• Excesso de comentários
• Classes de dados
• Código duplicado
• Intimidade inapropriada
!
!
!
• Classes muito grandes
• Classes “preguiçosas”
• Métodos longos
• Switches
def init // set the layout content_pane.layout(FlowLayout.new) ! // create the list movie_list = List.new(my_editor.movies) scroller = ScrollPane.new(movie_list) content_pane.add(scroller) ! // create the field movie_field = TextField.new(16) content_pane.add(movie_field) ! // create theadd button add_button = Button.new(“Add") .... end
Excesso de comentários
Classes de dadosclass Ponto attr_accessor :x, :y def initialize(x = 0, y = 0) @x = x; @y = y; end end
Com intimidade
def temperatura t = estacao.termometro t.temperatura end
Sem intimidade
def temperatura estacao.temperatura end
Classes muito grandes
• Desproporcional às outras
• Por quê?
• tenta fazer muita coisa?
• possui muito código condicional?
• possui muito comportamento condicional?
• Como identificar?
Classes “preguiçosas”
• Classes tão pequentas que não justificam sua existência
• Devem ser fundidas à outras classes
Switchesclass Empregado // 0 - engenheiro, 1 - vendedor, 2 - gerente attr_accessor :tipo_empregado ! def nome_do_departamento case @tipo_empregado when 0 return "Engenheiro" when 1 return "Vendedor" when 2 return "Gerente" else return "Desconhecido" end end
end
Dica
• Princípios de Orientação a objetos
• Design Patterns
Como refatorar?
Como refatorar
1. Estrutura de testes que proporcionem feedback
2. Pequenos passos
3. IDEs
Técnicas de refatoração
Refatorações• Extrair classe
• Extrair interface
• Extrair método
• Substituir código digitado por subclasses ou objeto de valor
• Substituir condicional por polimorfismo
• Utilizar métodos gabaritos
• Utilizar variavel explicativa
• Substituir construtores por métodos fábrica
• Substituir herança por delegação
• Substituir números mágicos por constantes
Extrair Classe
• Contexto
• classes muito grandes
• comportamento disperso
• Solução
• fracionar as classes em pedaços menores mais coesos
• Extração de comportamentos para uma nova classe
class MovieListWriter attr_accessor :destination! def initialize(aWriter = nil) destination = aWriter; end! def write_movie_list(a_list) a_list.movies.each do |movie| write_movie(movie) end end! def write_movie(a_movie) destination.write(a_movie.name) destination.write('|') destination.write(a_movie.category.to_s) destination.write('|') begin destination.write(a_movie.rating.to_s) rescue UnratedException => ex destination.write("-1") end destination.write('\n') endend
class Movie // ... def write_to(destination) destination.write(name) destination.write('|') destination.write(category.to_s) destination.write('|') begin destination.write(rating.to_s) rescue UnratedException => ex destination.write("-1") end destination.write('\n'); end // ...end
class MovieList write_to(destination) movies.each do |movie| movie.write_to(destination) end end end
Extrair Interface
• Contexto
• Se quer abstrair a forma de uma implementação concreta
• Comportamentos importantes substituíveis ou reversíveis
• Solução
• criar interfaces para poder substituir o concreto tardiamente
public class MovieList { private Collection<Movie> movies = new ArrayList<Movie>();!
public int size() { return movies.size(); } public void add(Movie movieToAdd) { movies.add(movieToAdd); }}
public interface IMovieList {!
public abstract int size();!
public abstract void add(Movie movie);!
}
public class MovieList implements IMovieList { //...}
Strategy
Extrair Método
• Contexto
• métodos muito longos
• lógicas de complexo entendimento
• Solução
• fracionar o método em métodos menores mais coesos
• Extração de comportamentos para novos métodos
public void init() { getContentPane().setLayout(new FlowLayout()); movieList = new JList(myEditor.getMovies()); JScrollPane scroller = new JScrollPane(movieList); getContentPane().add(scroller); movieField = new JTextField(16); getContentPane().add(movieField); addButton = new JButton("Add"); ....
}
public void init() { // set the layout getContentPane().setLayout(new FlowLayout()); ! // create the list movieList = new JList(myEditor.getMovies()); JScrollPane scroller = new JScrollPane(movieList); getContentPane().add(scroller); ! // create the field movieField = new JTextField(16); getContentPane().add(movieField); ! // create theadd button addButton = new JButton("Add"); .... }
public void init() { setLayout(); initMovieList(); initMovieField(); initAddButton(); } private void setLayout() { getContentPane().setLayout(new FlowLayout()); } private void initMovieList() { movieList = new JList(getMovies()); JScrollPane scroller = new JScrollPane(movieList); getContentPane().add(scroller); } private void initMovieField() { movieField = new JTextField(16); getContentPane().add(movieField); } private void initAddButton() {...
• Se um trecho de código duplicado diferentes do programa
Extrair Método 2
Classe1
Extrair Método 2• Se as duplicatas de código devem permanecer
sempre iguais, ou seja, uma vez que se realize uma alteração em uma delas, as demais devem refletir a alteração
Classe1
public void doGet(HttpservletRequest request, HttpServletResponse response) throws ServletException, IOException{ String p = request.getParameter(“personagem”); request.setAttribute(“personagem”, p); //Mais código } !public void doPost(HttpservletRequest request, HttpServletResponse response) throws ServletException, IOException{ String p = request.getParameter(“personagem”); request.setAttribute(“personagem”, p); //Mais código }
Extraindo o método• Concentre o código que se repete em um único
lugar, por exemplo, em um método e leve as dependências para lá
public void doGet(HtttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String p = request.getParameter(“personagem”); request.setAttribute(“personagem”, p); //Mais código }
public void novoMetodo(HtttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
String p = request.getParameter(“personagem”); request.setAttribute(“personagem”, p); //Mais código }
public void novoMetodo(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{ ..... } public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{ novoMetodo(request, response); } !
public void doPost(HttpservletRequest request, HttpServletResponse response)
throws ServletException, IOException{ novoMetodo(request, response); }
Refatorações• Extrair classe
• Extrair interface
• Extrair método
• Substituir código digitado por subclasses ou objetos de valor
• Substituir condicional por polimorfismo
• Utilizar métodos gabaritos
• Utilizar variavel explicativa
• Substituir construtores por métodos fábrica
• Substituir herança por delegação
• Substituir números mágicos por constantes
Substituir código digitado por subclasses • Contexto
• classes indicam subtipos através de código digitado
• Solução
• Criar uma subclasse para cada alternativa
• Vantagem
• Evitam-se complexos condicionais
class Empregado //0 - engenheiro, 1 - vendedor, 2 - gerente attr_accessor :tipo_do_empregado //..end
class Empregado // ...end!class Engenheiro < Empregado // ...end!class Vendedor < Empregado // ...end!class Gerente < Empregado // ...end
Substituir condicional por polimorfismo
• Contexto
• classes indicam subtipos através de código digitado
• Solução
• Criar uma subclasse para cada alternativa
• Vantagem
• Evitam-se complexos condicionais
public class Empregado // 0 - engenheiro, 1 - vendedor, 2 - gerente attr_accessor :tipo_do_empregado! def nome_do_departamento case @tipoDoEmpregado when 0 return "Engenharia" when 1 return "Vendas" when 2 return "Gerência" else return "Desconhecido" end endend
class Empregado def nome_do_departamento endend!class Engenheiro < Empregado def nome_do_departamento "Engenharia" endend!class Vendedor < Empregado def nome_do_departamento "Vendas" endend!class Gerente extends Empregado def nome_do_departamento "Gerência" endend
Utilizar métodos gabaritos
• Contexto
• métodos em subclasses executam passos similares na mesma ordem
• os passos são diferentes
• Solução
• extraia os passos para métodos com mesma assinatura
• crie um método gabarito final na superclasse
• especialize os métodos nas subclasses
Contextopublic class Cafe { public void prepararReceita(){ ferverAgua(); misturarCafeComAgua(); servirNaXicara(); adicionarAcucarELeite(); }
!
!
!!!
public class Cha { ! public void prepararReceita(){ ferverAgua(); misturarChaComAgua(); servirNaXicara(); adicionarLimao(); }
Utilizar variáveis explicativas
• Contexto
• expressões complexas de se entender
• Solução
• extrair partes delas
• guardar resultados intermediários em variáveis bem nomeadas
• Vantagem
• código de melhor entendimento
def calcular_total subtotal.mais(subtotal_taxavel.vezes(0.15))) .menos((subtotal().to_f > 100.0) ? (subtotal().vezes(0.10)) : 0)end
def calcular_total taxa = subtotal_taxavel().vezes(0.15) total = subtotal.mais(taxa) qualificado_ao_desconto = subtotal.to_f > 100.0 desconto = qualificadoAoDesconto ? subtotal.vezes(0.10) : Dinheiro.new(0.0) total.menos(desconto)}
Substituir construtor por métodos fábrica
• Contexto
• existência de diversos construtores para criar versões diferentes dos objetos
• pode haver confusão por falta de clareza de intenção do construtor
• Solução
• criar métodos fábrica estáticos
• Vantagem
• código de melhor entendimento
Como fazer• Execute um Extrair Método para isolar a lógica do
comportamento
• o método deve ser de classe
• repasse as dependências
• Teste
• Se o método fábrica não estiver no objeto desejado, utilize o Mover Método
• Teste
• Remova o construtor original se não há chamadas a ele
Extrair Método
Mover Método
Outra maneira Java
public class Avaliacao { private int valor = 0; private String revisor = null; private String revista = null;! public Avaliacao(int umaAvaliacao) { this(umaAvaliacao, "Anonimo", ""); }! public Avaliacao(int umaAvaliacao, String umRevisor) { this(umaAvaliacao, umRevisor, ""); }! public Avaliacao( int umaAvaliacao, String umRevisor, String umaRevista) { valor = umaAvaliacao; revisor = umRevisor; revista = umaRevista; } // ...}
public static Avaliacao novaAvaliacaoAnonima(int valor) { return new Avaliacao(valor, "Anonimo", "");}!public static Avaliacao novaAvaliacao(int valor, String revisor) { return new Avaliacao(valor, revisor, "");}!public static Avaliacao novaCritica( int valor, String revisor, String revista) { return new Avaliacao(valor, revisor, revista);}!private Avaliacao( int umaAvaliacao, String umRevisor, String umaRevista) { valor = umaAvaliacao; revisor = umRevisor; revista = umaRevista;}
starWars.adicionarAvaliacao(new Avaliacao(2));starWars.adicionarAvaliacao(new Avaliacao(4, "Joel Barbosa"));starWars.adicionarAvaliacao( new Avaliacao(5, "PH Santos", "TechTudo"));
starWars.adicionarAvaliacao( Avaliacao.novaAvaliacaoAnonima(2));!starWars.adicionarAvaliacao( Avaliacao.novaAvaliacao(4, "Joel Barbosa"));!starWars.adicionarAvaliacao( Avaliacao.novaCritica(5, "PH Santos", "TechTudo"));
Substituir herança por delegação
• Contexto
• uma subclasse utiliza apenas uma parte da interface de sua superclasse e não reutiliza os dados
• Solução
• crie um campo do tipo da superclasse
• refatore os métodos que utilizam o comportamento da superclasse
• remova a herança
Substituir números mágicos por constantes ou enums
• Contexto
• valores literais no código que possuem um significado
• Solução
• crie uma constante e nomeie-a com o seu significado
• substitua os valores literais pelas constantes
• Vantagem
• código de melhor entendimento
def energia_potencial(massa, altura) massa * altura * 9.81end
CONSTANTE_GRAVITACIONAL = 9.81 def energia_potencial(massa, altura) massa * altura * CONSTANTE_GRAVITACIONAL end
Resumo
• Um pouco de refatoração
• o que é
• técnicas específicas
• alguns indicadores
• Existe problema?
• Deve-se refatorar em pequenos incrementos
Bibliografia
• ASTELS, David. Test-Driven Development: A Pratical Guide. Prentice Hall, 2003.
• FOWLER, Martin; BECK, Kent; BRANT, John; Opdyke, William; ROBERTS, Don. Refactoring: Improving The Design of Existing Code.
• KERIEVSKY, Joshua. Refatoração Para Padrões. Porto Alegre: Bookman, 2008.
Bibliografia
• ASTELS, David. Test-Driven Development: A Pratical Guide. Prentice Hall, 2003.
• FOWLER, Martin; BECK, Kent; BRANT, John; Opdyke, William; ROBERTS, Don. Refactoring: Improving The Design of Existing Code.
• KERIEVSKY, Joshua. Refatoração Para Padrões. Porto Alegre: Bookman, 2008.