---
title: Wrap your dependencies
teaser: It will make your code more flexible and easier to maintain.
tags: refactoring,good code
author: Matheus Richard
published_on: 2024-08-26
---

We stand on the shoulders of giants. We use libraries and tools that other
people created to speed up our work. That's a good thing! But before you take a
new toy and start using it in your code, consider wrapping it.
Here are some ways you can do that:

## Wrapping behavior into a function

Say we are using [HtmlSanitizer] to filter user input in our front end:

```js
safeHtml = HtmlSanitizer.SanitizeHtml(someHtmlString);
// do something with the sanitized html
```

If we have to use that in multiple places in our code, we might want to wrap it
in a function, but some people just won't bother. After all, <q>it's just a simple
function call</q>, right?

What if we now also need to filter input in our JS back end? HtmlSanitizer is a
front-end-only library, so let's replace it with [sanitize-html] to keep fewer
dependencies and just one API to learn. Bummer! Now we have to change every
place we use HtmlSanitizer. If, instead, we had wrapped it in a function, that
would be an easy change:

```diff
- import HtmlSanitizer from 'html-sanitizer';
+ import sanitizeHtml from 'sanitize-html';

function sanitizeHtml(html) {
-  return HtmlSanitizer.SanitizeHtml(html);
+  return sanitizeHtml(html)
}
```

## Wrapping behavior into a module or class

If your needs are more complex than a single function, you might need to wrap
behavior in a module or class. Let's say we're using an HTTP client in our Ruby
code:

```rb
response = HttpClient.get('https://api.example.com/v1/resource')
```

And, again, let's say we're using this in multiple places and now we want to
change the HTTP client library. Even if the APIs were identical, the libraries
likely have different response objects and error handling.

If the previous library raised exceptions on errors, for example, but the new
one returns a response object with a status code, we'll have to change every
place that uses it. Even if the new one _also_ raises exceptions, we'd have to
change _which_ exceptions are rescued. Wrapping it in a module or class will
make this easy to change in a single place:

```rb
module MyHttpClient
  extend self

  Error = Class.new(StandardError)
  Response = Data.define(:status, :body, :headers)

  def get(url)
    OtherHttpClient
      .get(url)
      .then { Response.new(_1.status, _1.body, _1.headers) }
  rescue OtherHttpClient::Error => e
    raise Error, e.message # an error class that we control
  end

  # other methods...
end
```

## Wrapping behavior into a Stimulus controller

This one is quite common in Rails apps. Maybe it's a jQuery plugin, maybe it's a
JS library to make select fields searchable. Our first instinct might be
just to follow the docs and add some JS directly to the app:

```js
import choices from 'choices.js';

document.addEventListener('DOMContentLoaded', () => {
  new choices('[data-choices]');
});
```

My advice here is to wrap that behavior into a [Stimulus] controller. It might
take a few more lines of code in the beginning, but that will pay off in the
long run. Using [Stimulus' Values API] we can easily pass options and have
default values for them.

```js
// In app/javascript/controllers/select_box_controller.js
import { Controller } from 'stimulus';

export default class extends Controller {
  static values = {
    settings: { type: Object, default: { /* default options */ } }
  }

  connect() {
    new choices(this.element, this.settingsValue);
  }
}
```

And in the HTML:

```erb
<%=
  form.select(
    :field,
    options,
    multiple: true,
    data: { controller: 'select-box', select_box_settings_value: settings }
  )
%>
```

<aside class="info">
  <p>Also notice that I didn't name the controller "choices", to be more generic
  and don't tie it to the library we're using.</p>
  <p>I believe we should avoid tools and company names in our code <em>when possible</em>.
  Again, if we change the provider, we won't need to rename everything or (even
  worse!) live with a misleading name.</p>
</aside>

## Ok, but why?

The previous examples bring several benefits. To name a few:

  - **They make you define and agree on a common interface:**
    - What are the inputs? Are you using keyword arguments or positional a hash of options?
    - What are the outputs? Does it raise exceptions or return a result object?
  - **They are easier to test:** You own the implementation of the wrapper now. You can
    mock or stub it and be confident that it will behave as expected.
  - **They facilitate swapping the underlying implementation:** This is a *very*
    common scenario. Today [Choices.js] might be the best library for your
    needs, but who knows what can happen in the future? Maybe it will be
    abandoned, have critical security issues, or you'll just need different
    features that aren't available. If you have a wrapper, you can just replace
    the implementation and keep the same interface (or at least minimize the
    changes). Think about it as private and public methods. You have an external
    facing API that you maintain, but the internal implementation isn't exposed
    to the world.

When you control the interface of your dependencies via a wrapper, you are
lowering the strength, degree, and locality of the [connascence] shared
between your code and its dependencies. This makes your code **more flexible**
and **easier to maintain**.

[HtmlSanitizer]: https://github.com/jitbit/HtmlSanitizer
[sanitize-html]: https://github.com/apostrophecms/sanitize-html
[Stimulus]: https://stimulus.hotwired.dev/
[Stimulus' Values API]: https://stimulus.hotwired.dev/reference/values
[Choices.js]: https://github.com/Choices-js/Choices
[connascence]: https://thoughtbot.com/blog/connascence-as-a-vocabulary-to-discuss-coupling
