"Safe" monkey patching?

Ovid on 2008-08-05T13:01:34

The problem: sometimes you need to use a module, but it's broken. There are several really bad strategies for this (I'll explain them after my idea, in case you're curious).

Let's say you want 'DELETE' functionality in HTTP::Request::Common. I'm thinking about writing something like the following:

package HTTP::Request::Common;

use nextlib 1.26;

sub DELETE { _simple_req('DELETE' , @_); }

1;

That would:

  1. Find and load the HTTP::Request::Common which would have been loaded if this one didn't exit.
  2. Ensure that its version number hasn't changed (in case it's upgraded, you want to see if the "broken" behavior is fixed).
  3. Allow you to then monkey patch it.

Thus, you can monkey patch code, but if the module gets upgraded (and the version number is bumped up), this code could either fail dramatically or maybe just warn very, very loudly.

We have at least 15 modules with local patches and frankly, I'm struggling for a better solution. There have been all sorts of solutions discussed for this problem, but all of them (including mine) suck really, really hard.


Diffs?

davehodg on 2008-08-05T14:44:18

Sounds like a job for maintaining diffs to me. Pull the dists, apply the patches as usual.

The bonus is you can overlay lots of other stuff.

Or if the module owner is being unresponsive/unco-operative, just fork it and develop it yourself?

Or would creating a subclass with your additions work?

You need version ranges

btilly on 2008-08-05T18:01:04

Unless you always have the same version on production and every development machine. Otherwise one machine upgrades, finds the bug is still there, changes the version number, and breaks everyone else.

You should indicate whether versions after the last are fixed, or just not tested. That way you can distinguish between, "I'm continuing to patch this machine that has not been upgraded yet" and "I'm not patching this current machine" and "Check whether this monkeypatch is still needed now!"

Of course once you put these two pieces of functionality together, then you suddenly want a different module. Now the module you want would have different patches for different ranges of versions. There are many possible APIs for this. I'd personally lean towards having a bunch of chunks of code in DATA which are separated by something indicating which range of versions gets that monkeypatch. But use your imagination, you might find something else clearer and cleaner than that.

If you take this advice then I'd suggest calling the module that enables it MonkeyPatch. So it could look something like this for something that will patch versions 1.24-1.26 and would die on version 1.27:

package HTTP::Request::Common;
use MonkeyPatch;

1;

__END__

#####PATCH: 1.24 - 1.26 : die

sub DELETE { _simple_req('DELETE' , @_); }

Subclass?

djberg96 on 2008-08-05T20:18:16

Is subclassing an option?

Re:Subclass?

chromatic on 2008-08-06T16:29:59

I thought subclassing went out of fashion when the world discovered METAPROGRAMMING! (Alternately, when the world discovered reimplementation and uploading to the CPAN!)