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.

13beaa3b7239eca3319d54c6a9f3a85a?s=128

ASERG, DCC, UFMG

May 31, 2017
Tweet

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
  2. JavaScript is dominating the world… 2 http://gittrends.io/

  3. The language is prototype-based 3 But it is possible to

    emulate classes in JavaScript JavaScript
  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)
  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
  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
  7. 7 Goal We report a study on refactoring emulated classes

    in legacy JavaScript code to native ES6 structures
  8. 8 Empirical Study We propose and evaluate three rules to

    migrate classes from ES5 to ES6 syntax
  9. Migration Process 9 legacy code ES6 Rule #1 Rule #2

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

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

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

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

    Rule #3 13 legacy code
  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
  15. 15 Migration Rule #2 - Subclasses class C { …

    } C.prototype = new D(); class C extends D { … } legacy code ES6
  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
  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
  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
  19. The Bad Parts 19 1. Accessing this before super 2.

    Calling class constructors without new 3. Hoisting 4. Alias for method names
  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();
  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
  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();
  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
  24. 24 Bad #3: Hoisting // Legacy code var _tempDisplay =

    new DisplayObject(); DisplayObject.prototype.getBounds = function(..) { ... this.parent = _tempDisplay; }
  25. 25 // Migrated code var _tempDisplay = null; class DisplayObject

    { ... } _tempDisplay = new DisplayObject(); Bad #3: Hoisting
  26. 26 Bad #4: Alias for method names // Legacy code

    Slick.prototype.addSlide = Slick.prototype.slickAdd = function(markup, index, addBefore) { ... };
  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
  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)
  29. 29 The Ugly Parts 1. Getters and setters only known

    at runtime 
 (meta-programming) 2. Static data properties 3. Optional features
  30. 30 Ugly #1: Getters and setters only known at runtime

    // Legacy code flags.forEach(function(flag){ Socket.prototype.__defineGetter__(flag, function(){ ... }); }); 30
  31. 31 // Legacy code Parallax.prototype.ww = null; Parallax.prototype.orientationStatus = 0;

    31 31 Ugly #2: Static data properties
  32. 32 Ugly #3: Optional features // Legacy code var core

    = require('../core'); core.Container.prototype.getChildByName = function (name) { ... }; 32 32 32
  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
  34. 34 Feedback from Developers 34

  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
  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
  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
  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
  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
  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
  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
  42. leonardo.silva@ifnmg.edu.br Thank you!!!