Share
Recommending Automated Extract Method Refactorings (ICPC 2014)
Extract Method is a key refactoring for improving program comprehension. However, recent empirical research shows that refactoring tools designed to automate Extract Methods are often underused. To tackle this issue, we propose a novel approach to identify and rank Extract Method refactoring opportunities that are directly automated by IDE-based refactoring tools. Our approach aims to recommend new methods that hide structural dependencies that are rarely used by the remaining statements in the original method. We conducted an exploratory study to experiment and define the best strategies to compute the dependencies and the similarity measures used by the proposed approach. We also evaluated our approach in a sample of 81 extract method opportunities generated for JUnit and JHotDraw, achieving a precision of 48% (JUnit) and 38% (JHotDraw).
ASERG, DCC, UFMG
Transcript
26 slides
-
Danilo Silva Dept. of Computer Science UFMG, Brazil [email protected] Recommending
Automated Extract Method Refactorings Ricardo Terra Dept. of Computer Science UFLA, Brazil [email protected] Marco Tulio Valente Dept. of Computer Science UFMG, Brazil [email protected] APPLIED SOFTWARE ENGINEERING RESEARCH GROUP / -
Introduction Extract Method Refactoring Used in a wide range
of scenarios IDE-based automated support available, but underused 2 -
Proposed Solution Automated identification of Extract Method opportunities 3
01 public String execute() throws Exception { 02 logger.info("Starting execute()"); 03 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 Transaction t = sess.beginTransaction(); 05 Criteria criteria = sess.createCriteria(User.class); 06 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 User user = (User) criteria.uniqueResult(); 09 t.commit(); 10 sess.close(); 11 if (user != null) { 12 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 logger.info("Finishing execute() -- Success"); 14 return SUCCESS; 15 } 16 this.addActionError(this.getText("login.failure")); 17 logger.info("Finishing execute() -- Failure"); 18 return INPUT; 19 } -
Proposed Solution JExtract Generation of Extract Method candidates
Scoring function Structural similarity between dependency sets Filtering 4 -
Generation of Candidates Analysis of the AST to split
the code into blocks For each block For each subsequence of statements Check if it is a candidate for extraction 5 -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Generation of Candidates Moving window algorithm 6 01 public
String execute() throws Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } -
Scoring Function Structural dependencies Three levels: Variables
Types Packages 7 -
Scoring Function Variables 8 01 public String execute() throws
Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } Dependency on variable criteria -
Scoring Function Types 9 01 public String execute() throws
Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } Method of type Criteria invoked Dependency on type Criteria -
Scoring Function Packages 10 01 public String execute() throws
Exception { 02 1/01 logger.info("Starting execute()"); 03 1/02 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 1/03 Transaction t = sess.beginTransaction(); 05 1/04 Criteria criteria = sess.createCriteria(User.class); 06 1/05 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 1/06 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 1/07 User user = (User) criteria.uniqueResult(); 09 1/08 t.commit(); 10 1/09 sess.close(); 11 1/10 if (user != null) { 12 2/01 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 2/02 logger.info("Finishing execute() -- Success"); 14 2/03 return SUCCESS; 15 } 16 1/11 this.addActionError(this.getText("login.failure")); 17 1/12 logger.info("Finishing execute() -- Failure"); 18 1/13 return INPUT; 19 } Dependency on package org.hibernate (where Criteria belongs to) -
Scoring Function Two dependency sets: The code fragment
to be extracted Dep The remaining statements Dep’ Dep should not be similar to Dep’ Extracted fragment should hide complexity 11 Dep’ (Remaining) Dep (Extracted) Good Bad Dep’ (Remaining) Dep (Extracted) -
Scoring Function 12 Distance using Kulczynski set similarity coefficient
Final scoring function -
Case study: MyWebMarket Small Web application 25 well-known
Extract Method instances 14 of then supported by IDEs 13 Approach #Recom. #Found Recall Prec. JExtract (top-1) 14 12 85.7% 85.7% JExtract (top-2) 28 14 100.0% 50.0% JExtract (top-3) 42 14 100.0% 33.3% JDeodorant 20 2 14.3% 10.0% -
Case study: JUnit and JHotDraw Well-known opportunities introduced by
inlining methods 25 in JUnit 56 in JHotDraw 14 System Approach #Reco. #Found Recall Prec. JUnit (25 known opport.) JExtract (top-1) 25 12 48.0% 48.0% JExtract (top-2) 29 16 64.0% 32.7% JExtract (top-3) 72 18 72.0% 25.0% JDeodorant 39 0 0.0% 0.0% JHotDraw (56 known opport.) JExtract (top-1) 56 21 37.5% 37.5% JExtract (top-2) 110 28 50.0% 25.5% JExtract (top-3) 162 33 58.9% 20.4% JDeodorant 67 4 7.1% 6.0% -
Future Work New evaluation with developers Statement reordering
before extracting the code fragment Combine Extract Method with Move Method 15 -
Extra slide Some of JDeodorant’s recommendations incorrectly reordered statements
involving database transactions 17 01 public String execute() throws Exception { 02 logger.info("Starting execute()"); 03 Session sess = HibernateUtil.getSessionFactory().openSession(); 04 Transaction t = sess.beginTransaction(); 05 Criteria criteria = sess.createCriteria(User.class); 06 criteria.add(Restrictions.idEq(this.user.getUsername())); 07 criteria.add(Restrictions.eq("password", this.user.getPassword())); 08 User user = (User) criteria.uniqueResult(); 09 t.commit(); 10 sess.close(); 11 if (user != null) { 12 ActionContext.getContext().getSession().put(AUTHENTICATED_USER, user); 13 logger.info("Finishing execute() -- Success"); 14 return SUCCESS; 15 } 16 this.addActionError(this.getText("login.failure")); 17 logger.info("Finishing execute() -- Failure"); 18 return INPUT; 19 }