Engenharia de Software Moderna
Cap. 9 - Refactoring
Prof. Marco Tulio Valente
https://engsoftmoderna.info
1
Licença CC-BY; permite copiar, distribuir, adaptar etc; porém, créditos devem ser dados ao autor dos slides
Relembrando Cap. 1 (Introdução)
2
Manutenção de Software
3
Refactoring
4
Conceito tornou-se bastante popular
5
2018
2000
1999
Catálogo de Refactorings
6
Extração de Métodos
7
Extração de Métodos
8
Um exemplo real ...
9
10
Antes
11
Depois
O que se ganha ao extrair um método?
Quais as vantagens de extração de métodos?
12
Motivações para Extração de Métodos
13
Inline de Métodos (contrário de extração)
14
15
Antes
16
Depois
Movimentação de Métodos
17
18
Casos particulares de movimentação
(ao longo de uma hierarquia de classes)
19
Pull Up Method
20
Push Down Method
21
Extração de Classes
22
23
Qual o refactoring mais comum?
24
Renomeação
(variáveis, parâmetros, métodos, classes, exceções, etc)
(+50% das operações de refactoring)
25
Dar bons nomes a variáveis é um dos problemas mais difíceis em programação!
26
Exercícios
27
1. Dê o nome de refactorings A e B que se executados em sequência não produzem impacto no código de um sistema.
Ou seja, B reverte as mudanças realizadas por A.
28
2. Uma mudança no código que foi feita para melhorar o desempenho de um sistema é um refactoring?
29
3. (a) Qual transformação de código foi realizada no programa Java abaixo? (b) Ela é um refactoring? Justifique.
30
class A {
void f(){ print("oi");}
}
class B extends A {
...
}
class C {
void f(){ print("olá");}
}
main() {
B b = new B();
b.f();
}
class A {
void f(){ print("oi");}
}
class B extends A {
void f(){ print("olá");}
}
class C {
...
}
main() {
B b = new B();
b.f();
}
4. (a) Qual transformação de código foi realizada no programa Java abaixo? (b) Ela é um refactoring? Justifique.
31
package pacote1;
public class A {
void n() { (new B()).m("abc"); }
package pacote1;
public class B {
public void m(Object o) {…}
void m(String s) {…}
}
package pacote1;
public class A {
void n() { (new B()).m("abc"); }
package pacote2;
public class B {
public void m(Object o) {…}
void m(String s) {…}
}
pacote1/A.java
pacote1/A.java
pacote1/B.java
pacote2/B.java
Prática de Refactorings
32
Refactorings & Testes
“Desenvolvedores evitam refatorações em sistemas sem testes.
Em vez disso, eles reduzem as modificações àquelas necessárias para implementar novas funcionalidades ou corrigir bugs.
Assim, a complexidade vai se acumulando e erros de projeto não são corrigidos.”
-- John Ousterhout
33
Quando refatorar?
34
Refactorings Oportunistas
35
Refactorings Planejados
36
Refactorings Automatizados
37
38
39
Outros refactorings do VSCode
40
41
IntelliJ IDEA
https://www.jetbrains.com/help/idea/refactoring-source-code.html
Mais um refactoring: Remoção de Código Morto
42
Estudo de Caso: Meta/Facebook
43
https://engineering.fb.com/2023/10/24/data-infrastructure/automating-dead-code-cleanup/
Estatísticas de Uso
44
Frase original do artigo (já que os números acima são muito altos):
SCARF has grown to analyze hundreds of millions of lines of code; and five years on, it has automatically deleted more than 100 million lines of code in over 370,000 change requests.
Exercícios
45
1. Qual a relação entre a seguinte frase e a prática de refactoring?
46
“Para cada mudança que tiver que realizar em um sistema, primeiro torne-a fácil (aviso: isso pode ser difícil), então realize a mudança facilmente.” - Kent Beck
2. Normalmente, a realização de um refactoring depende de certas pré-condições. Por exemplo:
(a) Quando não é possível renomear uma variável local de nome a para ter o nome b?
(b) Quando não é possível mover um método f de uma classe A para uma classe B?
47
Exercício sobre design de software, testabilidade e refatoração
Primeiro, estude o código a seguir
48
49
import static javax.swing.JOptionPane.showMessageDialog;
class Dashboard {
private Stock stock;
public Dashboard(Stock stock) {
this.stock = stock;
}
public void alert() {
showMessageDialog(null, "New price " + stock.getName() + " " +
stock.getPrice());
}
}
50
class Stock {
private String name;
private float price;
private Dashboard dashboard;
public Stock(String name, float price) {
this.name = name;
this.price = price;
}
public void setDashboard(Dashboard dashboard) {
this.dashboard = dashboard;
}
public String getName() {
return this.name;
}
public float getPrice() {
return this.price;
}
51
// continuação da classe Stock
public void updatePrice(float price) {
this.price = price;
dashboard.alert();
}
}
public class Main {
public static void main(String[] args) {
Stock stock = new Stock("PETR", 40);
Dashboard dashboard = new Dashboard(stock);
stock.setDashboard(dashboard);
stock.updatePrice(50);
}
}
52
Exercício
53
Resposta
54
55
interface StockObserver {
public void alert();
}
class Dashboard implements StockObserver {
...
public void alert() {
showMessageDialog(null, ...);
}
}
class Stock { ...
private StockObserver observer;
public void setObserver(StockObserver observer) {
this.observer = observer;
}
...
public void updatePrice(float price) {
this.price = price;
observer.alert();
}
}
56
public class Main {
public static void main(String[] args) {
Stock stock = new Stock("PETR", 40);
Dashboard dashboard = new Dashboard(stock);
stock.setObserver(dashboard);
stock.updatePrice(50);
}
}
Resumindo
57
Code Smells
58
Code (ou Bad) Smells
59
Catálogo de Code Smells
60
Código Duplicado
61
Código Duplicado
62
63
Aiko Yamashita, Leon Moonen. Do developers care about code smells? An exploratory survey. WCRE 2013.
Código Duplicado ⇒ Clones
64
Clone Tipo 1 (comentários e espaços)
65
Código Original
Clone Tipo 2 (tipo 1 + nomes diferentes)
66
Código Original
Clone Tipo 3 (tipo 2 + mudanças em comandos)
67
Código Original
Clone Tipo 4 (algoritmos diferentes, mas equivalentes)
68
Código Original
Exercício: Qual o tipo dos seguintes clones?
69
70
(a)
Fonte: https://arxiv.org/abs/2107.13614 (incluindo próximos slides)
71
(b)
72
(c)
73
(d)
74
(e)
Pergunta adicional: vale a pena eliminar este clone?
Ferramentas para Detecção de Clones (ex.: SonarQube)
75
https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition#duplication
Type-2
76
Por que será que a quantidade de clones está aumentando?
Don't DRY Your Code Prematurely
77
DRY = Don’t Repeat Yourself
While functions may look the same, they may also serve requirements that evolve differently over time.
78
Duplicação "tolerável" ⇒ entidades diferentes
79
# Repetitive but allows for clear, entity-specific
# logic and future changes.
def set_task_deadline(task_deadline):
if task_deadline <= datetime.now():
raise ValueError(“Date must be in the future”)
def set_payment_deadline(payment_deadline):
if payment_deadline <= datetime.now():
raise ValueError(“Date must be in the future”)
✅
✅
Feature Envy
80
Feature Envy
81
82
Variáveis Globais
83
Variáveis Globais
84
Dependendo de quem chamou f, o resultado pode ser diferente!
Obsessão por Tipos Primitivos
85
Obsessão por Tipos Primitivos em 1 tweet
86
Obsessão por Tipos Primitivos
87
Objetos Mutáveis
88
Objetos Mutáveis vs Imutáveis
89
Exercício: (a) O que será impresso pelo seguinte programa Java? Justifique. (b) Strings em Java são imutáveis ou não?
90
class Main {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = s1.toUpperCase();
System.out.println(s1);
System.out.println(s2);
}
}
Exercício: (a) O que será impresso pelo seguinte programa em C++? (b) Strings em C++ são imutáveis ou não?
91
#include <iostream>
#include <string>
int main() {
std::string s = "bola";
s[2] = 'c';
std::cout << s;
}
Por que objetos imutáveis são "bons"?
92
Como interpretar esse code smell
93
94
Totalmente ilustrativo, apenas para fins didáticos
numeros = [1, 2, 3]
numeros[0] = 10
numeros.append(4)
del numeros[1]
print(numeros)
⇒[10, 3, 4]
96
Python
numeros = [1, 2, 3]
numeros[0] = 10
numeros.append(4)
del numeros[1]
print(numeros)
⇒[10, 3, 4]
numeros = [1, 2, 3]
numeros2 = [10 | tl(numeros)]
numeros3 = numeros2 ++ [4]
numeros4 = List.delete_at(numeros3, 1)
IO.inspect(numeros)
IO.inspect(numeros2)
IO.inspect(numeros3)
IO.inspect(numeros4)
⇒[1, 2, 3]
⇒[10, 2, 3]
⇒[10, 2, 3, 4]
⇒[10, 3, 4]
97
Python
Elixir
Comentários
98
A ideia é a seguinte:
"não comente código ruim, reescreva-o"
-- B. Kerninghan & P. J. Plauger
99
100
101
Comentários "Ruído"
(nada acrescentam …)
102
Exemplo #1
103
// this function sends an email
void sendEmail() {
...
}
// this class holds data for an employee
public class Employee {
...
}
Exemplo #2
104
Comentários apenas repetem o que já está bem claro no código
Fonte: A Philosophy of Software Design (capítulo 13)
Evidentemente, nem todo comentário é um code smell ...
105
/**
* Returns a string that is a substring of this string. The
* substring begins at the specified {@code beginIndex} and
* extends to the character at index {@code endIndex - 1}.
* Thus the length of the substring is {@code endIndex-beginIndex}.
* <p>
* Examples:
* <blockquote><pre>
* "hamburger".substring(4, 8) returns "urge"
* "smiles".substring(1, 5) returns "mile"
* </pre></blockquote>
*
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* @return the specified substring.
* @throws IndexOutOfBoundsException if the
* {@code beginIndex} is negative, or
* {@code endIndex} is larger than the length of
* this {@code String} object, or
* {@code beginIndex} is larger than
* {@code endIndex}.
*/
public String substring(int beginIndex, int endIndex) { ... }
> javadoc -d docs String.java
107
Mesmo exemplo, agora em Python
(usando docstrings)
110
def substring(s: str, begin_index: int, end_index: int) -> str:
"""
Returns a string that is a substring of the input string. The
substring begins at the specified `begin_index` and
extends to the character at index `end_index - 1`.
Thus the length of the substring is `end_index - begin_index`.
Examples:
substring("hamburger", 4, 8) returns "urge"
substring("smiles", 1, 5) returns "mile"
Parameters:
s (str): The input string.
begin_index (int): The beginning index, inclusive.
end_index (int): The ending index, exclusive.
Returns:
str: The specified substring.
Raises:
IndexError: If `begin_index` is negative, or
`end_index` is larger than the length of the string, or
`begin_index` is larger than `end_index`.
"""
111
Docstrings: strings de documentação após a definição de funções, classes ou módulos.
Ou seja, não são comentários
112
Documentar também código complexo por natureza
113
// format matched kk:mm:ss EEE, MMM dd, yyy
Pattern timePattern = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w*, \\d*, \\d*");
Exercícios
114
1. Seja a seguinte classe Preco. Uma vantagem é que ela pode incluir um método (não mostrado) para converter o valor para outras moedas, como dólar. (a) Por que seus objetos são mutáveis? (b) Reimplemente a classe para que seus objetos sejam imutáveis.
115
class Preco {
...
private double valor = 0.0;
void incrementa(double quant) {
this.valor+= quant;
}
...
}
Resposta em Java
final class Preco { // final: proíbe subclasses
...
private final double valor; // inicializado uma única vez
// (normalmente, na construtora)
public Preco(double valor) {
this.valor = valor;
}
Preco incrementa(double quant) {
return new Preco(this.valor + quant);
}
...
}
116
Resposta em Java, usando records
public record Preco(double valor) {
public Preco incrementa(double quant) {
return new Preco(valor + quant);
}
}
117
2. Nos próximos três slides, mostramos o código de uma função de um sistema de código aberto, chamado FitNesse, a qual é usada também em um dos exemplos do livro Clean Code.
(a) Qual code smell existe nesta função?
(b) Qual o principal refactoring que elimina esse smell?
Observação: se preferir, o código da função está aqui.
118
119
public static String testableHtml(
PageData pageData,
boolean includeSuiteSetup
) throws Exception {
WikiPage wikiPage = pageData.getWikiPage();
StringBuffer buffer = new StringBuffer();
if (pageData.hasAttribute("Test")) {
if (includeSuiteSetup) {
WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_SETUP_NAME, wikiPage
);
if (suiteSetup != null) {
WikiPagePath pagePath =
suiteSetup.getPageCrawler().getFullPath(suiteSetup);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -setup .")
.append(pagePathName)
.append("\n");
}
}
120
WikiPage setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
if (setup != null) {
WikiPagePath setupPath = wikiPage.getPageCrawler().getFullPath(setup);
String setupPathName = PathParser.render(setupPath);
buffer.append("!include -setup .")
.append(setupPathName)
.append("\n");
}
}
buffer.append(pageData.getContent());
if (pageData.hasAttribute("Test")) {
WikiPage teardown = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
if (teardown != null) {
WikiPagePath tearDownPath =
wikiPage.getPageCrawler().getFullPath(teardown);
String tearDownPathName = PathParser.render(tearDownPath);
buffer.append("!include -teardown .")
.append(tearDownPathName)
.append("\n");
}
121
if (includeSuiteSetup) {
WikiPage suiteTeardown = PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_TEARDOWN_NAME,
wikiPage
);
if (suiteTeardown != null) {
WikiPagePath pagePath =
suiteTeardown.getPageCrawler().getFullPath(suiteTeardown);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -teardown .")
.append(pagePathName)
.append("\n");
}
}
}
pageData.setContent(buffer.toString());
return pageData.getHtml();
}
Fim
122