1 of 122

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

2 of 122

Relembrando Cap. 1 (Introdução)

2

3 of 122

Manutenção de Software

  • Preventiva
  • Corretiva
  • Adaptativa
  • Evolutiva
  • Refactoring: melhorias no código ou design

3

4 of 122

Refactoring

  • Transformações de código que melhoram a manutenibilidade de um sistema mas sem afetar o seu funcionamento externo

4

5 of 122

Conceito tornou-se bastante popular

5

2018

2000

1999

6 of 122

Catálogo de Refactorings

  • Extração de Métodos
  • Inline de Métodos
  • Movimentação de Métodos
  • Extração de Classes
  • Renomeação
  • etc

6

7 of 122

Extração de Métodos

7

8 of 122

Extração de Métodos

8

9 of 122

Um exemplo real ...

9

10 of 122

10

Antes

11 of 122

11

Depois

12 of 122

O que se ganha ao extrair um método?

Quais as vantagens de extração de métodos?

12

13 of 122

Motivações para Extração de Métodos

  • Dividir um método grande em métodos menores
  • Remover duplicação de código
  • Em relação ao método extraído, permitir o seu:
    • Reúso
    • Teste
    • Redefinição em subclasses
    • Chamada recursiva

13

14 of 122

Inline de Métodos (contrário de extração)

14

15 of 122

15

Antes

16 of 122

16

Depois

17 of 122

Movimentação de Métodos

17

18 of 122

18

19 of 122

Casos particulares de movimentação

(ao longo de uma hierarquia de classes)

19

20 of 122

Pull Up Method

20

21 of 122

Push Down Method

21

22 of 122

Extração de Classes

22

23 of 122

23

24 of 122

Qual o refactoring mais comum?

24

25 of 122

Renomeação

(variáveis, parâmetros, métodos, classes, exceções, etc)

(+50% das operações de refactoring)

25

26 of 122

Dar bons nomes a variáveis é um dos problemas mais difíceis em programação!

26

27 of 122

Exercícios

27

28 of 122

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

29 of 122

2. Uma mudança no código que foi feita para melhorar o desempenho de um sistema é um refactoring?

29

30 of 122

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();

}

31 of 122

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

32 of 122

Prática de Refactorings

32

33 of 122

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

34 of 122

Quando refatorar?

  1. Refactorings oportunistas
  2. Refactorings planejados

34

35 of 122

Refactorings Oportunistas

  • Realizados no meio de uma outra tarefa de programação
  • Tipo de refactoring mais comum

35

36 of 122

Refactorings Planejados

  • Correção de um problema de design mais complexo
  • Sessão apenas para realização de refactorings

36

37 of 122

Refactorings Automatizados

37

38 of 122

38

39 of 122

39

40 of 122

Outros refactorings do VSCode

40

41 of 122

41

IntelliJ IDEA

https://www.jetbrains.com/help/idea/refactoring-source-code.html

42 of 122

Mais um refactoring: Remoção de Código Morto

  • Código que não está sendo mais usado
  • Mais comum do que a gente imagina…

42

43 of 122

Estudo de Caso: Meta/Facebook

  • Possui uma ferramenta interna para remover código morto

43

https://engineering.fb.com/2023/10/24/data-infrastructure/automating-dead-code-cleanup/

44 of 122

Estatísticas de Uso

  • Ferramenta já foi usada para analisar centenas de MLOC
  • Em 5 anos, ajudou a deletar +100 MLOC, via 370K PR

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.

45 of 122

Exercícios

45

46 of 122

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

47 of 122

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

48 of 122

Exercício sobre design de software, testabilidade e refatoração

Primeiro, estude o código a seguir

48

49 of 122

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 of 122

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 of 122

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 of 122

  • No código anterior, existe uma dependência circular direta entre as classes Dashboard e Stock
  • Dependências circulares são indicadores de problemas de design e testabilidade
  • Por exemplo, por que é difícil escrever um teste de unidade para o método updatePrice() de Stock?

52

53 of 122

Exercício

  • Refatore o código de modo a remover a dependência circular entre as duas classes.
  • Por que agora ficou mais fácil escrever um teste de unidade para updatePrice()?

53

54 of 122

Resposta

54

55 of 122

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 of 122

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);

}

}

57 of 122

Resumindo

57

58 of 122

Code Smells

58

59 of 122

Code (ou Bad) Smells

  • Indicadores de código de baixa qualidade
  • Difícil de manter, entender, modificar ou testar
  • Portanto, candidato a refatoração

59

60 of 122

Catálogo de Code Smells

  • Código Duplicado
  • Métodos Longos
  • Classes Grandes
  • Feature Envy
  • Métodos com Muitos Parâmetros
  • Variáveis Globais

  • Obsessão por Tipos Primitivos
  • Objetos Mutáveis
  • Classes de Dados
  • Comentários

60

