This is an update to some thoughts shared in Thoughts on Libraries and Optimization. In it, I decomposed the following example from Stack Overflow in the following manner:

// trim from end
static inline std::string &rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}

Was rewritten for clarity as:

static inline void rtrim(std::string &s) {
auto non_space = std::not1(std::ptr_fun<int, int>(std::isspace));
auto erasure_start = std::find_if(s.rbegin(), s.rend(), non_space);
s.erase(erasure_start.base(), s.end());
}

And I then suggested, naively, that the following was a better solution:

#include <boost/algorithm/string.hpp>
// ...
trim_right(your_string);

The reasoning on this was purely based on simplicity of understanding, from the perspective of someone who was working primarily in web development at the time. In that context, throwing in an include to save a bit of utility code seemed like a good idea.

A lot of that article’s thoughts on decomposing complex statements for readability stand. The idea of throwing friggin’ BOOST in for a string trim function does not.

I was wrong.

Dependencies are the enemy.

Throwing a 451MB library at your project to get a one-line trim_right string function is ridiculous. Adding dependencies on a whim is nuts in general. It’s why npm-based projects are often horrific clusterfucks of dependency trees (and sometimes supply chain vulnerabilities) because someone decided they needed to include an external library for a simple transform (in our rtrim example), or to put some quality-of-life candy shell around the language’s natural features.

Think I’m joking? There is literally an NPM package trim-right where the actual functioning code is right here. That’s a whole dependency for, functionally, ~7 lines of code. At the time of this writing, this has 52 projects depending on it, and 6,290,270 weekly downloads. Your project does not magically become slim and efficient by offloading all of your code to external dependencies; It becomes shit.

Don’t get me wrong, there are exceptions. Maybe you have a whole bunch of smaller modules to your project and you don’t want each one to have its own copy of a small utility function. In that case, I’d probably argue in favor of providing each of those modules with a reference to the utility function, rather than having them depend on that code, i.e. dependency injection.

Code, in general, isn’t that complicated; It’s when everything depends on something else that it starts to get increasingly difficult to understand. A 100 line function only gets confusing when the code it calls — its dependencies — have unexpected effects. Adding a dependency to get a 7 line function is ridiculous. Adding a 451MB library to get the same result is insane.

So, yes, the 3-line rtrim C++ example above is better when split into 3 lines. On the other hand, it’s much, much worse when turned into a giant new dependency. Here’s a version that took a minute to write up, and allows me to comprehend it at a glance:

// zero-fills the end of the string until it encounters a non-space value,
// then returns the new string length.
int rtrim(char* s) {
char* end = s + (strlen(s) - 1);
while (end >= s && isspace(*end) {
*(end--) = 0;
}
return (end - s) + 1;
}

Prefer a non-destructive version? Make a copy and run it on that instead. If you wanted to mix C and C++ style (shh, that’s another article) you could use string::substr after doing the backward search on the string’s .c_str(). There are dozens of ways to get this functionality, including just using the 3-liner full of std:: components.

The point is, as the cost of a minute, we can have perfectly functional trim functions, and we don’t have to include anything outside of a few std lib functions; Even then, I wouldn’t call strlen or isspace required, since they’d be trivial to write yourself if you really wanted. I’m making the assumption that standard versions of those functions are fine.

I guess the final point is that if you can avoid a dependency with a few minutes of work, it’s probably best to just do the work. Cluttering your code up with dependencies is a pretty gross price to pay for a few minutes of “free” productivity.