No! The opposite of that. Lots of little µframeworks, defining composable and generic types, is much better than a giant monolith.
The Swift Package Manager is taking this approach, and I think it's great: https://github.com/apple/swift-package-manager#modules
The caret character doesn't appear anywhere in the semver spec, so whatever that does, it's non-standard: http://semver.org/
If your modules are small and well-defined, they probably won't need many versions anyways - they might just stay on 1.0.x forever. If you want to do something different, it might make more sense to just write another module.
I implemented KeyMirror from NPM once. It's a simple array->object transformation. It's been in production for months without issue. But, I initially got guff from my boss over it for not using the package. If anything, the package is just an example proof-of-concept of an extremely simple idea. But, carrying the bloat of another package next to more relevant packages seems to be more important here than just merely owning a simple piece of code like this.
For example, ^1.3.2 will allow anything greater than 1.3.2 but not 2.0.0. It also has special behaviour that makes it more strict for projects with a major version of 0. If your dependencies follow semver then you'll get bug fixes and security updates without having to do anything or worry about breaking changes.
More info: https://nodesource.com/blog/semver-tilde-and-caret/
Trivial dependencies are a code smell, a liability, and an operational headache.
It may seem simple to write leftpad, but if 1000 projects that need it all write their own version, there will be at least 2000 more software bugs out there in the wild because of it. If you think that's rediculous, you're not being realistic about the huge disparity in skill levels of industry programmers as well as the considerable rush under which many projects are done.
Also important is that every time I can use a public ally available utility instead of writing it myself, it's one less thing I have to think about. The benefit of making less decisions really adds up, saving a ton of mental capacity to focus on the more important parts of my project. Even the simplest methods require some thought to design.
I know there are disadvantages (such as what happened as the topic of this post), but there are also ways to mitigate them. As far as having many versions that all do the same thing, there is usually winners and losers over time. Because of this I believe that eventually the dependency graph shrinks overall.
Note that I wouldn't advocate creating utilities for things that are not very generalizable.
In short: it would take me a whole lot more time to evaluate whether or not to depend on something as trivial as leftpad than to write it myself. I'm pretty confident I can do that in a minute or two and I trust my collaborators to not break it.
If you fixed the bad behavior you're experiencing, and the original author's effort saved you hours or days of coding, what's the downside?
Perhaps I'm not arguing for 15 second long code changes. But other than typing a single if statement, what takes literally less than one minute to securely change in any partially-complex project?
Something about javascript makes people crazy...
Does npm let you tag releases as security fixes? That would make automation to discover it possible.
That's... very naive. No one goes back and rewrites perfectly working code just to change a library. If it works don't touch it. Computers don't care, and if you rewrite it, you're introducing a bug. Also, there's plenty of new code to write! And oh yeah you have a billion little libraries, all used by an engineering culture constantly distracted by the new shinny, so you're going to be stuck with libraries that that haven't updated.
You're gonna have a bad time.
The benefits from an install registry don't go away just because the module is very tiny...
Why would i spend my time re-inventing the wheel for every little thing i do? And if i'm not reinventing, then i'd be copy/pasting which is much worse. At best that's a waste of time and effort to properly document the source, and at worst it's stealing or license violations.
I don't care if a module is a single line, if it does what i need it to and is well tested, then i'll use it. That might seem silly, but the fact is that it's pretty much no overhead, and no software is immune from bugs (even a 16 line function), so updates might be useful in the future.
Yeah, there is a chance that stuff like this can happen, but within an hour there were several alternatives to solve issues with installs, i'd say the system is working pretty well. Plus with proper software development techniques (like vendoring your dependencies) this wouldn't even be a problem at all.
Consider this specific case. This author moved all their modules from one hosted location to another. Now, if you want to use these modules from that author, you need to update the scripts and configs that install them (some package.json files in this case). In a better world, like the C or Python world, you might need to update one or two urls which point to a couple of this author's popular libraries (maybe one you use directly, and one used by one of your handful of direct dependencies).
In this crazy npm world, this author has 272 modules. Maybe 20 are in widespread use ... it's already a lot of work to figure that out. Maybe you use a couple directly, and your dependencies have private recursive sub-dependencies on additional copies or versions of these or other of this author's modules! Maybe you have to cut your own versions of some of your dependencies just to change their package.json to refer to the new URLs! Anyway, you probably have to sift through hundreds of your dependencies and sub-dependencies to see if any of them are included in these 272 moved modules.
I've seen npm dependency trees with over 2000 modules (not all unique of course). That's totally unmanageable. I think that's why privately versioned sub-dependencies is a big feature in nodejs: so you can try to ignore the problem of an unmanageable dependency tree. But if you need to make reliable software, at some point you need to manage your dependencies.
Also a "provides" field could go a long way into stopping issues like this. Allow packages to say that they provide a package in them that is compatible with another in these version ranges.
That would let "API compatible" packages be dropped in to replace even deeply nested packages easily, and would allow easy "bundling" in big libraries while still allowing easy creation and access to "micro libs".
I really believe that composing tons of small libraries is the way to go, but there needs to be better tooling to make it work. In my (admittedly not extremely expirenced) opinion, bundling many small libs into one big package to make it manageable is a symptom of a problem, not its resolution.
These 17 lines had 100% test coverage and were used by a stupidly large amount of people (read: battle tested), why not use it?
As is pointed out elsewhere in this thread, echo.c is roughly the same size, does that mean it's not a worthy program?
The fact that in JS land it would be it's a standalone module means you get more choice in what you need (no need to pull down 100 programs if you only need 1 or 2).
But to be fair this would have the same outcome if left-pad were part of a library that included another 50+ libs (that he also wrote and published, and subsequently un-published today).
Choice can be a bad thing too - when there are 10 different modules for doing a moderately complex thing, you have to figure out which one is best for your project, and whether it's still actively maintained, bugs are fixed, how do they feel about making breaking changes, etc.
Not necessarily, take a look at lodash and friends. There is nothing stopping bundling of tiny modules into big "libraries" to be used.
As for the rest, you need to do that validation anyway. But if it were bundled in a large library there is MUCH more code that you need to review.
with something like the module "left-pad", it's a no brainer to use the library. I know that it's under an EXTREMELY open license, the code is really small, and by vendoring your dependencies (you are vendoring your dependencies right?) this whole issue would have been a 5-10 minute fix that only needed to be done when you want to upgrade your dependencies next time.
And that doesn't remove the need for a library like this (or your own implementation) for a long time because you can't rely on a brand new release to be available for everyone.
I've seen several "one liners" in this thread already, and most of them either blow up when something that's not a string is passed in (regardless of how you view strict typing, js doesn't have it and this shouldn't happen), or are extremely slow comparatively (most of them creating and destroying an array every time they are called).
Plus this has 100% test coverage (even as trivial as it is, it still counts), and is "battle tested" (something like 2.5 million installs per month counts for something).
Sorry, but i'll stick to left-pad vs 20-seconds of thought one-liner.
How's this:
function leftpad (str, len, ch) {
ch = (len -= str.length) <= 0 ? '' : !ch && ch !== 0 ? ' ' : String(ch);
while (--len > 0) ch += ch[0];
return ch + String(str);
}
No local variables, less manipulation of the input string, the string grows at the tail which is more efficient, and the code is much shorter.(With a bit of work you can use the longer ch string that is built to reduce the number of string appends even more by appending multiple characters at once. Although probably not worth it for this function.)
I strongly disagree. My code has no magic initializers (the -1 in the original) and a simple linear code path, with no branching. It's very easy to read and understand.
The ternary operator at the top is simply read from left to right, it's not complicated.
> If your goal is to minimize the amount of lines, then you succeeded.
My goal was to maximize efficiency. Often that means less lines, but that was not the overt goal. And in fact this version runs faster, and uses less memory.
> If the goal is to produce both correct and readable code, then there's room for improvement.
You think so?
Then now it's your turn - rewrite this (or the original) to make it as readable as possible. I think you will find that a: mine is more readable than the original, and b: you won't be able to (need to) change much except to lift the len initializer out of the ternary operator in the first line onto its own line.
while (++i < len) {
str = ch + str;
}
And isn't this a bug?leftpad("x", 2, '00') will return "00x"
This is where the JavaScript ecosystem is right now -- JS doesn't have the kind of robust standard library other languages take for granted, so you end up with a lot of these things that look like they should be stdlib functions, but are instead third-party packages the entire world has to depend on for a usable programming environment.
This has nothing to do with the JavaScript standard library but the insanity of NPM. There are modules that have even more dependents than `pad-left` despite being included in the standard library (e.g. https://www.npmjs.com/package/date-now, https://www.npmjs.com/package/is-positive, https://www.npmjs.com/package/is-lower-case)
JS has an extremely large and robust standard library in comparison.
At this point, optimizing runtimes to only include necessary functions, and optimizing files to only include necessary functions are both things that are pretty much a solved problem. For example, azer has a random-color library, and an rng library. Having both of those as azer-random or something means that someone automatically gets all the dependencies, without having to make many requests to the server. This makes build times shorter and a lot easier.
Sometimes, in order to best optimize for small, you have to have some parts that are big. Libraries tend to be one of those things where a few good, big libraries lead to a smaller footprint than many tiny libraries.
> makes security auditing more difficult
What? If you go all the way, you just review all dependencies too. And if they have a good API, it's actually much easier. For example if your only source of filesystem access is libfilesystem, you can quickly list all modules which have any permanent local state.
Splitting huge libraries into well designed categories would make a lot of reviews easier.
> Having both of those as azer-random or something means that someone automatically gets all the dependencies, without having to make many requests to the server.
Also disagree. One-off builds shouldn't make a real difference. Continuous builds should have both local mirror and local caches.
A bit of code duplication would go a long way towards bringing sanity to JS land.
It's interesting to think about the distinction between promiscuous dependencies (as pioneered by Gemfile) and the Unix way. I like the latter and loathe the former, but maybe I'm wrong. Can someone think of a better example? Is there a modern dpkg/rpm/yum package with <50 LoC of payload?
Edit: Incidentally, I just checked and echo.c in coreutils 8.25 weighs in at 194 LoC (excluding comments, empty lines and just curly braces). And that doesn't even include other files that are needed to build echo. Back in 2013 I did a similar analysis for cat, and found that it required 36 thousand lines of code (just .c and .h files). It's my favorite example of the rot at Linux's core. There's many complaints you can make about Unix package management, but 'too many tiny dependencies' seems unlikely to be one of them.
I'm starting to see where http://suckless.org/philosophy and http://landley.net/aboriginal/ are coming from (watch Rob's talks, they're very opinionated but very enjoyable).
I didn't dig too deeply in 2013, but I did notice that about 2/3rds of the LoC were in headers.
#include <sys/param.h> #include <sys/stat.h> #include <ctype.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h>
which are needed for things like memory allocation, filesystem access, io, and so on.
One can imagine alternative implementations of cat that are full of #ifdefs to handle all the glorious varieties of unix that have ever been out there.
I thought how commands are bundled into packages was the entirety of what we were discussing. That was my interpretation of larkinrichards's comment (way) up above. Packages are the unit of installation, not use. Packages are all we're arguing about.
My position: don't put words in God's mouth :) The unix way is commands that do one thing and do it well. But the unix way is silent on the right way to disseminate said commands. There you're on your own.
I support packages of utility functions. Distributing them individually is a waste of resources when you have tree shaking.
I trust a dependency on lodash. I don't trust a dependency on a single 17 line function.
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true...
In OpenBSD, it's 3 LoC IIRC.
If not, writing it yourself works too. Or advocate for a better string stdlib.
For 0.0.x versions of packages the caret means "this version and this version only", so it won't break anything here...
Source: https://docs.npmjs.com/misc/semver#caret-ranges-123-025-004
A developer could come along, find this library, and hard-copy it into their source repo. The tested source code is there, and they don't have a dependency on npm.
This wouldn't work quite so well for large packages (chance of bugs is high and so patches are important), but for something like this? Just ditch npm.
I tend to agree, but this is conflating the issue of having dependencies with delivery.
It's perfectly ok to build small and compose large, with some of the smaller constituents being external dependencies, but the problem here is that the delivery of the packages isn't immutable and static. When you publish a package to npm, you don't publish it along with a copy of all your dependencies (by default, there are mechanisms to do this however.) The external dependencies will be pulled in separately when people install your package. What you're suggesting could still be done with an external dependency, just by making sure you it's only external at development time, but at publish it is truly bundled along with your package. This obviously comes with other costs, like the inability to dedupe packages.
This is an advantage of language concision. In coffeescript this isn't even a function, it's just a one-line idiom:
(ch for [0...len]).join('')[str.length..] + str
It is worth pointing to the silly state of NPM packages: Who decided that an external dependency was necessary for a module that is 17 lines of code?
Developers: less dependencies is better, especially when they're so simple!You know what's also awesome? The caret semver specifier[1]. You could install a new, broken version of a dependency doing that-- especially when other packages using peerDependencies rely on specific versions and you've used a caret semver specifier.
[1] https://github.com/lydell/line-numbers/pull/3/files