61 of 122

Código Duplicado

61

62 of 122

Código Duplicado

  • Dificulta a manutenção
  • Portanto, código candidato a refatoração

62

63 of 122

63

Aiko Yamashita, Leon Moonen. Do developers care about code smells? An exploratory survey. WCRE 2013.

64 of 122

Código Duplicado ⇒ Clones

64

65 of 122

Clone Tipo 1 (comentários e espaços)

65

Código Original

66 of 122

Clone Tipo 2 (tipo 1 + nomes diferentes)

66

Código Original

67 of 122

Clone Tipo 3 (tipo 2 + mudanças em comandos)

67

Código Original

68 of 122

Clone Tipo 4 (algoritmos diferentes, mas equivalentes)

68

Código Original

69 of 122

Exercício: Qual o tipo dos seguintes clones?

69

70 of 122

70

(a)

Fonte: https://arxiv.org/abs/2107.13614 (incluindo próximos slides)

71 of 122

71

(b)

72 of 122

72

(c)

73 of 122

73

(d)

74 of 122

74

(e)

Pergunta adicional: vale a pena eliminar este clone?

75 of 122

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 of 122

76

Por que será que a quantidade de clones está aumentando?

77 of 122

Don't DRY Your Code Prematurely

77

DRY = Don’t Repeat Yourself

78 of 122

While functions may look the same, they may also serve requirements that evolve differently over time.

78

79 of 122

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”)

80 of 122

Feature Envy

80

81 of 122

Feature Envy

  • Método que "inveja" dados e métodos de outra classe
  • Usa mais métodos e dados dessa outra classe
  • Logo, é um candidato a ser movido para ela

81

82 of 122

82

83 of 122

Variáveis Globais

83

84 of 122

Variáveis Globais

  • Acoplamento ruim
  • Dificulta o entendimento de um método

84

Dependendo de quem chamou f, o resultado pode ser diferente!

85 of 122

Obsessão por Tipos Primitivos

85

86 of 122

Obsessão por Tipos Primitivos em 1 tweet

86

87 of 122

Obsessão por Tipos Primitivos

  • CEP, Moeda, Data, Hora, Cor, Email, etc não devem ser tipos primitivos
  • Mas sim de um tipo próprio, com alguns métodos
  • Por exemplo, métodos para validação de valores
  • Algumas vezes, são chamados de Value Objects.

87

88 of 122

Objetos Mutáveis

88

89 of 122

Objetos Mutáveis vs Imutáveis

  • Mutáveis: estado pode mudar
  • Imutáveis: uma vez criados, estado não muda

89

90 of 122

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);

}

}

91 of 122

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;

}

92 of 122

Por que objetos imutáveis são "bons"?

  • Dão mais segurança a quem criou o objeto
    • Pode-se passar o objeto para outros métodos e ter certeza de que eles não vão alterar o seu estado
  • Não estão sujeitos a problemas devido a concorrência
    • Não precisamos de sincronizações, locks, mutex, etc

92

93 of 122

Como interpretar esse code smell

  • Sempre que possível:
    • Implemente objetos imutáveis
    • Principalmente, objetos simples (CEP, Data, Hora, etc)
  • Por outro lado:
    • Em linguagens imperativas é natural ter um bom número de objetos mutáveis

93

94 of 122

94

Totalmente ilustrativo, apenas para fins didáticos

95 of 122

95

96 of 122

numeros = [1, 2, 3]

numeros[0] = 10

numeros.append(4)

del numeros[1]

print(numeros)

⇒[10, 3, 4]

96

Python

97 of 122

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

98 of 122

Comentários

98

99 of 122

A ideia é a seguinte:

"não comente código ruim, reescreva-o"

-- B. Kerninghan & P. J. Plauger

99

100 of 122

100

101 of 122

101

102 of 122

Comentários "Ruído"

(nada acrescentam …)

102

103 of 122

Exemplo #1

103

// this function sends an email

void sendEmail() {

...

}

// this class holds data for an employee

public class Employee {

...

}

104 of 122

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)

105 of 122

Evidentemente, nem todo comentário é um code smell ...

105

106 of 122

/**

* 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) { ... }

107 of 122

> javadoc -d docs String.java

107

108 of 122

109 of 122

110 of 122

Mesmo exemplo, agora em Python

(usando docstrings)

110

111 of 122

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 of 122

112

113 of 122

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*");

114 of 122

Exercícios

114

115 of 122

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;

}

...

}

116 of 122

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

117 of 122

Resposta em Java, usando records

public record Preco(double valor) {

public Preco incrementa(double quant) {

return new Preco(valor + quant);

}

}

117

  • Records: recursos para implementação de objetos imutáveis, disponíveis a partir de Java 14
  • Sintaxe mais simples e compacta

118 of 122

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 of 122

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 of 122

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 of 122

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();

}

122 of 122

Fim

122