Finding someone else's class names in your app
A developer I work with made the comment that, “Seeing someone else’s company name in your code is a smell.”
That’s pretty darn good wisdom. Isolating the parts of your app most likely to change from those that are stable could be one of the best heuristics for maintaining a growing app. It’s good design and is the subject of the metrics from the churn gem. This kind of proxy class can also make testing easier. You can add test-env specific code, or stub methods in your app instead of on a gem (horrors).
So…next time you add a gem to your application, take a few moments to consider whipping up a proxy class that hides the gem’s classes. The easiest way to do this is to delegate directly to the gem’s methods using the same method names. You could think about using my favorite toy, #delegte, in Rails, but sometimes the simplest thing works great.
As an example, I recently implemented the fantastic stats tracking tool InstrumentalApp. Instead of referencing their gem directly throughout my code, I wrapped it up, making a change to another stat tracking library easier down the road:
module YourAppName module Metrics # All class methods class << self # Public: increment a counter with the given name def increment(key_name) I.increment(key_name) end # Public: Record a value at the current time for the given keyname def gauge(key_name, value) I.gauge(key_name, value) end # Public: Store the time it takes to complete the given block def time(key_name, &block) I.time(key_name) do yield end end # Public: Store the time in ms it takes to complete the given block def time_ms(key_name, &block) I.time_ms(key_name) do yield end end # Public: Change whether the stats are recorded in the background # without interrupting the request (and may be dropped if there are # connection issues) or if it will block the request. # # Don't turn on "synchronous" in production or you'll have a bad time. # It's really only for back-filling old data when we have all the # time in the world and don't want to lose any metric. def synchronous=(value) I.synchronous = value end end end end
While we could get cute with Ruby and do some #method_missing and *args, I think it’s better to be explicit with our proxy class. List the exact methods you will support long-term (since you’ll need to keep them around if you switch libraries) and with what arguments.
So take a look through your app. Where are you using the class name of a gem when you should consider wrapping their class with your own?