Refatoração

Post on 11-Jun-2015

907 views 1 download

description

Definição de refatoração, quando utilizar, exemplos e técnicas

Transcript of Refatoração

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.