Saturday, May 9, 2009

Method Composition

I figured I'd continue my thoughts on writing readable code and post about the advantages of method composition. I practice this pattern religiously as I think it adds an incredible amount of readability to my code. It is another fairly common technique and one that is quite easy to implement. Have a peek at the following example, http://pastie.org/473152 (Had to use pastie it's kind of long.)

Now that is pretty readable. The method body is not too large but there is a lot happening there. Take a look at a quick refactor (about 15 minutes, thanks specs) and let me know if you like the changes:

def self.render_timeline(slice)
data, data2, total_users = collect_data_from_slice(slice)
data_points = data.collect{|m| m[1] }
max = data_points.max
increment_max_until_last_set_of_ten_is_reached(max)
get_second_set_of_datapoints_if_slice_using_second(slice, datapoints, max)
graph_for_breadcrumbs= new_graph_with_data_points(slice, datapoints, max)
setup_right_axis_if_slice_using_second(slice, datapoints, max, graph_for_breadcrumbs)
colorize_graph(graph_for_breadcrumbs)
graph_for_breadcrumbs.set_labels_for_x_and_y_axis(slice, data)
graph_for_breadcrumbs.render
end

Do you see the advantages in readability & maintability? Granted I would bury this code in some sort of class for rendering the graph and perhaps a separate module for extracting data from the slice but you can see how much easier it is to gather what's going on with this method body, right? All I did was take code that was bulky or by itself didn't have an obvious meaning and wrapped it in a method body that gave it purpose. The other thing we did here is clearly expressed the intent of the render_timeline (renamed from timeline) method. As you read, you can almost predict what will happen next (http://en.wikipedia.org/wiki/Principle_of_least_astonishment.) This is great because we now have code that is readable, yields expected behavior, and can be reused by other members on the team!

There are some other great points here that I did not delve into: how easy a rework like this is with specs, composition vs inheritance, etc. But I'll save those for another time :)

Quick note, there are a couple gems I like to use to identify problem code. I especially like to use these gems when I am refactoring large methods/classes:

Roodi, http://github.com/martinjandrews/roodi/tree/master
Flog, http://ruby.sadi.st/Flog.html

Happy Coding!