The Devver Blog

A Boulder startup improving the way developers work.

Churn, a gem for class and method level churn rates

There are many metrics that can help identify potentially problematic code. One metric that has a high correlation with buggy code is high-churn code.

Code churn metrics were found to be among the most highly correlated with problem reports

Predicting Bugs from History, Microsoft Research

Through metric_fu, Ruby devs have had access to information about high-churn files, but that is a bit too high level. Often with Rails apps the few controllers you have bubble up to the top, as do your most commonly edited model files. Often this isn’t because old code is being modified and fixes being applied, often it is new features and methods are being added. What you really want to find is the classes and methods that seem to require updates every time the code is changed. Often this means a method needs to be refactored as it is doing too much, and is tightly coupled with many other parts of the code. If code is changing to often it is likely to be violating the Single Responsibility Principle.

Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to change.

Wikipedia, Single Responsibility Principle

In our own projects we have noticed this and sometimes recognized the problem. Often this leads to a bit of refactoring which separates the code in more logical and easier to debug and test pieces. Often though we haven’t noticed until many bugs keep popping up in the same area of the application. If there were a tool to identify these areas of the code base before a single person on the team notices the problem that would be great. Well, now with our Churn gem you can.

Churn analyzes each commit to your Git repo and notes which files, classes, and methods were changed. If you run Churn over a series of commits it will begin to build up a list of the highest churn classes and methods (it can always give you the high-churn files, since it can get that straight from Git log with no analysis). Currently the gem is just set up to output the information to the command line. Once we finish up a bit more testing it will be integrated into metric_fu, as well as Caliper. On Caliper it will be easy to go back and generate the current high-churn methods and classes for the projects history.

Give it a shot on your projects, and let me know if there are any feature requests, bugs, or problems. I hope this helps identify some places you can improve your projects.

Instructions:

> gem install churn
> cd PROJECT_ROOT_DIRECTORY
> churn

example output:

**********************************************************************
* Revision Changes
**********************************************************************
files:
 * "lib/churn/churn_calculator.rb"

classes:
 * {"klass"=>"ChurnCalculator", "file"=>"lib/churn/churn_calculator.rb"}

methods:
 * {"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#calculate_revision_data", "file"=>"lib/churn/churn_calculator.rb"}
 * {"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#filters", "file"=>"lib/churn/churn_calculator.rb"}
 * {"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#display_array", "file"=>"lib/churn/churn_calculator.rb"}
 * {"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#initialize", "file"=>"lib/churn/churn_calculator.rb"}
**********************************************************************
* Project Churn
**********************************************************************
files:
 * {:file_path=>"lib/churn/churn_calculator.rb", :times_changed=>13}
 * {:file_path=>"README.rdoc", :times_changed=>7}
 * {:file_path=>"lib/tasks/churn_tasks.rb", :times_changed=>6}
 * {:file_path=>"Rakefile", :times_changed=>5}
 * {:file_path=>"VERSION", :times_changed=>4}
 * {:file_path=>"lib/churn/git_analyzer.rb", :times_changed=>4}
 * {:file_path=>"test/test_helper.rb", :times_changed=>4}
 * {:file_path=>"test/unit/churn_calculator_test.rb", :times_changed=>3}
 * {:file_path=>"test/churn_test.rb", :times_changed=>3}

classes:
 * {"klass"=>{"klass"=>"ChurnCalculator", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>9}
 * {"klass"=>{"klass"=>"LocationMapping", "file"=>"lib/churn/location_mapping.rb"}, "times_changed"=>1}
 * {"klass"=>{"klass"=>"GitAnalyzer", "file"=>"lib/churn/git_analyzer.rb"}, "times_changed"=>1}
 * {"klass"=>{"klass"=>"ChurnTest", "file"=>"test/churn_test.rb"}, "times_changed"=>1}
 * {"klass"=>{"klass"=>"ChurnCalculatorTest", "file"=>"test/unit/churn_calculator_test.rb"}, "times_changed"=>1}

methods:
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#initialize", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>3}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#to_h", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>3}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#analyze", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>2}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#report", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>2}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#display_array", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>2}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#calculate_revision_data", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>2}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#emit", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>1}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#calculate_revision_changes", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>1}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#get_changes", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>1}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#calculate_changes!", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>1}
 * {"method"=>{"klass"=>"ChurnCalculator", "method"=>"ChurnCalculator#set_source_control", "file"=>"lib/churn/churn_calculator.rb"}, "times_changed"=>1}
 * {"method"=>{"klass"=>"GitAnalyzer", "method"=>"GitAnalyzer#date_range", "file"=>"lib/churn/git_analyzer.rb"}, "times_changed"=>1}

Visit Churn on github, and view the current Churn metrics.

Advertisements

Written by DanM

January 12, 2010 at 3:30 pm

Posted in Uncategorized

%d bloggers like this: