Deciphering Ruby Code Metrics - Code Climate Blog
Deciphering Ruby Code Metrics
This post is part of our historical archive. It represents the beliefs, actions, products, and services of Code Climate as of its publication date. Today, Code Climate focuses on providing enterprise leaders the software development data, context layer, and playbooks needed to build the AI-native software organization their enterprise needs. Head to codeclimate.com to learn more.
Aug 7, 2013
10 min read
Ruby has a rich ecosystem of code metrics tools, readily available and easy to run on your codebase. Though generating the metrics is simple, interpreting them is complex. This article looks at some Ruby metrics, how they are calculated, what they mean and (most importantly) what to do about them, if anything.
Code metrics fall into two tags: static analysis and dynamic analysis. Static analysis is performed without executing the code (or tests). Dynamic analysis, on the other hand, requires executing the code being measured. Test coverage is a form of dynamic analysis because it requires running the test suite. We’ll look at both types of metrics.
Lines of Code
One of the oldest and most rudimentary forms of static analysis is lines of code (LOC). This is most commonly defined as the count of non-blank, non-comment lines of source code. LOC can be looked at on a file-by-file basis or aggregated by module, architecture layer (e.g. the models in an MVC app) or by production code vs. test code.
Rails provides LOC metrics broken down by MVC layer and production code vs. tests via the rake stats command. The output looks something like this:
Lines of code alone can’t tell you much, but it’s usually considered in two ways: overall codebase size and test-to-code ratio. Large, monolithic apps will naturally have higher LOC. Test-to-code ratio can give a programmer a crude sense of the testing practices that have been applied.
Because they are so high level and abstract, don’t work on “addressing” LOC-based metrics directly. Instead, just focus on improvements to maintainability (e.g. decomposing an app into services when appropriate, applying TDD) and it will eventually show up in the metrics.
Complexity
Broadly defined, “complexity” metrics take many forms:
- Cyclomatic complexity — Also known as McCabe’s complexity, cyclomatic complexity is a count of the linearly independent paths through source code. While his original paper contains a lot of graph-theory analysis, McCabe noted that cyclomatic complexity “is designed to conform to our intuitive notion of complexity”.
- The ABC metric — Aggregates the number of assignments, branches, and conditionals in a unit of code. The branches portion of an ABC score is very similar to cyclomatic complexity, designed to be language and style agnostic.
- Ruby’s Flog scores — Perhaps the most popular way to describe the complexity of Ruby code. While Flog incorporates ABC analysis, it penalizes hard-to-understand Ruby constructs like meta-programming.
For my money, Flog scores seem to do the best job of being a proxy for how easy or difficult a block of Ruby code is to understand. Let’s take a look at how it’s computed for a simple method:
def blah # 11.2 total =
a = eval "1+1" # 1.2 (a=) + 6.0 (eval) +
if a == 2 # 1.2 (if) + 1.2 (==) + 0.4 (fixnum) +
puts "yay" # 1.2 (puts)
end
end
To use Flog on your own code, first install it:
$ gem install flog
Then you can Flog individual files or whole directories. By default, Flog scores are broken out by method, but you can get per-class total by running it with the -g option:
$ flog app/models/user.rb
$ flog -g app/controllers
All of this raises a question: What’s a good Flog score? It’s subjective, of course, but Jake Scruggs, one of the original authors of Metric-Fu, suggested that scores above 20 indicate the method may need refactoring, and above 60 is dangerous. Similarly, Code Climate will flag methods with scores above 25, considering anything above 60 “very complex”.
Duplication
Static analysis can also identify identical and similar code, which usually results from copying and pasting. In Ruby, Flay is the most popular tool for duplication detection. It hunts for large, identical syntax trees and also uses fuzzy matching to detect code which differs only by the specific identifiers and constants used.
Let’s take a look at an example of two similar, but not-quite-identical Ruby snippets:
###### From app/models/tickets/lighthouse.rb:
def build_request(path, body)
Post.new(path).tap do |req|
req["X-LighthouseToken"] = @token
req.body = body
end
end
####### From app/models/tickets/pivotal_tracker.rb:
def build_request(path, body)
Post.new(path).tap do |req|
req["X-TrackerToken"] = @token
req.body = body
end
end
The s-expressions produced by RubyParser for these methods are nearly identical, sans the string literal for the token header name. Running Flay against your project is simple:
$ gem install flay
$ flay path/to/rails_app
Test Coverage
One of the most popular code metrics is test coverage. Because it requires running the test suite, it’s dynamic analysis rather than static analysis. Coverage is often expressed as a percentage, as in: “The test coverage for our Rails app is 83%.”
Test coverage metrics come in three flavors:
- C0 coverage — The percentage of lines of code that have been executed.
- C1 coverage — The percentage of branches that have been followed at least once.
- C2 coverage — The percentage of unique paths through the source code that have been followed.
C0 coverage is by far the most commonly used metric in Ruby. Low test coverage can tell you that your code is untested, but a high test coverage metric doesn’t guarantee that your tests are thorough.
To calculate the test coverage for your Ruby 1.9 app, use SimpleCov.
Churn
Churn looks at your source code from a different dimension: the change of your source files over time. I like to express it as a count of the number of times a class has been modified in your version control history.
Depending on its complexity and churn, classes fall into one of four quadrants:
- Upper-right — Classes with high complexity and high churn. These are good top priorities for refactoring.
- Upper-left — Classes with high complexity and low churn.
- Lower-left — Classes with low churn and low complexity.
- Lower-right — Classes with low complexity and high churn.
Wrapping Up
Ruby is blessed with a rich ecosystem of code metrics tools. Generally, the tools are easy to get started with, so it’s worth trying them out and getting a feel for how they match up with your sense of code quality. Use the metrics that prove meaningful to you.
Keep in mind that while these metrics contain a treasure trove of information, they represent only a moment in time. They can tell you where you stand, but less about how you got there and where you’re going.