20 July 2011
Are class methods evil?
Nick Sutterer who is honorable citizen of my city published a post Are class methods evil? It’s really worth reading as he mentions couple of important aspects of Object Oriented approach and how they are used by Rails community.
Nick mentions my tweet
I’m more and more against using class methods. It often leads to procedural-like programming.
Why class methods?
I often hear “Why to create an object when I can just call a class method?”. The reasoning behind that is quite obvious:
- 1) shorter code
- 2) less objects = less GC
- 3) stateless behavior and no need to initialization
1) and 2) are completely valid arguments. However in my opinion these arguments shouldn’t impose the architecture. My main objection is assumption that given behavior is stateless thus doesn’t need to be encapsulated in object.
Stateless vs Stateful
- Class methods are executed in context of their arguments
- Instance methods, in addition to arguments, are executed in context of state of the object
Of course there are more differences but from my point of view that one is quite significant.
Using class methods means we can’t keep a state thus the state must be passed in method arguments. If some module is designed around class methods we deal with procedural-like programming passing arguments from one method to another.
Naturally the state may be kept in class variables but it’s introducing a global state which as Nick remarks should be abandoned.
Not a real world example but I think it shows the difference.
class CssProcessor def self.process(code) bust_cache(minify(code)) end def self.minify(code) AwesomeMinifier.minify(code, :type => :cs) end def self.bust_cache(code) AwesomeMinifier.bust_cache(code, :type => :css) end end CssProcessor.process("some_css_code")
Object oriented version
class CssProcessor def initialize(code) @minifier = AwesomeMinifier.new(code, :type => :css) end def process minify bust_cache end def minify @minifier.minify end def bust_cache @minifier.bust_cache end end processor = CssProcessor.new("some_css_code") processor.process
Initialization process and keeping a state isn’t useless even in such a simple example. At the first sight it may seem awkward to use an object when class method can be used more easily. Although it’s often a trap. The bigger codebase the more time needed to maintain a procedural code.
I prefer to think in object oriented manner from the ground up. It is like MVC a bit harder at the beginning but it quickly starts to pay for itself.
In his article Nick shows some examples of ActiveRecord usage. It is hard to avoid class methods due to ActiveRecord design but Andrzej Krzywda showed how to deal with it. I recommend reading his article.
Everything is an object
Rubyists like to emphasize that in their favorite programming language everything is an object. It’s not like C++ or JAVA which are “not fully object oriented”. But that fact is often forgotten and misunderstood. I think utility methods are a great example.
Is someting wrong with that code?
Math.sin(2) * Math::PI
In that case we don’t use the fact the number 2 is an object in Ruby. It’s used like a primitive. This is how we do it in C++.
2.sin * Math::PI
Of course Fixnum doesn’t have sin method. But we can extend Fixnum to provide it. Monkey patching Fixnum is harmful but there are plenty of other options
- singleton object methods
- monkey patch Fixnum with refinements (Ruby 2.0)
When using ActiveSupport it’s quite obvious to do
so why not to spread object approach over more parts of the codebase?