Recommending Automated Extract Method Refactorings (ICPC 2014)

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).

13beaa3b7239eca3319d54c6a9f3a85a?s=128

ASERG, DCC, UFMG

June 02, 2014
Tweet

Transcript

  1. Danilo Silva Dept. of Computer Science UFMG, Brazil danilofs@dcc.ufmg.br Recommending

    Automated Extract Method Refactorings Ricardo Terra Dept. of Computer Science UFLA, Brazil terra@dcc.ufla.br Marco Tulio Valente Dept. of Computer Science UFMG, Brazil mtov@dcc.ufmg.br APPLIED SOFTWARE ENGINEERING RESEARCH GROUP /
  2. Introduction Extract Method Refactoring  Used in a wide range

    of scenarios  IDE-based automated support available, but underused 2
  3. 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 }
  4. Proposed Solution JExtract  Generation of Extract Method candidates 

    Scoring function  Structural similarity between dependency sets  Filtering 4
  5. 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
  6. 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 }
  7. 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 }
  8. 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 }
  9. 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 }
  10. 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 }
  11. 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 }
  12. 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 }
  13. 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 }
  14. 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 }
  15. 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 }
  16. Scoring Function  Structural dependencies  Three levels:  Variables

     Types  Packages 7
  17. 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
  18. 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
  19. 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)
  20. 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)
  21. Scoring Function 12  Distance using Kulczynski set similarity coefficient

     Final scoring function
  22. 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%
  23. 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%
  24. Future Work  New evaluation with developers  Statement reordering

    before extracting the code fragment  Combine Extract Method with Move Method 15
  25. Thank you 16 APPLIED SOFTWARE ENGINEERING RESEARCH GROUP /

  26. 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 }