Using metrics like flog can be immensely helpful for cleaning up code. However, metrics are fallible, and flog is no exception.
During a session of some serious code refactoring, flog was returning high scores on a couple of methods that appeared to be relatively clean, and the main culprit for the scores was something called to_proc_normal.
Consider this example. We have a Test class and Fruit class. Within the Test class are the following methods:
def fruit_names_1
fruits.map(&:name)
end
def fruit_names_2
fruits.map do |fruit|
fruit.name
end
end
Both methods return an array containing the names of fruits associated to the Test class. They are, functionally, extremely similar. So how do they match up in flog?
11.8: Test#fruit_names_1 test.rb:10
7.5: to_proc_normal
1.5: block_pass
1.5: fruits
1.3: map
4.6: Test#fruit_names_2 test.rb:14
1.5: fruits
1.4: assignment
1.4: name
1.3: branch
1.3: map
Test#fruit_names_1 has a poor score in comparison to its counterpart thanks to this elusive to_proc_normal. We run some benchmarks—with 100,000 calls to each method on an instance of Test—to see if there is a difference in execution time:
Rehearsal ------------------------------------
Benchmarking fruit_names_1:
4.040000 1.750000 5.790000 ( 5.852924)
Benchmarking fruit_names_2:
3.990000 2.060000 6.050000 ( 6.092260)
-------------------------- total: 11.840000sec
user system total real
Benchmarking fruit_names_1:
4.030000 1.750000 5.780000 ( 5.806661)
Benchmarking fruit_names_2:
3.980000 2.070000 6.050000 ( 6.114498)
Benchmarking speaks for itself. While the first method has a flog score that may imply a some unwieldy code with lengthier execution time, it generally runs faster than the second anyway. Of course, flog is still awesome, and it’s one of the more helpful ways to quickly identify “code smells” (like large or overly-complicated methods) in a ruby project.
Note:
These metrics were originally measured on ruby version 1.8.7p72. After upgrading to version 1.9.1p378, the flog scores remained the same, but the benchmarks consistently favored the second method. And the performance difference itself induced a double-take:
Rehearsal ------------------------------------
Benchmarking fruit_names_1:
0.320000 0.000000 0.320000 ( 0.327978)
Benchmarking fruit_names_2:
0.190000 0.010000 0.200000 ( 0.191589)
--------------------------- total: 0.520000sec
user system total real
Benchmarking fruit_names_1:
0.320000 0.000000 0.320000 ( 0.325131)
Benchmarking fruit_names_2:
0.190000 0.000000 0.190000 ( 0.192975)
