DEV Community

W. Brian Gourlie
W. Brian Gourlie

Posted on • Edited on

NPM hot-takes and the pitfalls of trivialization

The infamous left-pad controversy

Most people are probably aware of the left-pad controversy by now. If not, I'll get you up to speed:

The maintainer of a popular NPM package deleted their package in protest, breaking thousands of other packages that had depended on it. The problem was quickly remedied and solutions put in place to prevent it from happening again, however, following the controversy was a deluge of blog posts and articles asking why we're dependent on tiny libraries that solve such trivial problems. "Have we forgot how to program?" one blog post asked, illustrating the general sentiment at the time.

Left-pad redux

The left-pad controversy still comes up from time-to-time, usually as the butt of jokes. I recently ran across a Medium article pointing out the absurdity of the is-odd package, which as of this writing has over 3 million downloads over the past seven days. Indeed, on the surface, relying on a package to determine if a number is odd appears to be the epitome of overkill. One of the most highly clapped comments made the following claim:

If you can’t write a function to determine if a given integral input is odd in under 10 seconds, you’re either a crappy typist or you shouldn’t be programming.

That's a pretty bold claim, and needlessly insulting to boot. Is it really that simple? The typical 10 second solution would probably look something like this:

function isOdd(i) { return i % 2 === 1; }
Enter fullscreen mode Exit fullscreen mode

Let's write some unit tests just for the hell of it:

expect(isOdd(-2)).toBe(false);
expect(isOdd(-1)).toBe(true);
expect(isOdd(0)).toBe(false);
expect(isOdd(1)).toBe(true);
expect(isOdd(-2)).toBe(false);
Enter fullscreen mode Exit fullscreen mode

Boom! Done. Let's call it a day.

Just kidding. We're not done yet.

When we initially considered this problem, we kind of assumed that all inputs would be integers. However, there's no way to enforce that a particular type be passed to the function. Not only that, but all numbers in javascript are actually double precision floats. Let's add a few unit tests to make sure our function works with floating point inputs:

expect(isOdd(1.5)).toBe(true); // FAIL!
Enter fullscreen mode Exit fullscreen mode

Ok, so our function doesn't work with floating point numbers. There are a couple options at this point:

  • I don't intend to pass anything but whole numbers to the function, so I don't care if it returns the wrong result.

  • I'll just return true if it's a not a whole number.

  • I'll throw an exception if it's not a whole number.

None of these solutions is necessarily wrong, since the best solution could involve any number of considerations. Either way, it still warrants consideration. Not quite the 10 second solution we initially thought it was.

Ok we're done. The end.

Just kidding, there's one last thing we need to consider here: Javascript is dynamically-typed and we need to determine how to handle non-numeric inputs. Again, we're faced with a few different solutions:

  • I don't intend on passing non-numeric inputs to the function, so I don't care what happens if I do.

  • I'll just let javascript do whatever wacky implicit conversion it wants and accept the result.

  • I'll throw an exception.

Again, none of these is necessarily wrong, but it's yet another consideration we didn't initially think of. Trivial libraries handle these considerations so we don't have to.

Behavior is not trivial

Trivial libraries do more than just solve trivial problems. They consider all the little edge cases that we're likely to overlook and provide consistent and robust behavior. This is especially important with languages like javascript.

As a general rule of thumb, I try not to trivialize things that look trivial on the surface. Programming, like the real world, is non-trivial and full of nuance.

Top comments (12)

Collapse
 
remotesynth profile image
Brian Rinaldi • Edited

So the comment you cite is obnoxious and unnecessary for sure, but it's funny that you cite that comment rather than anything in the article, which makes a number of important points imo. Let's look at is-odd, for instance. Yes, the library does check for non-integers and non-number values and throws an error in both cases, but this added a whole 4 lines of code to the core library (and not much more to the tests). You'd never guess the solution was still so simple given the manner in which you've chosen to present it.

However, the bigger point the author of the article (rather than the comment) made was that there are potential risks that come with adding dependencies and that those risks may outweigh the value of code that is, in fact, trivial. His larger point was more about the lack of security in the npm ecosystem in that packages get removed and potentially replaced with malicious code since anyone could take the same package name. Here's the article (since you don't actually link to it).

So, even if you accept your argument that even trivial code isn't that trivial (which, fwiw, I don't), it doesn't even address his actual concern which was that trivial dependencies needlessly open your app up to potential security risks for minimal gain.

Collapse
 
dubyabrian profile image
W. Brian Gourlie • Edited

Yes, the library does check for non-integers and non-number values and throws an error in both cases, but this added a whole 4 lines of code to the core library (and not much more to the tests). You'd never guess the solution was still so simple given the manner if which you've chosen to present it.

4 lines of code can contain a lot of nuance and complexity. Without looking at the is-odd source, would you have known the best way to check if a floating point number is a whole number? Would you have known the appropriate built-in exception to be thrown in order to be semantically correct?

Here is a single line of code from the is-odd source:

return !!(~~i & 1);

Care to explain what this single line of code is doing, and why?

However, the bigger point the author of the article (rather than the comment) made was that there are potential risks that come with adding dependencies and that those risks may outweigh the value of code that is, in fact, trivial.

This was addressed as a result of the left-pad incident, specifically, the possibility of an unpublished package being replaced with a malicious package. Beyond this already addressed issue, your argument is applicable to literally all open-source development.

