Flog and to_proc_normal

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)
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.