Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refactoring Legacy JavaScript Code to Use Classes: The Good, The Bad and The Ugly (ICSR 2017)

Refactoring Legacy JavaScript Code to Use Classes: The Good, The Bad and The Ugly (ICSR 2017)

JavaScript systems are becoming increasingly complex and large. To tackle the challenges involved in implementing these systems, the language is evolving to include several constructions for programmingin-the-large. For example, although the language is prototype-based, the latest JavaScript standard, named ECMAScript 6 (ES6), provides native support for implementing classes. Even though most modern web browsers support ES6, only a very few applications use the class syntax. In this paper, we analyze the process of migrating structures that emulate classes in legacy JavaScript code to adopt the new syntax for classes introduced by ES6. We apply a set of migration rules on eight legacy JavaScript systems. In our study, we document: (a) cases that are straightforward to migrate (the good parts); (b) cases that require manual and ad-hoc migration (the bad parts); and (c) cases that cannot be migrated due to limitations and restrictions of ES6 (the ugly parts). Six out of eight systems (75%) contain instances of bad and/or ugly cases. We also collect the perceptions of JavaScript developers about migrating their code to use the new syntax for classes.

ASERG, DCC, UFMG

May 31, 2017
Tweet

More Decks by ASERG, DCC, UFMG

Other Decks in Research