The kicker here is that trivial dependencies are far easier to audit, so even if you were going to take a hard-line paranoid approach, you seem to imply that bigger libraries are favorable because... well I'm not sure what your justification is. I'm pretty sure you're not either.

Collapse
 
remotesynth profile image
Brian Rinaldi • Edited

you seem to imply that bigger libraries are favorable because... well I'm not sure what your justification is. I'm pretty sure you're not either.

Hmmm.. Was I in any way this condescending in my comment? I don't think so.

Moving past that though, I was stating that the argument you rebut does not actually make the case you are rebutting (i.e. that trivial packages are not of value simply because they are trivial). Personally, I think his security concern is slightly overblown. However, I also feel that developers tend to add libraries without being aware of what is in them (or what is in the dependencies of their dependencies and so forth) - in which case they do open themselves up to potential security risks. It might be trivial to audit a trivial dependency, but that doesn't mean most developers are actually doing the auditing. I do think the danger is there that developers unintentionally install something malicious simply out of a complete lack of awareness rather than as a patch to a trivial dependency (which is what he argues).

I also think the triviality of things like is-odd makes it a needless dependency. If you don't want to write it yourself, why not take the 4 lines of code (and tests) and pop it into a utility library of some sort? This isn't really a maintenance issue - the code for a function of this sort is unlikely to ever change. It's not like you're missing out on some important update to how odd numbers are determined. This removes both the risk of installing a trivial library that you probably aren't auditing while also removing the risk of breaking due to the dependency being removed. Are both those risks trivial? Perhaps, but so is the code.

Thread Thread
 
dubyabrian profile image
W. Brian Gourlie • Edited

Was I in any way this condescending in my comment? I don't think so.

I certainly perceived as much. This article was sparked by a comment I read, and you seemed to take issue with the fact that I wasn't addressing the the article that the comment was in response to. I happen to disagree with the article as well, but that's not what I was addressing.

Again, your issue seems to be with the inherent trust we as developers place in the open source libraries we consume. This is not specific to NPM, or trivial libraries, it's inherent to open-source development. If this particular issue is something you feel strongly about, I suggest you write a post about it.

Thread Thread
 
remotesynth profile image
Brian Rinaldi

I didn't intend it to be condescending. So I apologize if you perceived it that way. I did think the context of the comment, especially given that it's author was being intentionally abrasive (he even admits to "going there") was important since it gave a potential misconception that the article it responded to made that sort of exclusionary argument (i.e. the "you're not a real developer unless..." type of argument) about trivial libraries.

Finally, I, in no way, made the case that developers should be broadly distrusting of open source. You generalize my argument. There is a risk/reward balance and the risk isn't just malicious code - it could also be that a removed or broken dependency breaks my build, which may or may not be a simple fix. In my view, the risk for a trivial dependency does not outweigh the reward. In your view, it does.

Collapse
 
v6 profile image
🦄N B🛡 • Edited

Care to explain what this single line of code is doing, and why?

No. No that is hideous and will haunt my dreams.

I am reminded of my many failed attempts to learn client-side javascript well enough to be productive.

"So, bruh... you've been figuring out how to get that date picker to work with left justification, for what, 2 weeks now?"

"Yes. Yes, for 2 weeks. At this point, I'm ready to publish an academic paper on it for Cornell."

Collapse
 
alainvanhout profile image
Alain Van Hout

The points you make are important ones with regard to using library methods. The question then becomes: do we need a separate library/dependency/liability for every trivial function (even if trivial >> 10 sec)?

(for context, I approach this from the Java ecosystem, where a small number of almost-de-facto-standard libraries are available to serve most needs)

Collapse
 
dubyabrian profile image
W. Brian Gourlie

I think that javascript is unique for a couple of reasons. Unlike most languages, javascript has a minimal standard library, so a lot of functionality that you'd expect to be included with the language isn't present, so people turn to NPM.

Someone could conceivably create a package to include all the trivial functionality that is otherwise being included piecemeal, but this package would grow to be pretty big. Then we're including a bunch of functionality we don't necessarily need to get access to one or two functions.

Collapse
 
alainvanhout profile image
Alain Van Hout

Definitely not to start a (reverse) war of the languages, but the java standard library is wholy inadequate. Together with tree shaking on the one hand, and the fact that a typical webapp pipes a lot of javascript on the other, there is no technical or practical impediment to having bigger libraries.

Thread Thread
 
dubyabrian profile image
W. Brian Gourlie • Edited

I actually feel like Java's standard library has improved considerably, especially with Java 8.

Tree shaking would solve the issue of depending on a handful of functions from a large library, but tree shaking also requires some effort to set up and can be imprecise due to the highly dynamic nature of javascript.

The biggest challenge at this point would be adoption. In practice, with so many packages depending transitively on things like left-pad and is-odd, introducing a consolidated package would likely just fragment things and make it worse.

Assuming that the issue of breaking thousands of packages à la left-pad is solved, I don't find micro-libraries to be particularly distasteful.

Collapse
 
thorstenhirsch profile image
Thorsten Hirsch

I can't stand looking at your unequal use of whitespace in the curly braces.

function isOdd(i) {return i % 2 === 1; }
Enter fullscreen mode Exit fullscreen mode

Please fix it. Pleeeeeeeease!!! ;-)

Collapse
 
dubyabrian profile image
W. Brian Gourlie

This is what happens when I'm left without my trusty code formatters. Fixed :)