$30 off During Our Annual Pro Sale. View Details »

Refactoring Graphs: Assessing Refactoring over Time

Refactoring Graphs: Assessing Refactoring over Time

Refactoring is an essential activity during software evolution. Frequently, practitioners rely on such transformations to improve source code maintainability and quality. As a consequence, this process may produce new source code entities or change the structure of existing ones. Sometimes, the transformations are atomic, i.e., performed in a single commit. In other cases, they generate sequences of modifications performed over time. To study and reason about refactorings over time, in this paper, we propose a novel concept called refactoring graphs and provide an algorithm to build such graphs. Then, we investigate the history of 10 popular open-source Java-based projects. After eliminating trivial graphs, we characterize a large sample of 1,150 refactoring graphs, providing quantitative data on their size, commits, age, refactoring composition, and developers. We conclude by discussing applications and implications of refactoring graphs, for example, to improve code comprehension, detect refactoring patterns, and support software evolution studies.

ASERG, DCC, UFMG

February 19, 2020
Tweet

More Decks by ASERG, DCC, UFMG

Other Decks in Research

Transcript

  1. Refactoring Graphs: Assessing
    Refactoring over Time
    Aline Brito, Andre Hora, Marco Tulio Valente
    IEEE SANER 2020

    View Slide

  2. Motivation
    Refactoring is an essential activity during software evolution
    2
    Refactoring
    engines
    Motivation Benefits and
    challenges
    Refactoring
    over time

    View Slide

  3. Motivation
    Refactoring is an essential activity during software evolution
    3
    Refactoring
    engines
    Motivation Benefits and
    challenges
    Refactoring
    over time

    View Slide

  4. Motivation
    Refactoring is an essential activity during software evolution
    4
    Refactoring
    engines
    Motivation Benefits and
    challenges
    Refactoring
    over time

    View Slide

  5. Motivation
    Refactoring is an essential activity during software evolution
    5
    Refactoring
    engines
    Motivation Benefits and
    challenges
    Refactoring
    over time

    View Slide

  6. Motivation
    Refactoring is an essential activity during software evolution
    6
    Refactoring
    engines
    Motivation Benefits and
    challenges
    Refactoring
    over time

    View Slide

  7. Motivation
    Refactoring is an essential activity during software evolution
    7
    Refactoring
    engines
    Motivation Benefits and
    challenges
    Refactoring
    over time

    View Slide

  8. “Refactoring takes time...”
    8
    Fowler, 1999

    View Slide

  9. Refactoring
    Graph
    9

    View Slide

  10. A refactoring graph is a
    set of disconnected
    subgraphs
    10
    Refactoring Graph
    Software history

    View Slide

  11. Example of Refactoring Subgraph
    11

    View Slide

  12. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    Method A()
    from class Foo
    12

    View Slide

  13. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    Method A()
    from class Foo
    Alice Bob
    Two developers
    13

    View Slide

  14. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    MOVE
    Alice moved
    method A() from class Foo to Bar
    14

    View Slide

  15. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    MOVE RENAME
    Six days later, Bob renamed
    method A() to B()
    15

    View Slide

  16. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    MOVE
    These operations create a
    refactoring subgraph over time
    RENAME
    16

    View Slide

  17. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    MOVE
    The refactoring subgraph contains three vertices
    RENAME
    17

    View Slide

  18. Example of Refactoring Subgraph
    MOVE RENAME
    The refactoring subgraph contains two edges
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    18

    View Slide

  19. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    MOVE
    The edge represents the refactoring operation
    RENAME
    19

    View Slide

  20. Example of Refactoring Subgraph
    MOVE
    util.Foo#A() util.Bar#A() util.Bar#B()
    The vertices are the full signature of methods
    RENAME
    20

    View Slide

  21. Example of Refactoring Subgraph
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    MOVE
    Method A() from class Foo and
    package util
    util.Foo#A()
    RENAME
    21

    View Slide

  22. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    MOVE
    A refactoring subgraph can include refactorings
    performed by one or more developers
    RENAME
    22

    View Slide

  23. Example of Refactoring Subgraph
    class Foo{
    A(){…}
    }
    class Bar{
    A(){…}
    }
    class Bar{
    B(){…}
    }
    MOVE
    The subgraph contains refactorings
    performed by two authors
    RENAME
    23

    View Slide

  24. Outline
    1. RefDiff Tool
    2. Dataset
    3. Building refactoring graphs
    4. Results
    24

    View Slide

  25. RefDiff Tool
    25

    View Slide

  26. RefDiff
    A multi-language refactoring detection tool
    Refactorings between two versions of a
    git-based project
    26
    RefDiff 2.0: A Multi-language Refactoring Detection Tool
    TSE, 2020

    View Slide

  27. RefDiff
    Rename Extract
    Move Extract and Move
    Rename and Move Push Down
    Inline Pull Up
    We center on eight refactorings at the method level
    27

    View Slide

  28. Rename
    Method
    util.Foo#a() util.Foo#b()
    util.Foo#m() util.Bar#m()
    util.Foo#a() util.Bar#b()
    Move and
    Rename
    Method
    Move
    Method
    Most trivial operations
    28

    View Slide

  29. Rename
    Method
    util.Foo#a() util.Foo#b()
    util.Foo#m() util.Bar#m()
    util.Foo#a() util.Bar#b()
    Move and
    Rename
    Method
    Move
    Method
    Change in method’s name
    29

    View Slide

  30. Rename
    Method
    util.Foo#a() util.Foo#b()
    util.Foo#a() util.Bar#b()
    Move and
    Rename
    Method
    Change in method’s class
    util.Foo#m() util.Bar#m()
    Move
    Method
    30

    View Slide

  31. util.Foo#m() util.Bar#m()
    Move
    Method
    Change in method’s name and class
    Rename
    Method
    util.Foo#a() util.Foo#b()
    util.Foo#a() util.Bar#b()
    Move and
    Rename
    Method
    31

    View Slide

  32. Push
    Down
    Method
    util.SubFoo2#m()
    util.SuperFoo#m()
    util.SubFoo1#m()
    util.SubFooi#m()
    Pull up
    Method
    util.SubFoo2#m() util.SuperFoo#m()
    util.SubFoo1#m()
    util.SubFooi#m()
    32

    View Slide

  33. Push
    Down
    Method
    util.SubFoo2#m()
    util.SuperFoo#m()
    util.SubFoo1#m()
    util.SubFooi#m()
    Moving a method from a
    superclass to one or more
    subclasses
    33

    View Slide

  34. Pull up
    Method
    util.SubFoo2#m() util.SuperFoo#m()
    util.SubFoo1#m()
    util.SubFooi#m()
    Moving one or more
    methods from subclasses
    to a superclass
    34

    View Slide

  35. Extract Method
    util.Foo#m2()
    util.Foo#m()
    util.Foo#m1()
    util.Foo#mi()
    util.Foo#m2()
    util.Foo#m()
    util.Foo#m1()
    util.Foo#mi()
    35

    View Slide

  36. Extract Method
    util.Foo#m2()
    util.Foo#m()
    util.Foo#m1()
    util.Foo#mi()
    Extracting multiple
    methods from a single
    method
    36

    View Slide

  37. Extract Method
    util.Foo#m2()
    util.Foo#m()
    util.Foo#m1()
    util.Foo#mi()
    Extracting a single method
    from multiple methods
    37

    View Slide

  38. Inline
    Method
    util.Bar2#m2()
    util.Foo#m()
    util.Bar1#m1()
    util.Bari#mi()
    Extract
    and Move
    Method
    util.Foo#m2()
    util.Bar#m()
    util.Foo#m1()
    util.Foo#mi()
    38

    View Slide

  39. Extract
    and Move
    Method
    util.Foo#m2()
    util.Bar#m()
    util.Foo#m1()
    util.Foo#mi()
    Extracting a method
    to a distinct class
    39

    View Slide

  40. Inline
    Method
    util.Bar2#m2()
    util.Foo#m()
    util.Bar1#m1()
    util.Bari#mi()
    Removal of trivial elements
    and replacement of the
    respective calls
    40

    View Slide

  41. Dataset
    41

    View Slide

  42. Dataset
    10 popular Java projects in terms of stars on GitHub
    42

    View Slide

  43. Dataset
    10 popular Java projects in terms of stars on GitHub
    43

    View Slide

  44. Dataset
    + 100 Java files
    + 1K commits
    10 popular Java projects in terms of stars on GitHub
    44

    View Slide

  45. Building Refactoring Graphs
    45

    View Slide

  46. Building Refactoring Graphs
    46
    Scripts
    INPUT OUTPUT
    We implement a set of scripts to build
    refactoring graphs

    View Slide

  47. Building Refactoring Graphs
    47
    Algorithm
    INPUT OUTPUT
    The input comprises a list of refactorings

    View Slide

  48. Building Refactoring Graphs
    48
    INPUT OUTPUT
    Identification of each refactoring and the
    two methods involved
    Algorithm

    View Slide

  49. Building Refactoring Graphs
    49
    INPUT OUTPUT
    Creation of a directed edge representing
    this refactoring
    Algorithm

    View Slide

  50. Building Refactoring Graphs
    50
    Algorithm
    INPUT OUTPUT
    The output includes
    sets of refactoring subgraphs in text format

    View Slide

  51. Building Refactoring Graphs
    51
    We detected a total of
    8,926 refactoring subgraphs

    View Slide

  52. Building Refactoring Graphs
    52
    We assess 1,150 (13%) refactoring subgraphs with
    more than one commit

    View Slide

  53. Results
    53

    View Slide

  54. RQ1: What is the size of
    refactoring subgraphs?
    54

    View Slide

  55. Number of vertices by refactoring subgraph
    55

    View Slide

  56. Number of vertices by refactoring subgraph
    Number of vertices ranges
    from two to four (85%)
    56

    View Slide

  57. Number of vertices by refactoring subgraph
    The most frequent cases are subgraphs with
    three vertices (639 occurrences, 56%)
    57

    View Slide

  58. Number of edges by refactoring subgraph
    58

    View Slide

  59. Number of edges by refactoring subgraph
    Number of edges ranges
    between two and three (83%)
    59

    View Slide

  60. Number of edges by refactoring subgraph
    The most frequent cases are subgraphs with
    two edges (772 occurrences, 67%)
    60

    View Slide

  61. Refactoring subgraph from MPAndroidChart
    RENAME EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    61

    View Slide

  62. Refactoring subgraph from MPAndroidChart
    RENAME EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    A developer renamed
    drawYLegend() to
    drawYLabels()
    62

    View Slide

  63. Refactoring subgraph from MPAndroidChart
    EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    RENAME
    13 days
    later
    The same
    developer
    extracted a new
    method
    63

    View Slide

  64. Refactoring subgraph from MPAndroidChart
    RENAME EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    Two days
    later
    The developer
    made new
    extractions to
    another class
    64

    View Slide

  65. Refactoring subgraph from MPAndroidChart
    RENAME EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    Only one
    developer
    65

    View Slide

  66. Refactoring subgraph from MPAndroidChart
    RENAME EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    Five
    vertices
    66

    View Slide

  67. Refactoring subgraph from MPAndroidChart
    RENAME EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    Four edges
    67

    View Slide

  68. Refactoring subgraph from MPAndroidChart
    RENAME EXTRACT
    EXTRACT
    AND
    MOVE
    EXTRACT
    AND
    MOVE
    Three
    commits
    Commit C1 Commit C2
    Commit C3
    68

    View Slide

  69. RQ2: How many commits are
    there in refactoring subgraphs?
    69

    View Slide

  70. Number of commits by refactoring subgraph
    70

    View Slide

  71. Number of commits by refactoring subgraph
    Most refactoring subgraphs are created in
    two or three commits (95%)
    71

    View Slide

  72. Number of commits by refactoring subgraph
    Most recurrent case has
    two commits (81%)
    72

    View Slide

  73. Refactoring subgraph from Elasticsearch
    MOVE
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Commit C1 Commit C2
    73

    View Slide

  74. Refactoring subgraph from Elasticsearch
    MOVE
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Commit C1
    A developer moved
    two methods to
    another class
    74

    View Slide

  75. Refactoring subgraph from Elasticsearch
    MOVE
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    A second developer
    extracted duplicated
    code from three
    methods
    Three months
    later
    Commit C2
    75

    View Slide

  76. Refactoring subgraph from Elasticsearch
    MOVE
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Two methods are the
    ones moved early
    76

    View Slide

  77. Refactoring subgraph from Elasticsearch
    MOVE
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Two authors
    were
    responsible for
    this refactoring
    subgraph
    77

    View Slide

  78. Refactoring subgraph from Elasticsearch
    MOVE
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Commit C1 Commit C2
    Two
    commits
    78

    View Slide

  79. RQ3: What is the age of
    refactoring subgraphs?
    79

    View Slide

  80. Age of the refactoring subgraphs
    Most recent
    commit
    Oldest
    commit
    Age
    80

    View Slide

  81. Age of the refactoring subgraphs
    81

    View Slide

  82. Age of the refactoring subgraphs
    82
    67% of the refactoring subgraphs have
    more than one month

    View Slide

  83. Age of the refactoring subgraphs
    Some subgraphs
    have few days
    83

    View Slide

  84. Age of the refactoring subgraphs
    Most subgraphs
    have weeks or
    even months
    84

    View Slide

  85. Refactoring subgraph from Spring Framework
    RENAME
    RENAME
    85

    View Slide

  86. Refactoring subgraph from Spring Framework
    RENAME
    RENAME
    Commit C1
    A developer renamed method
    before(...) to filterBefore(...)
    86

    View Slide

  87. Refactoring subgraph from Spring Framework
    RENAME
    The same developer reverted the operation, renaming
    filterBefore(...) to before(...)
    RENAME
    87
    Six days
    later

    View Slide

  88. Refactoring subgraph from Spring Framework
    RENAME
    RENAME
    A single developer
    was responsible for
    this refactoring
    subgraph
    88

    View Slide

  89. Refactoring subgraph from Spring Framework
    RENAME
    RENAME
    Two commits
    Commit C1
    Commit C2
    89

    View Slide

  90. RQ4: Which refactorings
    compose the refactoring
    subgraphs?
    90

    View Slide

  91. Frequency of refactoring operations
    91

    View Slide

  92. Frequency of refactoring operations
    Most common refactoring operations include
    rename (21%), extract and move
    (19%), and extract (17%)
    92

    View Slide

  93. Frequency of refactoring operations
    We detected only 83 occurrences of
    move and rename operations
    93

    View Slide

  94. Frequency of refactoring operations
    There are also few inheritance-based
    refactorings
    94

    View Slide

  95. Homogeneous:
    Subgraphs with a single
    refactoring operation
    Heterogeneous:
    Subgraphs with two or
    more distinct refactoring
    operations
    Two groups:
    95

    View Slide

  96. Heterogeneous vs Homogeneous subgraphs
    96

    View Slide

  97. Heterogeneous x Homogeneous subgraphs
    Most refactoring subgraphs include
    more than one refactoring type
    (72%)
    97

    View Slide

  98. Number of distinct refactoring operations
    Most heterogeneous subgraphs includes two
    distinct refactoring types (84%)
    98

    View Slide

  99. Homogeneous refactoring subgraph from
    Facebook Fresco
    EXTRACT EXTRACT
    EXTRACT
    EXTRACT
    99

    View Slide

  100. Homogeneous refactoring subgraph from
    Facebook Fresco
    EXTRACT EXTRACT
    EXTRACT
    EXTRACT
    Four extract
    method
    operations
    100

    View Slide

  101. Homogeneous refactoring subgraph from
    Facebook Fresco
    EXTRACT EXTRACT
    EXTRACT
    EXTRACT
    A developer
    extracted method
    fetchDecodedImage(...)
    101

    View Slide

  102. Homogeneous refactoring subgraph from
    Facebook Fresco
    EXTRACT EXTRACT
    EXTRACT
    years later
    EXTRACT
    A second
    developer made
    two new extract
    operations
    102

    View Slide

  103. Homogeneous refactoring subgraph from
    Facebook Fresco
    EXTRACT EXTRACT
    EXTRACT
    EXTRACT
    Two
    developers
    103

    View Slide

  104. Homogeneous refactoring subgraph from
    Facebook Fresco
    EXTRACT EXTRACT
    EXTRACT
    EXTRACT
    Commit C1 Commit C2
    Commit C3
    Three
    commits
    104

    View Slide

  105. RQ5: Are the refactoring
    subgraphs created by the same
    or multiple developers?
    105

    View Slide

  106. Subgraphs
    performed by a
    single developer
    Subgraphs created
    by multiple
    developers
    Two groups:
    106

    View Slide

  107. Developers of refactoring subgraphs
    107

    View Slide

  108. Developers of refactoring subgraphs
    Most refactoring subgraphs are created
    by a single developer (60%)
    108

    View Slide

  109. Refactoring subgraph from Square Okhttp
    RENAME
    RENAME
    RENAME
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    109

    View Slide

  110. Refactoring subgraph from Square Okhttp
    RENAME
    RENAME
    RENAME
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Commit C1
    A developer
    renamed three
    methods
    110

    View Slide

  111. Refactoring subgraph from Square Okhttp
    RENAME
    RENAME
    RENAME
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Commit C2
    A second developer
    extracted method
    checkDuration(...)
    111

    View Slide

  112. Refactoring subgraph from Square Okhttp
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Commit C2 Commit C3
    … moving to a
    new class named
    Util
    RENAME
    RENAME
    RENAME
    112

    View Slide

  113. Refactoring subgraph from Square Okhttp
    RENAME
    RENAME
    RENAME
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Two
    developers
    113

    View Slide

  114. Refactoring subgraph from Square Okhttp
    RENAME
    RENAME
    RENAME
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Seven refactoring
    operations
    114

    View Slide

  115. Refactoring subgraph from Square Okhttp
    RENAME
    RENAME
    RENAME
    MOVE
    EXTRACT
    EXTRACT
    EXTRACT
    Commit C3
    Three
    commits
    Commit C2
    Commit C1
    115

    View Slide

  116. Large Subgraph Example
    116

    View Slide

  117. Large refactoring subgraph from Square Okhttp
    117
    37 vertices

    View Slide

  118. Large refactoring subgraph from Square Okhttp
    Push down and
    move method
    operations
    118

    View Slide

  119. Large refactoring subgraph from Square Okhttp
    24 extract and
    move method
    operations
    119

    View Slide

  120. Large refactoring subgraph from Square Okhttp
    21 extract and move
    operations to a
    single method
    120

    View Slide

  121. Large refactoring subgraph from Square Okhttp
    public int readInt() throws IOException {
    require(4, Deadline.NONE);
    return buffer.readInt();
    }
    A developer performed 21 extract method
    operations to move this duplicated code
    to a single method
    121

    View Slide

  122. Implications and Conclusions
    122

    View Slide

  123. Refactoring-aware Software Evolution
    Refactoring Graphs is a
    key data structure to improve the results of
    current software evolution tools
    123

    View Slide

  124. Example: Git Blame
    Show the last author that changed each line of a file
    124

    View Slide

  125. Example: Git Blame
    Bob creates a method to calculate the area of a square
    class Math{
    }
    float squareArea(float l){
    + return l * l * l;
    }
    + float squareArea(float l){
    + return l * l;
    + }
    125

    View Slide

  126. Example: Git Blame
    Git-blame shows Bob
    as a creator
    Bob
    class Math{
    }
    float squareArea(float l){
    + return l * l * l;
    }
    + float squareArea(float l){
    + return l * l;
    + }
    126

    View Slide

  127. Example: Git Blame
    class Math{
    }
    float squareArea(float l){
    + return l * l * l;
    }
    Bob introduces a bug in a second commit
    + return l * l * l;
    l
    127

    View Slide

  128. Example: Git Blame
    class Math{
    }
    float squareArea(float l){
    return l * l;
    }
    + return l * l * l;
    Git-blame shows Bob
    as responsible for the
    last change (bug)
    Bob
    128

    View Slide

  129. Example: Git Blame
    class Math{
    }
    - float squareArea(float l){
    - return l * l * l;
    - }
    Alice moves the method to a utility class
    class Utility{
    }
    + float squareArea(float l){
    + return l * l * l;
    + }
    129

    View Slide

  130. Example: Git Blame
    Git-blame shows
    Alice as creator of
    method squareArea
    Alice
    class Utility{
    }
    float squareArea(float l){
    + return l * l * l;
    }
    + float squareArea(float l){
    + return l * l * l;
    + }
    130

    View Slide

  131. Example: Git Blame
    class Math{
    float squareArea(float l){
    return l * l;
    }
    }
    class Math{
    float squareArea(float l){
    return l * l * l;
    }
    }
    class Utility{
    float squareArea(float l){
    return l * l * l;
    }
    }
    Bob is the real creator of squareArea()
    131

    View Slide

  132. Example: Git Blame
    class Math{
    float squareArea(float l){
    return l * l;
    }
    }
    class Math{
    float squareArea(float l){
    return l * l * l;
    }
    }
    class Utility{
    float squareArea(float l){
    return l * l * l;
    }
    }
    Bob is responsible for the bug
    132

    View Slide

  133. Example: Git Blame
    class Math{
    float squareArea(float l){
    return l * l;
    }
    }
    class Math{
    float squareArea(float l){
    return l * l * l;
    }
    }
    class Utility{
    float squareArea(float l){
    return l * l * l;
    }
    }
    git-blame may miss relevant data due
    to refactoring operations
    133

    View Slide

  134. Example: Git Blame
    class Math{
    float squareArea(float l){
    return l * l;
    }
    }
    class Math{
    float squareArea(float l){
    return l * l * l;
    }
    }
    class Utility{
    float squareArea(float l){
    return l * l * l;
    }
    }
    A refactoring history can improve existing
    tools and techniques
    134

    View Slide

  135. 135
    Future Studies
    Other popular programming languages and ecosystems
    Refactoring graphs based on class and package level

    View Slide

  136. Refactoring subgraphs...
    … are small
    … have up to three
    commits
    … are often heterogeneous
    … are mostly created by a
    single developer
    … span from a few days to months
    RQ1 RQ2
    RQ3
    RQ4 RQ5
    136

    View Slide

  137. Refactoring Graphs: Assessing
    Refactoring over Time
    Aline Brito, Andre Hora, Marco Tulio Valente
    IEEE SANER 2020

    View Slide