Monkey-Patching VS. S.O.L.I.D. Principles

Monkey-patching Vs. S.O.L.I.D. principles?

There's a difference between monkey-patching (overwriting or modifying pre-existing methods) and simple addition of new methods. I think the latter is perfectly fine, and the former should be looked at suspiciously, but I'm still in favour of keeping it.

I've encountered quite a few those problems where a third party extension monkeypatches the core libraries and breaks things, and they really do suck. Unfortunately, they all invariably seem stem from the the third party extension developers taking the path of least resistance, rather than thinking about how to actually build their solutions properly.

This sucks, but it's no more the fault of monkey patching than it's the fault of knife makers that people sometimes cut themselves.

The only times I've ever seen legitimate need for monkey patching is to work around bugs in third party or core libraries. For this alone, it's priceless, and I really would be disappointed if they removed the ability to do it.

Timeline of a bug in a C# program we had:

  1. Read strange bug reports and trace problem to a minor bug in a CLR library.
  2. Invest days coming up with a workaround involving catching exceptions in strange places and lots of hacks which compromises the code a lot
  3. Spend days extricating hacky workaround when Microsoft release a service pack

Timeline of a bug in a rails program we had:

  1. Read strange bug reports and trace problem to a minor bug in a ruby standard library
  2. Spend 15 minutes performing minor monkey-patch to remove bug from ruby library, and place guards around it to trip if it's run on the wrong version of ruby.
  3. Carry on with normal coding.
  4. Simply delete monkeypatch later when next version of ruby is released.

The bugfixing process looks similar, except with monkeypatching, it's a 15 minute solution, and a 5-second 'extraction' whereas without it, pain and suffering ensues.

PS: The following example is "technically" monkeypatching, but is it "morally" monkeypatching? I'm not changing any behaviour - this is more or less just doing AOP in ruby...

class SomeClass
alias original_dostuff dostuff
def dostuff
# extra stuff, eg logging, opening a transaction, etc
original_dostuff
end
end

What is monkey patching and why is it so abhorrent?

I wouldn't say monkey patching is bad, it's more like a "should avoid" practice, there are definitely scenarios where monkey patching is useful.

Another way to solve this problem is through inheritance so you could have something like:

class SuperHash < Hash
def delete_blanks!
delete_if { |k, v| v.is_nil? }
end
end

Is it a bad idea to monkey patch Object to have a more natural way for testing if an element is in an array?

Ignoring the general arguments for/against monkey patching in Ruby, the code you describe is an example of a convenience method to help with your readability. Arguments against its inclusion also apply to the generic case as well, so there doesn't appear to be a specific drawback. Technically, this method is already included in the Ruby on Rails framework, so the authors shared your view in supporting it as a natural expression.

Automatically require a class when running / starting ruby

ruby and irb both take a -r option that lets you specify a library to load when running those executables. If you want to automatically load your monkey.rb library, you can start ruby with the invocation $ ruby -r monkey (assuming monkey.rb is in your $RUBYLIB path. If you don't want to do that each time, you can set up an alias in your shell config file. For example (in Bash), you could add:

alias ruby='ruby -r monkey'

class self, alias_method, and monkey patching Mechanize::Cookie

Looks like the original parse method has a yield cookie if block_given? statement in it. You'll need to be able to pass a block as well.

EDIT:

To be more clear...

class Foo
def self.x
yield "yielded from x!" if block_given?
end
end

class Foo
class <<self
alias :y :x
end
# new implementation of x's last parameter is an optional block
def self.x(&block)
puts "in redefined x."
puts "block=#{block}"
self.y(&block) #use the block as the last parameter
end
end

Foo.x{|value| puts "value is '#{value}'"}

Monkey patching with mock in before block

You could just stub the new method for a test block or entire spec file:

# test file

# you could also create a class double if you need its methods:
# https://relishapp.com/rspec/rspec-mocks/v/3-9/docs/verifying-doubles/using-a-class-double
let(:slack_client) { double("slack client") }

before(:each) do
allow(::Slack::Web::Client).to receive(:new).and_return(slack_client)
end

# simple example:
it "checks slack client method to be a double" do
expect(SlackWrapper.client).to be(slack_client)
end

How do you detect that monkey patching has occurred in Ruby?

I found this blog posting that touches on how to use method_added to track monkey patching. It's not too hard to extend it to track the methods that were patched.

http://hedonismbot.wordpress.com/2008/11/27/monkey-business-2/:

By using open classes, we can re-define method_added for instances of Class and do some custom stuff every time a method is defined for any class. In this example, we’re re-defining method_added so that it tracks where the method was last defined.

#!/usr/bin/env ruby                                                                                                                                                           

class Class
@@method_history = {}

def self.method_history
return @@method_history
end

def method_added(method_name)
puts "#{method_name} added to #{self}"
@@method_history[self] ||= {}
@@method_history[self][method_name] = caller
end

def method_defined_in(method_name)
return @@method_history[self][method_name]
end
end


Related Topics



Leave a reply



Submit