Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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)

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

15 Migration Rule #2 - Subclasses class C { … } C.prototype = new D(); class C extends D { … } legacy code ES6

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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)

Slide 29

Slide 29 text

29 The Ugly Parts 1. Getters and setters only known at runtime 
 (meta-programming) 2. Static data properties 3. Optional features

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

34 Feedback from Developers 34

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

[email protected] Thank you!!!