
Why Ruby Class Methods Resist Refactoring - jabo
http://blog.codeclimate.com/blog/2012/11/14/why-ruby-class-methods-resist-refactoring/
======
klochner
Ruby shines because it is a terse/expressive language that doesn't need heavy
use of patterns, so you can grok a lot of logic at a glance. You want a hash?
Write it inline, you don't need a lazy-loaded memoized function.

The original was the easiest to understand, and the refactored versions didn't
expose much that would benefit from testing or code reuse. This feels over-
engineered to me.

And not to nitpick, but since when is _class << self_ an ugly form? It makes
perfect sense if all methods will be class methods.

~~~
ryanto
The biggest problem with "class << self" syntax is that it is hard to read in
classes that have more than a handful or so lines. At quick glance you might
not be able to tell if "def xxx" is a class method or instance method.
However, with "def self.xxx" it is more easily recognizable.

Btw, I think its a small nitpick. Plenty of great code bases use "class <<
self".

~~~
klochner
Totally agree, that's why I said 'if all methods are class methods...', in
which case _class <<self_ should go at the top of the class body. If there are
instance methods I prefer _def self.bar_.

------
stormbrew
I feel like this misses the real reason the construction presented is awkward.
It's not because class methods are hard to refactor, it's because class
methods in ruby are _not static methods_. The thing being attempted seems to
be to avoid creating a method that doesn't access state, as if this were C++
and state is relatively expensive.

A class method in ruby is not a static method. It is an instance method on the
class. It sounds like nitpicking, but the distinction is real. You call #new
on a class because the class is The Thing that makes objects, not an awkward
pseudo-global scope. Class methods should thus be things for which the class
is the subject.

------
simon_weber
"The signature of 'this shouldn't be a class' is that it has two methods, one
of which is __init__." [0]

Why not write a function? This addresses all of the objections. Then refactor
by pulling out concerns into other functions.

Need to share logic around the functions? Use a decorator. Passing too many
attributes? Create a class to contain them.

I think classes are best avoided until it's obvious that encapsulating state
provides a benefit, which I don't see in this case.

[0]
[https://www.youtube.com/watch?v=o9pEzgHorH0&t=3m](https://www.youtube.com/watch?v=o9pEzgHorH0&t=3m)

~~~
stormbrew
Comparisons to python are difficult here because in Ruby there's no such thing
as an object that doesn't have a class. Even the classes have classes (which
is, as I touched on elsewhere, what class methods actually are -- instance
methods on the class).

You can store a proc in a variable or a constant, but you're getting into some
very ugly patterns at that point and you still haven't really left classes
behind.

In Ruby everything is encapsulated and there's really no point in fighting
that.

~~~
lmm
> Comparisons to python are difficult here because in Ruby there's no such
> thing as an object that doesn't have a class. Even the classes have classes
> (which is, as I touched on elsewhere, what class methods actually are --
> instance methods on the class

So exactly the same as Python then?

> You can store a proc in a variable or a constant, but you're getting into
> some very ugly patterns at that point and you still haven't really left
> classes behind.

How so? If you need to pass around something that quacks like a proc, do it as
a proc.

~~~
stormbrew
> So exactly the same as Python then?

Recently? Sort of, yes. Though in python it's more like everything has a
blessed hash, and the assumption that everything is an object is much more
thoroughly baked in in Ruby. Also the way that classes interact with their
instances is quite different.

For example, in python a member of the class that is not overridden in an
instance also happens to be a member of an instance. This is how default
attributes work:

    
    
        >>> class x(object):
        ...     pass
        ... 
        >>> x.blah = 1
        >>> x().blah
        1
    

This actually gives you something qualitatively like a static method (through
the staticmethod decorator), it is callable from an instance as well as the
class and behaves always as if it has no access to the instance's state.

This kind of blending of instance and class doesn't happen in Ruby. The class
is a completely distinct object with its own independent state, and is
instantiated just like any other object, some syntactic sugar aside. It is an
instance of class Class and its methods are the methods of class Class, while
instances created by calling its #alloc method have the methods that are
defined on it.

Python now shares some concept of everything being an object, I'll grant, but
their models remain quite different and you really do have to work
significantly against the grain to effectively use ruby in a non-OO fashion.
In ways you simply don't in Python.

------
programminggeek
One of the biggest problems in ruby is that there is often no clear difference
in code between a method and a variable. This leads to some seriously bad code
by developers for the sake of elegance.

For example, you can have a Model.foo method that seems harmless enough,
except that actually calls other methods on other models to do some
calculation that maybe two or three layers deep makes a call to an external
service. Let's say for the sake of example that two or three levels of
database queries and api service calls ends up spawning hundreds of queries
and at least dozens of external api calls. This takes anywhere from 10 to 30
seconds if the dataset is large enough.

A junior programmer would think he is doing the right thing by simply calling
Model.foo because he might be so foolish to not know the difference between
Model.foo and Model.foo(). Since Rails by default doesn't annotate database
model attributes, it's even easier to think that Model.foo Model.bar
Model.whatever are just attributes so there would be no performance penalty
for using them.

As it turns out, this makes your code terribly slow even though it looks
"elegant" and "clean". Why? Because it is super non-obvious to many/most
programmers that there is a potentially huge difference between a simple
attribute on a model vs. a calculated attribute method derived from
potentially a multitude of other data sources. When you are calling Model.foo
and Model.bar, it is completely unclear that one might be a method and one
might be an attribute.

I've seen this happen many times to both really good and really average Ruby
programmers. At this point it is a genuine flaw in Ruby that is compounded by
the way I see people using ActiveRecord and Rails.

~~~
hayksaakian
Its all about abstraction.

If you're calling Model.foo or Model.bar, you don't care about the specific
implementation, you just want foo or bar.

The calling code doesn't need to know how Model works.

This way you can refactor either ones into normal attributes down the road
without changing any dependant code.

~~~
programminggeek
Perhaps, but the simple habit of Model.foo() (for methods) and Model.bar for
data would make it easier to spot when one is an attribute and one is a
method.

~~~
phamilton
That seems to fit the definition of leaky abstraction (an implementation
detail influencing whether how you use it). Leaky abstractions are all over.

That same logic would suggest that we should name our methods Model.foo_slow()
and Model.bar_fast() because the execution speed is important and should be
transparent to the caller.

In practice, Model.foo() ends up often being memoized so that all but the
first call are practically equivalent to accessing an attribute.

------
krosaen
In some ways using an object to share parameters between helpers is an
alternative to having a function that can have internal helper methods that
all have access to the original parameters via the shared enclosed
environment. But that doesn't make this "object oriented" and I don't see why
that should be a goal in and of itself.

------
kyllo
A class is just a reusable definition of a data structure. The
SyncToAnalyticsService example is not really a data structure at all, it's
just a function. It's a verb, not a noun. But it's made into a class just
because OOP and patterns. This feels like someone tried to write Java in Ruby.

------
ollysb
Why not just add a new class which is instantiated and called inside the
perform method? There are valid reasons for not choosing class methods but
claiming it limits the way in which you implement a method is just nonsense.

