For a long time I thought automatically requiring dependencies was significantly better than manually requiring them. It’s hard to remember now exactly how I arrived at this opinion, but it was certainly influenced by using Rails in the early days before Bundler existed.
Rails had a confusing and unfortunate mix of patterns back then. Dependencies were manually required but then later became automatically required. Code in the
app folder was auto-loaded. Code in the
lib folder was auto-loaded but then later became manually required. This was so confusing that it became a common (although bad) habit to switch
lib back to being auto-loaded.
The place Rails ended up very clearly discouraged manually requiring dependencies and that made sense to me at the time. Why do something manually that can be done automatically?
Now I’ve generally reversed my stance. I think using
Bundler.require has some distinct advantages, especially for larger projects.
It provides amazing design feedback and documentation
Manually requiring dependencies at the top of every file very explicitly tells everyone exactly what dependencies that file has. This makes code easier to read, understand, test, and refactor. It also makes it painfully obvious when a class is too complicated. If a class needs a lot of
require statements to work there’s probably too much going on in that class.
It forces you to think about your dependencies and the cost associated with using them
Dependencies aren’t free. Every dependency you have adds complexity and increases coupling. Automatically loading all dependencies makes them seem like they’re free and can be used whenever and wherever in your application. Having to manually require a dependency to use it forces you to justify its use and keeps your dependency list lean.
It makes removing dependencies easier
If you automatically load all your dependencies when you load your environment, you have no way of knowing what parts of your application are using any given dependency. You’d have to manually search for class names, method names, and other ways of identifying a dependency in order to update it or remove it, which is tedious and error-prone. If you manually require dependencies, you can just search for the
require statement and remove or replace the dependency.
Ruby is designed to have un-scoped dependencies—once you require a dependencies it’s available everywhere. There are some distinct advantages to this design, but one extreme downside is it allows you to stop thinking about your application as a structured collection of small, loosely-coupled objects, the negative effects of which compound as your application becomes larger. Manually requiring dependencies is a great way to subtly enforce better design.