Transcript

  1. Refactoring Legacy JavaScript
    Code to Use Classes: The Good,
    The Bad and The Ugly
    Leonardo Silva (IFNMG, Brazil)
    Marco Túlio Valente (UFMG, Brazil)
    Alexandre Bergel (University of Chile)
    ICSR, Salvador 2017

    View Slide

  2. JavaScript is dominating the world…
    2
    http://gittrends.io/

    View Slide

  3. The language is prototype-based
    3
    But it is possible to emulate classes in
    JavaScript
    JavaScript

    View Slide

  4. 4
    4
    function Circle (radius) { // class
    this.radius = radius; // attribute
    }
    Circle.prototype.getArea = function() { // method
    return (3.14 * this.radius * this.radius)
    }
    var myCircle = new Circle (10); // object
    Classes in legacy JavaScript (ES5)

    View Slide

  5. 5
    5
    class Circle { // class
    constructor(radius) {
    this.radius = radius; // attribute
    }
    getArea() { // method
    return (3.14 * this.radius * this.radius)
    }
    }
    var myCircle = new Circle (10); // object
    Classes in ES6
    5

    View Slide

  6. 6
    Motivation
    1. There is a large codebase of legacy JavaScript code
    2. Developers frequently emulate classes
    3. Developers are not fully aware of ES6 new features
    Identifying Classes in Legacy JavaScript Code. Journal of Software: Evolution and Process,2017
    Growing a language: An empirical study on how (and why) developers use some recently-
    introduced and/or recently-evolving JavaScript features. Journal of Systems and Software, 2016

    View Slide

  7. 7
    Goal
    We report a study on refactoring emulated classes
    in legacy JavaScript code to native ES6 structures

    View Slide

  8. 8
    Empirical Study
    We propose and evaluate three rules to migrate
    classes from ES5 to ES6 syntax

    View Slide

  9. Migration Process
    9
    legacy
    code
    ES6
    Rule #1 Rule #2 Rule #3
    migrated
    code

    View Slide

  10. 10
    Migration Process
    10
    Rule #1 Rule #2 Rule #3
    legacy
    code
    ES6
    migrated
    code

    View Slide

  11. 11
    Migration Process
    11
    Rule #1 Rule #2 Rule #3
    11
    ES6
    migrated
    code
    legacy
    code

    View Slide

  12. 12
    Migration Process
    12
    Rule #1 Rule #2 Rule #3
    12
    12
    ES6
    migrated
    code
    legacy
    code

    View Slide

  13. 13
    Migration Process
    migrated
    code
    ES6
    Rule #1 Rule #2 Rule #3
    13
    legacy
    code

    View Slide

  14. 14
    Migration Rule #1 - Classes
    function C(..) {
    this.m1 = function(..) { … }

    }
    C.prototype.m2 = function(..) { … }
    C.m3 = function(p3) { … }
    class C {
    constructor(..) { … }
    m1(..) { … }
    m2(..) { … }
    static m3(..) { … }
    }
    legacy
    code
    ES6

    View Slide

  15. 15
    Migration Rule #2 - Subclasses
    class C {

    }
    C.prototype = new D();
    class C extends D {

    }
    legacy
    code
    ES6

    View Slide

  16. 16
    Migration Rule #3 - super() calls
    class C extends D {
    constructor(..) {
    D.call(this, p1);

    }

    }
    class C extends D {
    constructor(..) {
    super(p1);

    }

    }
    legacy
    code
    ES6

    View Slide

  17. The Good Parts
    Cases that are straightforward to migrate
    17
    The Bad Parts
    Cases that require manual and ad-hoc migration
    The Ugly Parts
    Cases that cannot be migrated due to limitations of ES6
    Migration Process

    View Slide

  18. Dataset
    18
    System
    Checkout
    Date
    LOC Classes Methods
    Class
    Density
    Fastclick 01-Sep-16 846 1 16 0.74
    Grunt 30-Aug-16 1.895 1 16 0.16
    Slick 24-Aug-16 2.905 1 94 0.90
    Parallax 31-Aug-16 1.018 2 56 0.95
    Socket.io 25-Aug-16 1.408 4 59 0.95
    Isomer 02-Sep-16 990 7 35 0.79
    Algorithms.js 21-Aug-16 4.437 20 101 0.54
    Pixi.js 05-Sep-16 23.952 83 518 0.71

    View Slide

  19. The Bad Parts
    19
    1. Accessing this before super
    2. Calling class constructors without new
    3. Hoisting
    4. Alias for method names

    View Slide

  20. 20
    Bad #1: Accessing this before super
    // Legacy code
    function MinHeap(compareFn) {
    this._comparator = compareFn;
    ...
    }
    function PriorityQueue() {
    MinHeap.call(this, function(a, b) {
    return this.priority(a) < this.priority(b) ? -1 : 1;
    });
    ...
    }
    PriorityQueue.prototype = new MinHeap();

    View Slide

  21. 21
    // Migrated code
    class MinHeap {
    ...
    setComparator(compareFn) {
    this._comparator = compareFn;
    }
    }
    class PriorityQueue extends MinHeap {
    constructor() {
    super();
    this.setComparator(
    (function(a, b) {
    return this.priority(a) < this.priority(b) ? -1 : 1;
    }).bind(this));
    ...
    }
    }
    21
    Bad #1: Accessing this before super

    View Slide

  22. 22
    Bad #2: Calling class constructors without new
    // Class constructor (legacy code)
    function Server(srv, opts){
    if (!(this instanceof Server))
    return new Server(srv, opts);
    }
    // Client code
    var io = Server();
    // or
    var io = new Server();

    View Slide

  23. 23
    // Migrated code
    class _Server{
    constructor(srv, opts) { … }

    }
    function Server(srv, opts) {
    if (!(this instanceof _Server))
    return new _Server(srv, opts);
    }
    Bad #2: Calling class constructors without new

    View Slide

  24. 24
    Bad #3: Hoisting
    // Legacy code
    var _tempDisplay = new DisplayObject();
    DisplayObject.prototype.getBounds = function(..) {
    ...
    this.parent = _tempDisplay;
    }

    View Slide

  25. 25
    // Migrated code
    var _tempDisplay = null;
    class DisplayObject {
    ...
    }
    _tempDisplay = new DisplayObject();
    Bad #3: Hoisting

    View Slide

  26. 26
    Bad #4: Alias for method names
    // Legacy code
    Slick.prototype.addSlide =
    Slick.prototype.slickAdd =
    function(markup, index, addBefore) { ... };

    View Slide

  27. 27
    // Migrated code
    class Slick {
    ...
    slickAdd(markup, index, addBefore) { ... }
    // Method alias
    addSlide(markup, index, addBefore) {
    return slickAdd(markup, index, addBefore); }
    }
    Bad #4: Alias for method names

    View Slide

  28. 28
    The Bad Parts - Summary
    1. Accessing this before super
    algorithms.js (2)
    pixi.js (1)
    2. Calling class constructors
    without new
    socket.io (1)
    3. Hoisting algorithms.js (3)
    pixi.js (80)
    4. Alias for method names
    Slick (25)
    socket.io (8)
    pixi.js (6)

    View Slide

  29. 29
    The Ugly Parts
    1. Getters and setters only known at runtime 

    (meta-programming)
    2. Static data properties
    3. Optional features

    View Slide

  30. 30
    Ugly #1: Getters and setters only known at runtime
    // Legacy code
    flags.forEach(function(flag){
    Socket.prototype.__defineGetter__(flag,
    function(){ ... });
    });
    30

    View Slide

  31. 31
    // Legacy code
    Parallax.prototype.ww = null;
    Parallax.prototype.orientationStatus = 0;
    31
    31
    Ugly #2: Static data properties

    View Slide

  32. 32
    Ugly #3: Optional features
    // Legacy code
    var core = require('../core');
    core.Container.prototype.getChildByName =
    function (name) { ... };
    32
    32
    32

    View Slide

  33. 33
    The Ugly Parts - Summary
    1. Getters and setters only known
    at runtime (meta-programming)
    socket.io (5)
    2. Static data properties parallax (28)
    pixi.js (14)
    3. Optional features pixi.js (6)
    33

    View Slide

  34. 34
    Feedback from Developers
    34

    View Slide

  35. 35
    Created Pull Requests
    System ID # Comments
    Opening
    Date
    Status on
    12-Oct-2016
    Fastclick #500 0 01-Sep-16 Open
    Grunt #1549 2 31-Aug-16 Closed
    Slick #2494 5 25-Aug-16 Open
    Parallax #159 1 01-Sep-16 Open
    Socket.io #2661 4 29-Aug-16 Open
    Isomer #87 3 05-Sep-16 Closed
    Algorithms.js #117 4 23-Aug-16 Open
    Pixi.js #2936 14 09-Sep-16 Merged

    View Slide

  36. 36
    System ID # Comments
    Opening
    Date
    Status on
    12-Oct-2016
    Fastclick #500 0 01-Sep-16 Open
    Grunt #1549 2 31-Aug-16 Closed
    Slick #2494 5 25-Aug-16 Open
    Parallax #159 1 01-Sep-16 Open
    Socket.io #2661 4 29-Aug-16 Open
    Isomer #87 3 05-Sep-16 Closed
    Algorithms.js #117 4 23-Aug-16 Open
    Pixi.js #2936 14 09-Sep-16 Merged
    36
    • No  comments  
    • Last  commit  =  April,  2016  
    • Repository  sparsely  maintained
    Created Pull Requests

    View Slide

  37. 37
    System ID # Comments
    Opening
    Date
    Status on
    12-Oct-2016
    Fastclick #500 0 01-Sep-16 Open
    Grunt #1549 2 31-Aug-16 Closed
    Slick #2494 5 25-Aug-16 Open
    Parallax #159 1 01-Sep-16 Open
    Socket.io #2661 4 29-Aug-16 Open
    Isomer #87 3 05-Sep-16 Closed
    Algorithms.js #117 4 23-Aug-16 Open
    Pixi.js #2936 14 09-Sep-16 Merged
    37
    “We  currently  support  node  0.10  that  does  
    not  support  this  syntax.  Once  we  are  able  to  
    drop  node  0.10  we  might  revisit  this.”
    37
    Created Pull Requests

    View Slide

  38. 38
    System ID # Comments
    Opening
    Date
    Status on
    12-Oct-2016
    Fastclick #500 0 01-Sep-16 Open
    Grunt #1549 2 31-Aug-16 Closed
    Slick #2494 5 25-Aug-16 Open
    Parallax #159 1 01-Sep-16 Open
    Socket.io #2661 4 29-Aug-16 Open
    Isomer #87 3 05-Sep-16 Closed
    Algorithms.js #117 4 23-Aug-16 Open
    Pixi.js #2936 14 09-Sep-16 Merged
    38
    38
    38
    Created Pull Requests
    • The  comments  suggest  that  they  are  
    under  evalua*on

    View Slide

  39. 39
    System ID # Comments
    Opening
    Date
    Status on
    12-Oct-2016
    Fastclick #500 0 01-Sep-16 Open
    Grunt #1549 2 31-Aug-16 Closed
    Slick #2494 5 25-Aug-16 Open
    Parallax #159 1 01-Sep-16 Open
    Socket.io #2661 4 29-Aug-16 Open
    Isomer #87 3 05-Sep-16 Closed
    Algorithms.js #117 4 23-Aug-16 Open
    Pixi.js #2936 14 09-Sep-16 Merged
    39
    “IMHO  the  class  syntax  is  misleading,  as  
    JS  ‘classes’  are  not  actually  classes.  Using  
    prototypal  paGerns  seems  like  a  simpler  way  to  
    do  inheritance.”
    39
    39
    39
    Created Pull Requests

    View Slide

  40. 40
    System ID # Comments
    Opening
    Date
    Status on
    12-Oct-2016
    Fastclick #500 0 01-Sep-16 Open
    Grunt #1549 2 31-Aug-16 Closed
    Slick #2494 5 25-Aug-16 Open
    Parallax #159 1 01-Sep-16 Open
    Socket.io #2661 4 29-Aug-16 Open
    Isomer #87 3 05-Sep-16 Closed
    Algorithms.js #117 4 23-Aug-16 Open
    Pixi.js #2936 14 09-Sep-16 Merged
    40
    “Awesome  work!  It  is  really  great  Kming  because  we  
    were  planning  on  doing  this  very  soon  anyways.”
    40
    40
    40
    40
    40
    Created Pull Requests

    View Slide

  41. Final Remarks
    • Bad / Ugly cases are present in 75% of the studied systems
    • Moving from ES5 to ES6 classes can be challenging
    • Developers tend to move to ES6
    • There are demands for new class-related features in ES6
    • We used the lessons learnt to develop a refactoring tool
    41

    View Slide

  42. [email protected]
    Thank you!!!

    View Slide