3

I often see the advice that you should try to make each library independent. And yet in reality I can never seem to achieve this. Is this BS advice or is it actually possibly in any realistic way?

Example: I write a 2D Vector library, I then use that to make a 2D collision library. I then build a 2D physics library that uses both the 2D Vector Library and the 2D Collision library. Am I seriously expected to figure out a way to make the physics library not depend on the other 2 libraries? Likely I'll eventually want to be able to serialize/deserialize the physics and collision data which will mean I'll want to make them further depend on some serialization library.

Another example: I have a string library with lots of string functions (think endsWith(str, ending) and startsWith(str, start) or whatever. I then write a templating library that uses my string library.

I rarely seem to be able to write a new library that doesn't use other libraries.

I could write interfaces and then write adaptors for all of those to attempt to decouple one library from another but it's unlikely to be performant and often the models of the different libraries mean that even if I could decouple with an interface trying to use a different library than the one originally decoupled will end up being impossible or massively non-performant.

So, is that original advice BS or is there some way to actually apply the advice in the real world?

  • 7
    it's not unsurprising that higher level libraries would depend on lower level libraries – ratchet freak Apr 4 '14 at 22:45
  • 2
    I think the idea is that each library is standalone. So If you want to use library A with library B then so be it, but they should also just work on their own. If library A needs library B to work, then that's poor coupling. Also, you've described the use of multiple libraries to build an app; it's the app then which depends on the libraries, and not the case of libraries depending on other libraries (if they're decoupled properly). – nobism Feb 8 '18 at 6:28
13

The idea behind that advice is that dependency management sucks on many platforms (keyword: “DLL hell”, although the problem exists in various degrees for all platforms and all languages). This is not just a problem of having compatible libraries for dynamic linking, but also for fetching the correct version of every dependency before compilation. If these dependencies are not managed automatically, this can go wrong.

Once a proper dependency management is in place (e.g. all major Linux distros have package managers like apt or rpm, various language-specific solutions like tlmgr, cpan, pip, gem, npm, maven, … exist)1 the cost of having dependencies is negligible when deploying your library.

And you should reuse existing code wherever possible: It is generally infeasible to rewrite everything yourself, and we can look much farther “standing on the shoulders of giants” (Newton). Abstraction is a prerequisite for building non-trivial programs.

Incorporating dependencies directly into your library (i.e. rewriting these dependencies) can be a valid option in some cases:

  • This case of Not-Invented-Here is justified, e.g. for legal reasons.
  • The required levels of performance can only be reached by finely tuned code.
  • No infrastructure for deployment exists, so everything has to be bundled together.

1: tlmgr (TeX), cpan (Perl), pip (python), gem (Ruby), npm (node), maven (java)

  • 3
    Dependency hell is still a thing with a package manager, it just manifest differently. – immibis Jan 31 '18 at 4:53
  • Most of the package managers you listed are not sufficient to avoid dependency hell. Take Ruby gems: You need an additional step, as for instance provided by Ruby’s Bundler. – Konrad Rudolph Feb 2 '18 at 12:27
  • One can still re-use code without dynamic linking or re-writing, be it through static linking, just including it in your project (e.g. header-only libs), or (if really needed) copy-paste. Whether that bloats the executable or shrinks it, it might make inlining and therefore optimizing easier. – Deduplicator Feb 4 '18 at 20:42
  • 2
    package managers you listed are not sufficient to avoid dependency hell. I guess package managers are not intended to avoid dependency hell. Just to make it less painful. – Laiv Feb 5 '18 at 7:49
9

It's entirely possible to build libraries using bare code. In fact, I'll go out on a limb and say that it's not uncommon to do so. However...

The history of software is laying abstractions on top of older abstractions. Once you've figured out how to do a thing, it is customary to wrap that thing in a function, module or library and call it, rather than reinventing the wheel every time.

In this way, you stand on the shoulders of the ones who have come before you. So you can either climb Mount Everest by foot, or get airlifted to the summit by helicopter. Each approach has its own advantages.

2

I would say that libraries should only depend on the libraries it absolutely needs to get the job done. They shouldn't need to depend on libraries that unrelated parts of the system depend upon.

Sometimes you will see that LibraryA depends on LibraryB and LibraryC. Then if LibraryZ needs to depend on LibraryA, then it also has to depend on LibraryB and C. That's the kind of dependency hell that should be avoided when possible. LibraryZ shouldn't need to know anything about Libraries B & C.

A slightly roundabout real world example....

I'm currently building a system with a number of plugin libraries, and had to answer a similar but slightly different question:

"Is it possible to have plugins that know nothing about any other plugins, but still use information/functionality from other plugin libraries?"

(eg Web Pages have Navigation on them, but to build a Navigation widget - you need to know what Pages are available and the urls of those pages)

After a lot of investigation and prototyping I decided on a messaging bus system. Everything knows about the messages for the system (and a few other core implementation libraries), but they don't know anything about what handles those messages.

While this isn't exactly "libraries don't depends on any other libraries" in this question, it does seriously limit the extent of dependency injection and dll hell, as PluginLibraryZ knows absolutely nothing about PluginLibraryA or any of the libraries that PluginLibraryA depends on (eg library B & C from above). The plugin libraries only know about the messages library, and other libraries that are absolutely necessary to do its own job, and nothing more.

So for your example here, your Physics library would still depend on the 2D vector and 2D Collision libraries - but another part of the system (eg ThrowBall library) that had a need to use the physics library could just raise a message of "WhereDoesBallLand" with all the required parameters (eg weight of the ball, angle and force of throw etc). So your Physics library would do all the calculations to find out where the ball would land, and the ThrowBall library would get an answer to the "Where does the ball land" question. But the ThrowBall library wouldn't depend on the Physics library or the "D vector or Collision libarries.

NOTE: I'm sure that Message Buses aren't the only way of achieving this separation of functionality and dependencies - but it worked for me in my plugin library dependency issue. I even got it down to the frontend website didn't need to know about any of the plugin libraries - just the messages and a couple of core functionality libraries.

1

I often see the advice that you should try to make each library independent. And yet in reality I can never seem to achieve this.

This kind of advice, like most things, should be taken in moderation accompanied by a a large dose of critical thinking about your specific situation.

40 or 50 years ago, computers weren't being used for jobs anywhere near the level of sophistication that they are today. The relative simplicity of that era's problems meant that the building blocks required were themselves a lot simpler, so any random library was more likely to have few or no dependencies. Our ability to tackle more-complex tasks comes from what we learned while conquering the easier ones. It follows that, having solved a problem and implemented the code to do it, we're not going to solve the next problem by revisiting everything we did as if it were all new material. We'd never make any progress doing that.

Other than pathologically-simple cases like a floating-point math library where the functions are simple enough that there isn't a need for dependencies, many real-world cases have them and there's no sin in it. Being able to break your libraries up into the right pieces shows that you have a good handle on re-use and that you understand where to place the boundaries.

If you're getting into interfaces and adapters or a kitchen-sink library solely to comply with someone's dogmatic definition of a "best practice," it's probably time to stop and reevaluate what you're doing.

protected by gnat Feb 2 '18 at 12:01

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.