CSC/ECE 517 Spring 2014/ch1a 1w1e rm: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
 
(52 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This page discusses how to elegantly refactor code, including several common metrics used in determining the potential quality of refactoring code, as well as which refactoring techniques to use in coordination with such metrics.
This page discusses refactoring techniques and metrics. It also includes tips on refactoring in Ruby and list of tools for automated refactoring.


==Background==
==Background==
The practice of [http://en.wikipedia.org/wiki/Code_refactoring code refactoring] deals with changing the content or structure of code without changing the code's function in its execution.  Code refactoring has become a standard programming practice, as it potentially promotes readability, [http://en.wikipedia.org/wiki/Extensibility extensibility], and [http://en.wikipedia.org/wiki/Reusability reusability] of code.


Whether done through an [http://en.wikipedia.org/wiki/Integrated_development_environment IDE] or by hand, large-scale code projects can prove tedious to refactor. If minimal non-functional benefits are achieved through refactoring, time is wasted.  Furthermore, if not done properly, code refactoring can actually break the functionality of the code.  In the extreme case, code could be structured so badly that starting over completely may be more viable than refactoring.  As such, it is important to be able to know when and what to refactor.
The idea behind [http://en.wikipedia.org/wiki/Code_refactoring code refactoring] is to take a code base and improve its readability, [http://en.wikipedia.org/wiki/Extensibility extensibility], and [http://en.wikipedia.org/wiki/Reusability reusability]. The practice is focused on having a better quality and more standardized code base, and not on fixing bugs or changing functionality.
 
Refactoring has its difficulties, whether or not it is done using an [http://en.wikipedia.org/wiki/Integrated_development_environment IDE] or whether it is done by hand. There are lots of errors that can be made during refactoring, so it is pertinent to determine when it is necessary to refactor. Code metrics are one method used to determine how badly a file needs refactoring.


==Refactoring Techniques==
==Refactoring Techniques==
Before a coder performs a refactor, they must, either formally or informally, identify their ‘code smells’. A code smell refers to a negative quality of a code base that either implements bad programming practices or slows down code development or runtime. These aren’t typically bugs, but can increase the chance of bugs later on if not fixed. Based off of the type of code smell, a different refactoring technique is used to fix it.
Before a coder performs a refactor, they must, either formally or informally, identify their ‘code smells’. A code smell refers to a negative quality of a code base that either implements bad programming practices or slows down code development or runtime. These aren't typically bugs, but can increase the chance of bugs later on if not fixed. Based on the type of code smell, a different refactoring technique is used to fix it.


===Duplicate Code===
===Duplicate Code===
Line 14: Line 15:
*Extract Method  / Pull-up Method
*Extract Method  / Pull-up Method
**If common code is used in multiple places, simply pull it out and make a method that can be called from all of the necessary places. Variables can be passed in if slight variations are needed between calls.
**If common code is used in multiple places, simply pull it out and make a method that can be called from all of the necessary places. Variables can be passed in if slight variations are needed between calls.
**If the code is used in various subclasses, put the common code in a method in the superclass, so that it can be seen and called by it’s children.
**If the code is used in various subclasses, put the common code in a method in the superclass, so that it can be seen and called by its children.<ref>http://www.refactoring.com/catalog/extractMethod.html Extract Method Example</ref>
**[http://www.refactoring.com/catalog/extractMethod.html Extract Method Example]
Before Refactor
<pre>
class Cat 
end 
 
class Lion < Mammal
  def likes 
    puts "Boxes" 
  end 
end
class HouseCat < Mammal
  def likes 
    puts "Boxes" 
  end 
end
</pre>
After Pull Up Refactor
<pre>
class Cat 
  def likes 
    puts "Boxes" 
  end 
end 
 
class Lion < Mammal
end
class HouseCat < Mammal
end
</pre>


*Form Template Method
*Form Template Method
**If two methods in subclasses perform similar steps in the same order, but the steps are different, then get the steps into methods with the same signature, so that the original methods become the same. Then pull them up. [http://www.refactoring.com/catalog/formTemplateMethod.html]
**If two methods in subclasses perform similar steps in the same order, but the steps are different, then get the steps into methods with the same signature, so that the original methods become the same. Then pull them up. <ref>http://www.refactoring.com/catalog/formTemplateMethod.html</ref>
**[http://www.integralist.co.uk/posts/refactoring-techniques/#form-template-method Form Template Method Example]
Before Refactor
<pre>
class Cat
end
class Tiger < Cat
  def live
    puts “Wake up”
    puts “Catch deer”
    puts “Eat deer”
    puts “Sleep in sun”
  end
end
class HouseCat < Cat
  def live
    puts “Wake up”
    puts “Wake human up”
    puts “Eat food”
    puts “Sleep in sun”
  end
end
</pre>
After Form Template Refactor
<pre>
class Cat
  def live
    puts “Wake up”
    do_morning_routine
    puts “Sleep in the sun”
  end
end
class Tiger < Cat
  def do_morning_routine
    puts “Catch deer”
    puts “Eat deer”
  end
end
class HouseCat < Cat
  def do_morning_routine
    puts “Wake human up”
    puts “Eat food”
  end
end
</pre>


====Large Method/Class====
===Large Method/Class===
If a project is not planned out well enough in advance, it is easy for methods and classes to become populated with excess functionality. While the functionality may be necessary for the project, it might not be in the method or class.  
If a project is not planned out well enough in advance, it is easy for methods and classes to become populated with excess functionality. While functionality of a block of code may be necessary for the project, the code block might not really pertain to the method or class it is included in.  
*Extract Method
*Extract Method
**This can be used when duplicate code occurs, as discussed above. But it can also be used when a method performs multiple functions that have the ability to be split up into various functions that serve a single purpose.  
**This can be used when duplicate code occurs, as discussed above. But it can also be used when a method performs multiple functions that have the ability to be split up into various functions that serve a single purpose.  
Before Refactoring
<pre>
def foo
  puts "hey"
  puts "how are you"
  puts "bye!"
end
def bar
  puts "bye!"
end
</pre>
After Extract Method Refactor
<pre>
def greet
  puts "hey"
  puts "how are you"
end
def farewell
  puts "bye!"
end
def foo
  greet
  farewell
end
def bar
  greet
end
</pre>
*Extract Class/Subclass/Superclass
*Extract Class/Subclass/Superclass
**If there are class variables or methods that don’t directly pertain to a class, then it may be necessary to create a new class for those pieces.[http://www.refactoring.com/catalog/extractClass.html Example]
**If there are class variables or methods that don’t directly pertain to a class, then it may be necessary to create a new class for those pieces.[http://www.refactoring.com/catalog/extractClass.html Example]
**If there are pieces of a class that are only for a specific subset of instances, then a subclass can be constructed to contain these.[http://www.refactoring.com/catalog/extractSubclass.html Example]
**If there are pieces of a class that are only for a specific subset of instances, then a subclass can be constructed to contain these.[http://www.refactoring.com/catalog/extractSubclass.html Example]
**If there are pieces that multiple classes use, then a superclass can be constructed to handle these generic functions, leaving the subclasses to deal with the remaining differences [http://www.refactoring.com/catalog/extractSuperclass.html Example]
**If there are pieces that multiple classes use, then a superclass can be constructed to handle these generic functions, leaving the subclasses to deal with the remaining differences<ref>http://www.refactoring.com/catalog/extractSuperclass.html Example </ref>
Before Refactoring
<pre>
class Person
  def initialize(f,m,l,s)
    @firstName = f
    @middleName = m
    @lastName = l
    @SSN = s
  end
end
</pre>
After Extract Class Refactoring
<pre>


====Improving Readability and Clarity====
class FullName
A lot of times, refactoring can be used to do simple, yet necessary changes like renaming variables, methods, or classes. As features get added to a project, classes tend to get charged with more uses than originally planned, so sometimes, the original naming scheme no longer applies and a new one needs to be instilled.
  def initialize(f,m,l)
    @firstName = f
    @middleName = m
    @lastName = l
  end
end


Moving methods and parameters around to where they have the best accessibility also has its uses. Classes also tend to be promoted or demoted to super and sub classes after their ultimate functional purpose is determined.
class Person
  def initialize(f,m,l,s)
    @name = FullName.new(f,m,l)
    @SSN = s
  end
end
</pre>


====More Techniques====
===Improving Readability and Clarity===
There is an extensive list of coding smells that can are able to be improved through refactoring.
Many times, refactoring can be used to do simple, yet necessary changes like renaming variables, methods, or classes. As features get added to a project, classes tend to get charged with more uses than originally planned, so sometimes, the original naming scheme no longer applies and a new one needs to be instilled.


A description of smells with their techniques exists [http://ghendry.net/refactor.html here].
Moving methods and parameters around to where they have the best accessibility also has its uses. Classes also tend to be promoted or demoted to super and sub classes after their ultimate functional purpose is determined.


A list of techniques with examples in Ruby are listed [http://www.refactoring.com/catalog/index.html here].
===More Techniques===
 
There many more techniques than those that are listed above. Each has its specific purpose, though one must take care to make sure that it is a necessary refactoring.
==Getting Started with Refactoring in Ruby==
The first step in refactoring is writing solid set of tests for that section of code to avoid introducing bugs. In Ruby, this can be done using [http://www.ruby-doc.org/stdlib-2.1.0/libdoc/test/unit/rdoc/Test/Unit.html Test::Unit] or [http://rspec.info/ Rspec]. Next step is to make small changes, test again, make small changes and so on. <ref>http://www.amazon.com/Refactoring-Edition-Addison-Wesley-Professional-Series/dp/0321984137</ref>
 
To start refactoring in Ruby, a [http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ CodeClimate blog] suggests 7 patterns to refactor fat models in Ruby:
# Extract Value Objects
# Extract Service Objects
# Extract Form Objects
# Extract Query Objects
# Introduce View Objects
# Extract Policy Objects
# Extract Decorators
 
It is always confusing when to start refactoring. If either one of the following conditions is true, it is a good time to start refactoring code:
* The Rule of Three
* When you add function
* When you need to fix a bug
* During code review,
* For greater understanding
 
A [http://blog.codeclimate.com/blog/2014/01/09/when-is-it-time-to-refactor/ CodeClimate blog] also suggests some other conditions to identify the need to refactor code.


==Metrics==
==Metrics==
There are a variety of metrics that are used to quantify the merits of refactoring.  Nearly all of these metrics can be calculated by [http://en.wikipedia.org/wiki/Program_analysis_(computer_science) program analysis tools].  The final metric mentioned could be calculated by some [http://en.wikipedia.org/wiki/Version_control version control system].
It can be hard to decide whether or not to refactor, especially when it’s for something that appears to be working fine with just a surface level smell. Program analysis tools are used to derive various types of code metrics, which allow coders to identify problem areas and future pitfalls of their code base. These tools look at the code in several different ways, including the number of times a source file has been edited to help determine if it is a possible target of [feature envy], duplicate code structure to identify replicated lines, and block depths to suggest possible complexity issues.


===Complexity===
===Open Source Ruby Metric Tools===
In general, complexity is a measure of the number of branches and paths in the code.
There are a variety of open source code metric tools that can be employed specifically for Ruby programs. [https://www.ruby-toolbox.com/categories/code_metrics The Ruby Toolbox] contains the most popular of these tools along with ratings.


[http://en.wikipedia.org/wiki/Cyclomatic_complexity Cyclomatic complexity], in particular, is a popular metric for measuring a method's complexity. In its simplest form, cyclomatic complexity can be thought of as adding 1 to the number of decision points within the code <ref> http://www.codeproject.com/Articles/13212/Code-Metrics-Code-Smells-and-Refactoring-in-Practi</ref>.  These include cases in switch statements, loops, and if-else statements.  In the following example, there are two decision points (the if and the else), so the cyclomatic complexity is 2+1=3.
[http://rubydoc.info/gems/simplecov/frames Simple Cov] is at the top of this list. It was developed specifically to be of use to anyone using any framework. It integrates itself with the project’s own test cases to check the code coverage of them in addition to cucumber features.


<pre>
===Code Climate===
public void evaluate(condition) {
[https://codeclimate.com Code Climate] is a hosted code metrics tool that analyzes projects in a multitdue of ways. It produces three main metric ratings with grades from A-F that comprise of the methods and classes contained in each file and project.
  if(condition a) {
#Complexity Metric - This is based off of the Assignment, Branches, and Conditions ([http://c2.com/cgi/wiki?AbcMetric ABC]) metric, where the number of assignments, branches, and conditions are counted and analyzed. However, because Code Climate is constructed for Ruby programs, it also takes into account certain types of Ruby constructs that may increase the metric score, but are actually beneficial to the project.
    //do something
#Duplication Metric - This looks at the syntax trees of the code in order to identify identical and similar code structures. Because the syntax trees are being analyzed, code formatting and different method and class names do not affect the score. <ref>https://codeclimate.com/docs#quality-metrics</ref>
  }
#Churn Method - This integrates with the Git repository to look at the change history of the project’s files. Files with excessively high change histories have a tendency to have a high complexity rating as well, as they can be the result of feature envy, where extra functions are added into a file instead of being added to a new file with its own functionality.
  else {
    //do something else
  }
}
</pre>


Cyclomatic complexity values are divided into tiers of risk, where values less than 11 are of low risk, values between 11 and 21 are of moderate risk, values between 21 and 51 are of high risk, and values greater than 50 are of extreme risk <ref>http://www.klocwork.com/products/documentation/current/McCabe_Cyclomatic_Complexity</ref>.  For a method in the first tier (e.g. with a cyclomatic complexity of 10), refactoring may not be necessary.  Conversely, for a method in the extreme risk tier (e.g. with a cyclomatic complexity of 2,000), throwing out the code and starting over may be the appropriate solution instead of refactoring.  For a method with a cyclomatic complexity in one of the middle tiers, refactoring is likely the best option.  The example below shows how refactoring can reduce the cyclomatic complexity of a method.
==Getting Started with Refactoring in Ruby==


<pre>
===First step: Writing tests===
public void evaluateAll() {
The first step in refactoring is writing solid set of tests for the section of code under consideration, in order to avoid introducing bugs. In Ruby, this can be done using [http://www.ruby-doc.org/stdlib-2.1.0/libdoc/test/unit/rdoc/Test/Unit.html Test::Unit] or [http://rspec.info/ Rspec]. Next step is to make small changes, test, make small changes, test again and so on<ref name='Refactoring Ruby Edition'>http://www.amazon.com/Refactoring-Edition-Addison-Wesley-Professional-Series/dp/0321984137</ref>. This ensures that we can revert back to the working version of code if any small refactoring causes the application to stop working.
  for(int n = 0; n < conditionList.size(); n ++) {
    if(conditionList.get(n).equals(a)) {
      //do something
    }
    else {
      //do something else
    }
  }
}
</pre>


Using extract method on this simple example, the original method can be reduced to the following two methods.
===When to refactor===
<pre>
It is always confusing when to start refactoring. If either one of the following conditions is true, it is a good time to start refactoring code<ref name='Refactoring Ruby Edition'/>:
public void evaluateAll() {
* The Rule of Three - If any code segment gives trouble more than two times, it is time to refactor it
  for(int n = 0; n < conditionList.size(); n ++) {
* Adding new function - It might be helpful to refactor when adding new functionality as it would give better understanding of code. Sometimes, revisiting old design might help to accommodate new features better
    evaluate(conditionList.get(n));
* Fixing a bug - It is useful to refactor code which had a bug; this might save bug reports from the same piece of code in future
  }
* During code review - Refactoring is useful during code reviews as it can be applied immediately making code reviews more fruitful
}
* Understanding other's code - If a new member joins the team and is asked to refactor existing code, it helps them to get a better understanding of the code base. It might take extra time to get them started, but trying to refactor will help them contribute to the project better in the long run.
</pre>


<pre>
A [http://blog.codeclimate.com/blog/2014/01/09/when-is-it-time-to-refactor/ CodeClimate blog] suggests some other conditions to decide when to refactor code.
public void evaluate(condition c) {
  if(c.equals(a)) {
    //do something
  }
  else {
    //do something else
  }
}
</pre>


In this manner, the cyclomatic complexity of the original method is reduced.  Intuitively, choosing code with higher cyclomatic complexity yields a better-quality refactoring.
==Automated Code Refactoring==
 
=== RubyMine ===
===Duplicate Code===
*[http://www.jetbrains.com/ruby/webhelp/refactoring.html RubyMine] has a built in refactoring menu.
The metric of duplicate code is the number of times similar code structures are detected in a program.  For example, although they are used to print different statements, the following code fragments contain similar structures.
*To perform refactoring, select a code fragment to refactor. Refactorings available for your selection appear under the Refactor Menu in RubyMine. <ref>http://www.jetbrains.com/ruby/webhelp/refactoring-source-code.html#common</ref><br/><br/> [[File:RubyMine_refactoring_menu.png]]
<pre>
System.out.println("Player position: " + player.position[0] + " " + player.position[1] + " " + player.position[2]);
</pre>
<pre>
System.out.println("Enemy velocity: " + enemy.velocity[0] + " " + enemy.velocity[1] + " " + enemy.velocity[2]);
</pre>
If they were unified into a single method, both reusability and extensibility of the code's function would be improved.
Once again using the extract method operation (or extract class, depending on the context), a new method can be created to facilitate the function of both previous code fragments.  This is shown below.
<pre>
public void printAttribute(String description, float attribute[]) {
  System.out.println(description + ": " + attribute[0] + " " + attribute[1] + " " + attribute[2]);
}
</pre>
Using this unified method in the respective locations of the original code fragments allows function to remain unchanged.
<pre>
printAttribute("Player position", player.position);
</pre>
<pre>
printAttribute("Enemy velocity", enemy.velocity);
</pre>


The extract method solution to refactoring code is not the only example of how to refactor duplicate code structures.  For example, within a single class, the printAttribute method might might solve local duplication issues, but issues could still exist outside of the class.  For example, such a method could be defined in two related classes, as shown below.
===RFactor ===
<pre>
*[http://fabiokung.com/2009/02/04/rfactor-ruby-refactoring-for-your-loved-editor/ RFactor] is a Ruby gem, which aims to provide common and simple refactorings for Ruby code for text editors like TextMate
class Enemy extends MovingObject {
*The first release has only Extract method implemented while other refactorings are coming soon
  // ...non-duplicate variables for Enemy class
*It is available on [http://github.com/fabiokung/rfactor-tmbundle/tree/master GitHub]  
  // ...non-duplicate methods for Enemy class
  public void printAttribute(String description, float attribute[]) {
    System.out.println(description + ": " + attribute[0] + " " + attribute[1] + " " + attribute[2]);
  }
}
</pre>
<pre>
class Player extends MovingObject {
  // ...non-duplicate variables for Player class
  // ...non-duplicate methods for Player class
  public void printAttribute(String description, float attribute[]) {
    System.out.println(description + ": " + attribute[0] + " " + attribute[1] + " " + attribute[2]);
  }
}
</pre>
In the case of these two classes, printAttribute is shared by both.  Assuming all subclasses of MovingObject contain some float array of size three, implementing the pull-up method refactoring pattern would move the printAttribute into the MovingObject class.  Thus, the duplication issue would be resolved.


In practice, no code structure should exist in more than one location <ref>http://sourcemaking.com/refactoring/duplicated-code</ref>. As such, addressing code with the most duplicate structures yields the highest quality of refactoring.
=== Other IDEs ===
*Some other IDEs with built-in refactoring include [http://www.aptana.com/products/studio3 Aptana Studio], [http://www.aptana.com/products/radrails Aptana RadRails] and [https://netbeans.org/features/ruby/index.html NetBeans 7] (which requires Ruby and Rails [http://plugins.netbeans.org/plugin/38549 plugin])
*A StackOverflow [http://stackoverflow.com/questions/8705144/ide-with-refactoring-support-for-ruby-on-rails post] compares these tools ranking them as RubyMine being the best, followed by NetBeans, RadRails 2 and Aptana Studio 3


===Method and Class Length===
==Further Reading==
The length of both methods and classes is also used as a metric to determine the potential quality of refactoring.  Unlike the previous two metrics, however, the length of a method or class does not directly imply a problem in code design.  Rather, the greater length that a method or class has likely implies that there may be another issue that can be quantified in other metrics (such as high complexity and duplicate code)<ref>http://blog.codeclimate.com/blog/2013/08/07/deciphering-ruby-code-metrics/</ref>.  Method and Class lengths are not only calculated through the number of non-commented lines of code within each, but also through the number of members, parameters, variables, and imports <ref>http://trijug.org/downloads/refactor-metrics.pdf</ref>.
*[http://en.wikipedia.org/wiki/Code_smell Ways to identify problems in code]
 
In practice, classes with more than 1,000 lines of code and methods with more than 100 lines of code should be refactored.


===Volatility===
*[http://ghendry.net/refactor.html A description of smells with their techniques exists]
The metric of volatility is a measure of the number of changes in code over time <ref>http://www.grahambrooks.com/blog/metrics-based-refactoring-for-cleaner-code/ </ref>. Although by itself, volatility is not a very useful metric to use in refactoring, it attains such merit when plotted against complexity.  The relationship between volatility and complexity is shown in the table below.
{| border="1"
|+ Refactoring Priority with Volatility vs. Complexity
! scope = "col" |
! scope = "col" | Low Volatility
! scope = "col" | High Volatility
|-
! scope = "row" | High Complexity
| Low Priority || High Priority
|-
! scope = "row" | Low Complexity
| No Priority || No Priority
|}


As shown in the table, code with a low volatility and low complexity does not need to be refactored.  The same is true for code with a high volatility and low complexity, as there is little potential for future problems in the code. In regards to code with a high complexity but low volatility, refactoring should occur, but it is not of the highest priority. Code that has both a high volatility and high complexity, however, is assigned the highest priority.
*[http://www.refactoring.com/catalog/index.html A list of techniques with examples in Ruby are listed]


==Review of Metrics in Practice==
*[http://blog.codeclimate.com/blog/2012/11/14/why-ruby-class-methods-resist-refactoring/ Resistance With Refactoring Ruby Class Methods]
In review, the question of when and what to refactor can be determined by prioritizing refactoring based upon the highest potential quality to be gained through the refactoring.  In regards to the metrics presented above, the priority of refactoring code should increase when:
*Methods in the code have a high cyclomatic complexity.
*There exist multiple duplicate code structures.
*Methods have more than 100 lines of code.
*Classes have more than 1000 lines of code.
*The code possesses a high cyclomatic complexity and volatility over time.


==Automated Code Refactoring==
*[http://www.railsinside.com/uncategorized/460-the-first-step-of-refactoring-a-rails-application.html Refactoring a Rails Application]
* [http://www.jetbrains.com/ruby/webhelp/refactoring.html RubyMine]
** RubyMine has a built in refactoring menu. 
** Select a symbol or code fragment to refactor. Refactorings available for your selection appears. <ref>http://www.jetbrains.com/ruby/webhelp/refactoring-source-code.html#common</ref><br/> [[File:RubyMine_refactoring_menu.png]]
* [http://fabiokung.com/2009/02/04/rfactor-ruby-refactoring-for-your-loved-editor/ RFactor]
** RFactor is a Ruby gem, which aims to provide common and simple refactorings for Ruby code for text editors like TextMate
** The first release has only Extract method implemented while other refactorings are coming soon
** It is available on [http://github.com/fabiokung/rfactor-tmbundle/tree/master GitHub]  


* Some other IDEs with built-in refactoring include [http://www.aptana.com/products/studio3 Aptana Studio], [http://www.aptana.com/products/radrails Aptana RadRails] and [https://netbeans.org/features/ruby/index.html NetBeans 7] (which requires Ruby and Rails [http://plugins.netbeans.org/plugin/38549 plugin]).
*[http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ Refactoring Fat Models]
** A StackOverflow [http://stackoverflow.com/questions/8705144/ide-with-refactoring-support-for-ruby-on-rails post] compares these tools ranking them as RubyMine being the best followed by NetBeans, RadRails 2 and Aptana Studio 3


==Further Reading==
*[https://speakerdeck.com/brianvh/refactoring-views-in-rails Talk on Refactoring Fat Views]
*[http://www.refactoring.com/catalog/index.html A list of refactoring techniques.]


*[http://en.wikipedia.org/wiki/Code_smell Ways to identify problems in code.]
*[http://www.amazon.com/Rails-AntiPatterns-Refactoring-Addison-Wesley-Professional/dp/0321604814 Rails AntiPatterns: Best Practice Ruby on Rails Refactoring]


==References==
==References==
<references/>
<references/>

Latest revision as of 05:02, 19 February 2014

This page discusses refactoring techniques and metrics. It also includes tips on refactoring in Ruby and list of tools for automated refactoring.

Background

The idea behind code refactoring is to take a code base and improve its readability, extensibility, and reusability. The practice is focused on having a better quality and more standardized code base, and not on fixing bugs or changing functionality.

Refactoring has its difficulties, whether or not it is done using an IDE or whether it is done by hand. There are lots of errors that can be made during refactoring, so it is pertinent to determine when it is necessary to refactor. Code metrics are one method used to determine how badly a file needs refactoring.

Refactoring Techniques

Before a coder performs a refactor, they must, either formally or informally, identify their ‘code smells’. A code smell refers to a negative quality of a code base that either implements bad programming practices or slows down code development or runtime. These aren't typically bugs, but can increase the chance of bugs later on if not fixed. Based on the type of code smell, a different refactoring technique is used to fix it.

Duplicate Code

Duplicate code can be a tricky concept when refactoring. Large sections of duplicated code can be easy to find and fixed by pulling it out and creating a single centralized method to call, however it is usually not that easy. Sometimes it may only be one or two lines of code that are duplicated which calls for an assessment of whether or not it is in the best interest to create a new method for a couple lines of code. Other times, code is not duplicate, rather it is similar enough that a generic method could be created to serve various purposes. Some specific techniques to deal with this are:

  • Extract Method / Pull-up Method
    • If common code is used in multiple places, simply pull it out and make a method that can be called from all of the necessary places. Variables can be passed in if slight variations are needed between calls.
    • If the code is used in various subclasses, put the common code in a method in the superclass, so that it can be seen and called by its children.<ref>http://www.refactoring.com/catalog/extractMethod.html Extract Method Example</ref>

Before Refactor

class Cat  
end  
  
class Lion < Mammal 
  def likes  
    puts "Boxes"  
  end  
end 
class HouseCat < Mammal 
  def likes  
    puts "Boxes"  
  end  
end 

After Pull Up Refactor

class Cat  
  def likes  
    puts "Boxes"  
  end  
end  
  
class Lion < Mammal 
end 
class HouseCat < Mammal 
end 
  • Form Template Method
    • If two methods in subclasses perform similar steps in the same order, but the steps are different, then get the steps into methods with the same signature, so that the original methods become the same. Then pull them up. <ref>http://www.refactoring.com/catalog/formTemplateMethod.html</ref>

Before Refactor

class Cat
end
class Tiger < Cat
  def live
    puts “Wake up”
    puts “Catch deer”
    puts “Eat deer”
    puts “Sleep in sun”
  end
end
class HouseCat < Cat
  def live
    puts “Wake up”
    puts “Wake human up”
    puts “Eat food”
    puts “Sleep in sun”
  end
end

After Form Template Refactor

class Cat
  def live
    puts “Wake up”
    do_morning_routine
    puts “Sleep in the sun”
  end
end
class Tiger < Cat
  def do_morning_routine
    puts “Catch deer”
    puts “Eat deer”
  end
end
class HouseCat < Cat
  def do_morning_routine
    puts “Wake human up”
    puts “Eat food”
  end
end

Large Method/Class

If a project is not planned out well enough in advance, it is easy for methods and classes to become populated with excess functionality. While functionality of a block of code may be necessary for the project, the code block might not really pertain to the method or class it is included in.

  • Extract Method
    • This can be used when duplicate code occurs, as discussed above. But it can also be used when a method performs multiple functions that have the ability to be split up into various functions that serve a single purpose.

Before Refactoring

def foo
  puts "hey"
  puts "how are you"
  puts "bye!"
end
def bar
  puts "bye!"
end

After Extract Method Refactor

def greet
  puts "hey"
  puts "how are you"
end
def farewell
  puts "bye!"
end
def foo
  greet
  farewell
end
def bar
  greet
end
  • Extract Class/Subclass/Superclass
    • If there are class variables or methods that don’t directly pertain to a class, then it may be necessary to create a new class for those pieces.Example
    • If there are pieces of a class that are only for a specific subset of instances, then a subclass can be constructed to contain these.Example
    • If there are pieces that multiple classes use, then a superclass can be constructed to handle these generic functions, leaving the subclasses to deal with the remaining differences<ref>http://www.refactoring.com/catalog/extractSuperclass.html Example </ref>

Before Refactoring

class Person
  def initialize(f,m,l,s)
    @firstName = f
    @middleName = m
    @lastName = l
    @SSN = s
  end
end

After Extract Class Refactoring


class FullName
  def initialize(f,m,l)
    @firstName = f
    @middleName = m
    @lastName = l
  end
end

class Person
  def initialize(f,m,l,s)
    @name = FullName.new(f,m,l)
    @SSN = s
  end
end

Improving Readability and Clarity

Many times, refactoring can be used to do simple, yet necessary changes like renaming variables, methods, or classes. As features get added to a project, classes tend to get charged with more uses than originally planned, so sometimes, the original naming scheme no longer applies and a new one needs to be instilled.

Moving methods and parameters around to where they have the best accessibility also has its uses. Classes also tend to be promoted or demoted to super and sub classes after their ultimate functional purpose is determined.

More Techniques

There many more techniques than those that are listed above. Each has its specific purpose, though one must take care to make sure that it is a necessary refactoring.

Metrics

It can be hard to decide whether or not to refactor, especially when it’s for something that appears to be working fine with just a surface level smell. Program analysis tools are used to derive various types of code metrics, which allow coders to identify problem areas and future pitfalls of their code base. These tools look at the code in several different ways, including the number of times a source file has been edited to help determine if it is a possible target of [feature envy], duplicate code structure to identify replicated lines, and block depths to suggest possible complexity issues.

Open Source Ruby Metric Tools

There are a variety of open source code metric tools that can be employed specifically for Ruby programs. The Ruby Toolbox contains the most popular of these tools along with ratings.

Simple Cov is at the top of this list. It was developed specifically to be of use to anyone using any framework. It integrates itself with the project’s own test cases to check the code coverage of them in addition to cucumber features.

Code Climate

Code Climate is a hosted code metrics tool that analyzes projects in a multitdue of ways. It produces three main metric ratings with grades from A-F that comprise of the methods and classes contained in each file and project.

  1. Complexity Metric - This is based off of the Assignment, Branches, and Conditions (ABC) metric, where the number of assignments, branches, and conditions are counted and analyzed. However, because Code Climate is constructed for Ruby programs, it also takes into account certain types of Ruby constructs that may increase the metric score, but are actually beneficial to the project.
  2. Duplication Metric - This looks at the syntax trees of the code in order to identify identical and similar code structures. Because the syntax trees are being analyzed, code formatting and different method and class names do not affect the score. <ref>https://codeclimate.com/docs#quality-metrics</ref>
  3. Churn Method - This integrates with the Git repository to look at the change history of the project’s files. Files with excessively high change histories have a tendency to have a high complexity rating as well, as they can be the result of feature envy, where extra functions are added into a file instead of being added to a new file with its own functionality.

Getting Started with Refactoring in Ruby

First step: Writing tests

The first step in refactoring is writing solid set of tests for the section of code under consideration, in order to avoid introducing bugs. In Ruby, this can be done using Test::Unit or Rspec. Next step is to make small changes, test, make small changes, test again and so on<ref name='Refactoring Ruby Edition'>http://www.amazon.com/Refactoring-Edition-Addison-Wesley-Professional-Series/dp/0321984137</ref>. This ensures that we can revert back to the working version of code if any small refactoring causes the application to stop working.

When to refactor

It is always confusing when to start refactoring. If either one of the following conditions is true, it is a good time to start refactoring code<ref name='Refactoring Ruby Edition'/>:

  • The Rule of Three - If any code segment gives trouble more than two times, it is time to refactor it
  • Adding new function - It might be helpful to refactor when adding new functionality as it would give better understanding of code. Sometimes, revisiting old design might help to accommodate new features better
  • Fixing a bug - It is useful to refactor code which had a bug; this might save bug reports from the same piece of code in future
  • During code review - Refactoring is useful during code reviews as it can be applied immediately making code reviews more fruitful
  • Understanding other's code - If a new member joins the team and is asked to refactor existing code, it helps them to get a better understanding of the code base. It might take extra time to get them started, but trying to refactor will help them contribute to the project better in the long run.

A CodeClimate blog suggests some other conditions to decide when to refactor code.

Automated Code Refactoring

RubyMine

RFactor

  • RFactor is a Ruby gem, which aims to provide common and simple refactorings for Ruby code for text editors like TextMate
  • The first release has only Extract method implemented while other refactorings are coming soon
  • It is available on GitHub

Other IDEs

  • Some other IDEs with built-in refactoring include Aptana Studio, Aptana RadRails and NetBeans 7 (which requires Ruby and Rails plugin)
  • A StackOverflow post compares these tools ranking them as RubyMine being the best, followed by NetBeans, RadRails 2 and Aptana Studio 3

Further Reading

References

<references/>