One nice piece of advice for designing flexible programs is depend upon abstractions, not implementations.
This is the idea behind the extract class refactoring. You package up some set of data and functionality, and only allow clients to interact with it through a public API. The class’s internal workings are intentionally hidden.
This tends to lead to flexible designs because implementation details can be changed without affecting any of the calling code. Changes are kept to just one file, which makes them easier, and ease of change is the best test for good design.
Most programmers are at least somewhat familiar with this idea. However, it’s easy to forget it when you start working with outside libraries or services.
Here’s a paraphrased example from a Rails app I reviewed recently. This app uses the braintree gem to charge users, create subscriptions, and refund money.
Just because we’re calling methods in another class does not mean we’re programming against an abstraction. It’s certainly better than making raw HTTP calls to Braintree, but our choice of vendor and gem used are implementation details that have leaked into our business logic.
With calls to Braintree littered throughout, switching to another vendor will require the editing and re-testing of many files. We’ve fallen short of the ideal we described above, where one change requires edits only in one place.
Fortunately, the fix is quite easy: a simple wrapper.
Another good name for the new class would be PaymentGatewayAdapter, as it’s an example of the adapter pattern.
This code is only subtly different, but yields significant benefits:
- Switching from Braintree to another vendor now requires edits to only one file. If this change is somewhat likely, this benefit alone probably justifies the refactoring.
- If Braintree changes its public API, we again have only one class to edit.
- Testing has become easier. PaymentGateway gives us what some people call 'seams': a place where components join together where we can easily stub behavior. Before, unit tests required stubbing Braintree's code. Upgrading the gem could potentially break many tests. Now, we can stub our own methods, which is safer.
- We can use method names that better match our problem domain. A small win, but a pleasant one.
- We can change the parameters that PaymentGateway's methods expect, such as we did in PaymentGateway#refund.
- We have a better home for constants like SUBSCRIPTION_AMOUNT and similar data.
The only downside I can think of for this code is that it adds a bit of indirection. You’ll have to jump through one additional file to found out how customer charging is implemented. However, the wrapper is very thin, so I don’t think this cost is large.
It’s easy to accidentally find yourself depending on implementation details. External libraries and services are frequently sources of this, and warrant extra suspicion. Implementations that have a decent likelikhood of change should always be wrapped in sanitary fashion.