A couple of weeks ago, I came up this post on twitter:
#Ruby when you think you've seen everything in Ruby pic.twitter.com/6Q7m9kOcwV
— Konrad Oleksiuk (@koleksiuk) October 16, 2020
Just after reading that piece of code, in my mind two contrasting thoughts appeared: clever & evil.
The correct term to represent my state of mind about that is: sublime.
The Sublime (from the Latin sublimis, or in the variant sublimus, composed of sub-, "below", and limen, "threshold"; therefore properly: "what is at the limit", or of sub-, "below" , and limen-, "threshold", properly "that reaches below the highest threshold") is an aesthetic category that dates back to classical antiquity and later to Romanticism.
(source: Wikipedia)
😎 What's clever about this?
It's the power and pervasiveness of ruby metaprogramming: everything in ruby, also what may appear as a simple "statement" of the language, in reality is a method call (+ syntactic sugar that makes the parenthesis optional).
It makes testing of private method as easy as the testing of public ones. Let's explain with a simple example.
class MyClass
attr_accessor :object_id
...
# some public methods
...
private
def object
@object ||= object_class.find_by(id: object_id)
end
end
How would you test private method object
on MyClass
?
Without that trick you would have four choices:
- Make private method that need to be tested public. Of course, this choice has no sense: the design of an API about what is public (= must be considered available to clients) and what is not, should not be conditioned by testing constraints.
- Do not test private methods: someone may agree that's fine. I don't and I'll explain why in the next paragraph.
- Exploit ruby's message passing feature:
require 'test_helper'
class MyClassTest
let!(:obj) { AClass.create }
let(:subject) { MyClass.new }
def test_object
subject.stubs(:object_class).returns(AClass)
assert_equal obj, subject.send(:object) # this sounds quite "awkward"...
end
end
😈 What's wrong (evil) about this?
The question on where a purist of code and TDD may rise his finger is:
Shall I really test private methods?
The good Steven Solomon (@soonernotfaster) has written an excellent post about what should be considered the right way to test private methods and I agree with him.
So, my intention here is not to rewrite the same considerations, but just add my personal point of view about one simple fact: as I've written above, testing (especially legacy code), seldom, is a path toward good code design.
Of course, if you're doing right TDD, writing the code first will let you enjoy the beautyness of the so called "emerging design" it brings, and probably you'll never had to test private methods directly.
But
If you daily deal with legacy code you will often have to compromise between the optimum and the just works solution.
In these choices, bring on a bad original design decision by keeping the code uncovered will just you take your codebase deeper under in the grave of technical debt that - one day or another one - will present its bill.
One the other hand, the cost to fully refactor in just one shot the whole "spaghetti code" mud might be too hard to face and the temptation could be to say: "...well, I'll covert this with tests later, someday". That means: never.
So what?
Shall I use it or not?
Here we come in the land of the opinions about pattern and anti-pattern of software design.
I think that in this case there is no indisputable truth.
Perfection is an asymptote: you know where it is, it can be your goal, but you know you'll never be able to reach it (and I think it's fine so).
Moreover, it's a path, made by little steps: we should approach it in a way that bring us each day a step further towards it, without falling in the chasm of despair and resignation.
In my little city, Bologna, we have a proverb, hard to translate in english: I'll try to do it:
Rather than nothing, it's worth "rather"...
or, paraphrasing: whatever you do to enhance something, is better than to do nothing at all.
I guess (and hope) that, in our path to perfection, someday we'll look back at that code, thinking: "What a horrible piece of code I wrote: let's remove that private unless and refactor!"
Don't blame yourself now, for writing what you already know that will be blamed (probably and hopefully) someday in the future: that day, will be a good day, since you will have grown, as developer, and you won't, if you've hadn't done that little, bad, useful step first.
Top comments (0)