How to Write Awesome code – Ruby on Rails – V1
Everyone wish to write the better code, But How, Is there any pattern / behaviour? Can anyone point to your code what can be improved?
The answer is YES, There is pattern you should follow the OOPS way by it’s core. I have written SOLID principle before this, Please check this too. I am going to write more series in this topic as it’s really interesting and helpful to everyone.
In Ruby it’s damn easy but we don’t understand what mistake we are doing, so chances of improvement is less. So, Let’s take an example of below MyClass in code1 as below.
Code1:
class MyClass
attr_reader :input
def initialize(input)
@input = input
end
def double
case input
when Numeric then target . 2
when String then target.next # lazy fail ex
when Array then target.collect {|o| MyClass.new(o).double}
else
raise “don’t know how to double #{input.class} #{input.inspect}”
end
end
end
The result of the code1 is Output1 as below:
>> MyClass.new(‘aaa’).double
=> "aab"
>> MyClass.new(49).double
=> 98
>> MyClass.new([‘b’, 6]).double
=> ["c", 12]
>> MyClass.new({‘x’=>‘y’}).double
RuntimeError: don’t know how to double Hash {"x"=>"y"}
from (irb):73:in `double'
from (irb):80
from :0
The above code looks Good, Right ?
You’re probably used / gone thought with this pattern. and, Its everywhere in Ruby on Rails.
But, This style of code is Absolutely Wrong and that you should do a different thing.
Why is the problem in above code?
If I change how double
works on any of these classes, MyClass
must change, but that’s not the real problem. What happens if MyClass
wants to double some new kind of object? I have to go into MyClass
and add a new branch to the case statement. How annoying is that?
But that’s the least of it. If I’m writing code that follows this pattern, I likely have many classes that do stuff based on the classes of their collaborators. My entire application behaves this way. Every time I add a new collaborating object I have to go change code everywhere. Each subsequent change makes things worse. My application is a teetering house of cards and eventually it will come tumbling down.
Also, what if some other wishes to use MyClass
with their new SuperDuperClass
object? They can’t reuse MyClass
without changing it since MyClass
has a very limited notion of what kinds of objects can be doubled.
MyClass
is both rigid and closed for extension.
Code2:
class Numeric
def double
self * 2
end
end
class String
def double
self.next
end
end
class Array
def double
collect {|e| e.double}
end
end
class Object
def double
raise "don't know how to double #{self.class} #{self.inspect}"
end
end
class MyClass
attr_reader :input
def initialize(input)
@input = input
end
def double
input.double
end
end
What basically it should be as below (Something like this) :
Using this new code, Output1 will be the same as before, but now we can also use this way as output2:
Output2:
>> 'aaa'.double
=> "aab"
>> 49.double
=> 98
>> ['b', 6].double
=> ["c", 12]
In this above code2,
The Objects are what they are and because of that, they actually behave the way they do.
Above statement is very simple but incredibly important.
They tell each other what, not how.
What is the event/notification/request and it is the responsibility of the sender.(sender should know).
How is the behaviour/implementation and it should be completely hidden in the receiver (sender not required to know).
Therefore:
MyClass
should not know howNumeric
implementsdouble
.MyClass
should just ask target to double itself.- All possible targets must implement
double
. - Object should implement
double
to help you get your Ducks in a row.
Thanks for reading. For more information I highly recommend to read Sandi Metz books.
Courtesy 99 Bottles of OOP by Sandi Metz.
Happy Learning & Coding!
Note: This concept can be used with any programming language.